From e0bf533272e7f113f5b4f77f54aee3ba13bef8f6 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 01:32:05 +0100 Subject: [PATCH 0001/1030] Added basic Gem support --- .gitmodules | 3 + CMakeLists.txt | 9 + Gem | 1 + Libraries/CMakeLists.txt | 10 +- Libraries/clap-juce-extensions | 2 +- Libraries/melatonin_blur | 2 +- Source/Objects/Gem.h | 181 ++++ Source/Objects/ImplementationBase.cpp | 1 + Source/Pd/Instance.cpp | 5 + Source/Pd/MessageListener.h | 2 +- Source/Pd/Setup.cpp | 1095 +++++++++++++++++++++++++ Source/Pd/Setup.h | 1 + 12 files changed, 1305 insertions(+), 7 deletions(-) create mode 160000 Gem create mode 100644 Source/Objects/Gem.h diff --git a/.gitmodules b/.gitmodules index c2dbb506d7..fab54093e7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -42,3 +42,6 @@ [submodule "Libraries/melatonin_blur"] path = Libraries/melatonin_blur url = https://github.com/sudara/melatonin_blur.git +[submodule "Gem"] + path = Gem + url = https://github.com/plugdata-team/plugdata-gem.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 37532e92c2..1a0ce6b1ac 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,9 @@ set(PLUGDATA_COMPANY_WEBSITE "github.com/plugdata-team/plugdata") if("${CMAKE_SYSTEM_NAME}" MATCHES "iOS") set(PLUGDATA_ICON_BIG "${CMAKE_CURRENT_SOURCE_DIR}/Resources/Icons/plugdata_logo_ios.png") +file(GLOB IOS_LAUNCHSCREEN "${CMAKE_CURRENT_SOURCE_DIR}/Resources/Icons/plugdata_launchscreen_ios.png") +set_source_files_properties(${IOS_LAUNCHSCREEN} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + elseif(APPLE) set(PLUGDATA_ICON_BIG "${CMAKE_CURRENT_SOURCE_DIR}/Resources/Icons/plugdata_logo_mac.png") else() @@ -221,6 +224,7 @@ target_link_libraries(juce juce::juce_audio_plugin_client juce::juce_dsp juce::juce_cryptography + juce::juce_opengl PUBLIC juce::juce_recommended_lto_flags #juce::juce_recommended_warning_flags @@ -539,6 +543,11 @@ set_property(TARGET Tests PROPERTY VISIBILITY_INLINES_HIDDEN ON) endif() +if(APPLE) +# 2x speedup for debug builds in xcode +SET_TARGET_PROPERTIES(myTarget PROPERTIES XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH[variant=Debug] "YES") +endif() + if(MSVC) set_target_properties(pthreadVC3 pthreadVSE3 pthreadVCE3 PROPERTIES EXCLUDE_FROM_ALL 1 EXCLUDE_FROM_DEFAULT_BUILD 1) endif() diff --git a/Gem b/Gem new file mode 160000 index 0000000000..ae972171cf --- /dev/null +++ b/Gem @@ -0,0 +1 @@ +Subproject commit ae972171cfd5c38c5319b89e8810eba0d997922f diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 0c217a7e2f..f9d5411e75 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -253,12 +253,14 @@ source_group(pdlua FILES ${PDLUA_SOURCES}) # ------------------------------------------------------------------------------# set(LIBPD_COMPILE_DEFINITIONS PD=1 USEAPI_DUMMY=1 PD_INTERNAL=1) +add_subdirectory(Gem) if(ENABLE_SFIZZ) list(APPEND LIBPD_COMPILE_DEFINITIONS ENABLE_SFIZZ=1) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/pd-else/Code_source/Compiled/audio/sfz~) endif() + # COMPILE DEFINITIONS OPTIONS # ------------------------------------------------------------------------------# if(PD_EXTRA) @@ -383,8 +385,8 @@ source_group(ofelia FILES ${OFELIA_SOURCES}) # ------------------------------------------------------------------------------# # TARGETS # ------------------------------------------------------------------------------# -add_library(externals STATIC ${ELSE_SOURCES} ${CYCLONE_SOURCES} ${PDLUA_SOURCES} ${AUBIO_SOURCES} ${OFELIA_SOURCES}) -add_library(externals-multi STATIC ${ELSE_SOURCES} ${CYCLONE_SOURCES} ${PDLUA_SOURCES} ${AUBIO_SOURCES} ${OFELIA_SOURCES}) +add_library(externals STATIC ${ELSE_SOURCES} ${CYCLONE_SOURCES} ${GEM_SOURCES} ${PDLUA_SOURCES} ${AUBIO_SOURCES} ${OFELIA_SOURCES}) +add_library(externals-multi STATIC ${ELSE_SOURCES} ${CYCLONE_SOURCES} ${GEM_SOURCES} ${PDLUA_SOURCES} ${AUBIO_SOURCES} ${OFELIA_SOURCES}) # ag: pdlua needs PLUGDATA symbol at compile time for PlugData integration. target_compile_definitions(externals PRIVATE ${LIBPD_COMPILE_DEFINITIONS} PLUGDATA=1) @@ -443,8 +445,8 @@ elseif(MSVC) "${CMAKE_CURRENT_BINARY_DIR}/$/pd.dll" ${CMAKE_SOURCE_DIR}/Plugins/Standalone/pd.dll) elseif(APPLE) - target_link_libraries(externals ${externals_libs}) - target_link_libraries(externals-multi ${externals_libs}) + target_link_libraries(externals ${externals_libs} Gem) + target_link_libraries(externals-multi ${externals_libs} Gem) endif() # PTHREAD diff --git a/Libraries/clap-juce-extensions b/Libraries/clap-juce-extensions index 5f369ad244..54d83e1215 160000 --- a/Libraries/clap-juce-extensions +++ b/Libraries/clap-juce-extensions @@ -1 +1 @@ -Subproject commit 5f369ad244e4b1ccd2eb943bd6dac993fb444262 +Subproject commit 54d83e121545490fdbb2d3dec3ba0c1da6005dde diff --git a/Libraries/melatonin_blur b/Libraries/melatonin_blur index 2356c3ed5e..4c2423c7ba 160000 --- a/Libraries/melatonin_blur +++ b/Libraries/melatonin_blur @@ -1 +1 @@ -Subproject commit 2356c3ed5e84fe5fceca6c6b1342fee53fbadddc +Subproject commit 4c2423c7bad5cc407fbc2884b70f8577b6059c08 diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h new file mode 100644 index 0000000000..c3f5f26eaa --- /dev/null +++ b/Source/Objects/Gem.h @@ -0,0 +1,181 @@ +/* + // Copyright (c) 2021-2022 Timothy Schoen + // For information on usage and redistribution, and for a DISCLAIMER OF ALL + // WARRANTIES, see the file, "LICENSE.txt," in this distribution. + */ + +#include +#include + +extern void performGemRender(); +extern void initGemWindow(); + +void dequeueEvents(void); + +void triggerMotionEvent(int x, int y); +void triggerButtonEvent(int which, int state, int x, int y); +void triggerWheelEvent(int axis, int value); +void triggerKeyboardEvent(const char *string, int value, int state); +void triggerResizeEvent(int xSize, int ySize); + +class GemJUCEWindow final : public OpenGLAppComponent +{ +public: + //============================================================================== + GemJUCEWindow() + { + setSize (800, 600); + //openGLContext.setSwapInterval(0); + openGLContext.setMultisamplingEnabled(true); + + auto pixelFormat = OpenGLPixelFormat(8, 8, 16, 8); + pixelFormat.multisamplingLevel = 2; + openGLContext.setPixelFormat(pixelFormat); + } + + ~GemJUCEWindow() override + { + shutdownOpenGL(); + } + + void initialise() override + { + openGLContext.makeActive(); + initGemWindow(); + } + + void shutdown() override + { + } + + void render() override + { + OpenGLHelpers::clear(Colours::black); + + sys_lock(); + dequeueEvents(); + performGemRender(); + sys_unlock(); + } + + void resized() override + { + const ScopedLock lock (mutex); + bounds = getLocalBounds(); + + triggerResizeEvent(bounds.getWidth(), bounds.getHeight()); + } + + void paint (Graphics&) override + { + } + + void mouseDown(const MouseEvent& e) override + { + triggerButtonEvent(e.mods.isRightButtonDown(), 1, e.x, e.y); + } + + void mouseUp(const MouseEvent& e) override + { + triggerButtonEvent(e.mods.isRightButtonDown(), 0, e.x, e.y); + } + + void mouseMove(const MouseEvent& e) override + { + triggerMotionEvent(e.x, e.y); + } + void mouseDrag(const MouseEvent& e) override + { + triggerMotionEvent(e.x, e.y); + } + + void mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) override + { + triggerWheelEvent(wheel.deltaX, wheel.deltaY); + } + + bool keyPressed(KeyPress const& key) override + { + //triggerKeyboardEvent(key.getTextDescription().toRawUTF8(), key.getKeyCode(), 1); + } + +private: + + Rectangle bounds; + CriticalSection mutex; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GemJUCEWindow) +}; + +// window/context creation&destruction +bool initGemWin(void) +{ + return true; +} + +std::unique_ptr gemJUCEWindow; + +int createGemWindow(WindowInfo& info, WindowHints& hints) +{ + if(info.window) + { + info.window->removeFromDesktop(); + delete info.window; + } + + auto* window = new GemJUCEWindow(); + gemJUCEWindow.reset(window); + info.window = window; + + info.window->addToDesktop(ComponentPeer::windowHasTitleBar | ComponentPeer::windowIsResizable | ComponentPeer::windowHasDropShadow); + window->setVisible(true); + window->openGLContext.makeActive(); + info.context = &window->openGLContext; + //info.have_constContext = 1; + + hints.real_w = window->getWidth(); + hints.real_h = window->getHeight(); + + return 1; +} +void destroyGemWindow(WindowInfo& info) { + if(info.window) { + info.window->removeFromDesktop(); + delete info.window; + } +} + +void initWin_sharedContext(WindowInfo& info, WindowHints& hints) { + //info.have_constContext = 1; + if(info.window) info.window->openGLContext.makeActive(); +} + +// Rendering +void gemWinSwapBuffers(WindowInfo& info) { + if (info.context) { + info.context->makeActive(); + info.context->swapBuffers(); + } +} +void gemWinMakeCurrent(WindowInfo& info) { + if (info.context) { + info.context->makeActive(); + } +} + +// We handle this manually with JUCE +void dispatchGemWindowMessages(WindowInfo& info) { +} + +// Window behaviour +int cursorGemWindow(WindowInfo& info, int state) +{ + if(info.window) info.window->setMouseCursor(state ? MouseCursor::NormalCursor : MouseCursor::NoCursor); + return state; +} + +int topmostGemWindow(WindowInfo& info, int state) +{ + if(info.window && state) info.window->toFront(true); + return state; +} diff --git a/Source/Objects/ImplementationBase.cpp b/Source/Objects/ImplementationBase.cpp index 415cdd2c4a..9ca5df1cff 100644 --- a/Source/Objects/ImplementationBase.cpp +++ b/Source/Objects/ImplementationBase.cpp @@ -27,6 +27,7 @@ int clone_get_n(t_gobj*); #include "ImplementationBase.h" #include "ObjectImplementations.h" +#include "Gem.h" ImplementationBase::ImplementationBase(t_gobj* obj, t_canvas* parent, PluginProcessor* processor) : pd(processor) diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 18bb06287e..0ac5fdbc5a 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -284,6 +284,11 @@ void Instance::initialisePd(String& pdlua_version) set_class_prefix(gensym("cyclone")); class_set_extern_dir(gensym("10.cyclone")); pd::Setup::initialiseCyclone(); + + set_class_prefix(gensym("gem")); + + class_set_extern_dir(gensym(ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("14.gem").getFullPathName().toRawUTF8())); + pd::Setup::initialiseGem(); class_set_extern_dir(gensym("")); set_class_prefix(nullptr); diff --git a/Source/Pd/MessageListener.h b/Source/Pd/MessageListener.h index 48e6fc26c9..316722869b 100644 --- a/Source/Pd/MessageListener.h +++ b/Source/Pd/MessageListener.h @@ -149,7 +149,7 @@ class MessageDispatcher : private AsyncUpdater { } } - moodycamel::ReaderWriterQueue messageQueue = moodycamel::ReaderWriterQueue(32768); + moodycamel::ConcurrentQueue messageQueue = moodycamel::ConcurrentQueue(32768); std::map>> messageListeners; CriticalSection messageListenerLock; }; diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index fb0a2ece4e..cd588a3831 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -159,6 +159,545 @@ extern "C" { void pd_init(); +void Gem_setup(); +void gemcubeframebuffer_setup(); +void gemframebuffer_setup(); +void gemhead_setup(); +void gemkeyboard_setup(); +void gemkeyname_setup(); +void gemlist_setup(); +void gemlist_info_setup(); +void gemlist_matrix_setup(); +void gemmanager_setup(); +void gemmouse_setup(); +void gemreceive_setup(); +void gemwin_setup(); +void modelfiler_setup(); +void render_trigger_setup(); +void GemSplash_setup(); +void circle_setup(); +void colorSquare_setup(); +void cone_setup(); +void cube_setup(); +void cuboid_setup(); +void curve_setup(); +void curve3d_setup(); +void cylinder_setup(); +void disk_setup(); +void gemvertexbuffer_setup(); +void imageVert_setup(); +void mesh_line_setup(); +void mesh_square_setup(); +void model_setup(); +void multimodel_setup(); +void newWave_setup(); +void polygon_setup(); +void pqtorusknots_setup(); +void primTri_setup(); +void rectangle_setup(); +void ripple_setup(); +void rubber_setup(); +void scopeXYZ_setup(); +void slideSquares_setup(); +void sphere_setup(); +void sphere3d_setup(); +void square_setup(); +void surface3d_setup(); +void teapot_setup(); +void text2d_setup(); +void text3d_setup(); +void textextruded_setup(); +void textoutline_setup(); +void torus_setup(); +void trapezoid_setup(); +void triangle_setup(); +void tube_setup(); +void accumrotate_setup(); +void alpha_setup(); +void ambient_setup(); +void ambientRGB_setup(); +void camera_setup(); +void color_setup(); +void colorRGB_setup(); +void depth_setup(); +void diffuse_setup(); +void diffuseRGB_setup(); +void emission_setup(); +void emissionRGB_setup(); +void fragment_program_setup(); +void glsl_fragment_setup(); +void glsl_geometry_setup(); +void glsl_program_setup(); +void glsl_tesscontrol_setup(); +void glsl_tesseval_setup(); +void glsl_vertex_setup(); +void linear_path_setup(); +void ortho_setup(); +void polygon_smooth_setup(); +void rotate_setup(); +void rotateXYZ_setup(); +void scale_setup(); +void scaleXYZ_setup(); +void separator_setup(); +void shearXY_setup(); +void shearXZ_setup(); +void shearYX_setup(); +void shearYZ_setup(); +void shearZX_setup(); +void shearZY_setup(); +void shininess_setup(); +void specular_setup(); +void specularRGB_setup(); +void spline_path_setup(); +void translate_setup(); +void translateXYZ_setup(); +void vertex_program_setup(); +void light_setup(); +void spot_light_setup(); +void world_light_setup(); +void part_color_setup(); +void part_damp_setup(); +void part_draw_setup(); +void part_follow_setup(); +void part_gravity_setup(); +void part_head_setup(); +void part_info_setup(); +void part_killold_setup(); +void part_killslow_setup(); +void part_orbitpoint_setup(); +void part_render_setup(); +void part_sink_setup(); +void part_size_setup(); +void part_source_setup(); +void part_targetcolor_setup(); +void part_targetsize_setup(); +void part_velcone_setup(); +void part_velocity_setup(); +void part_velsphere_setup(); +void part_vertex_setup(); +void pix_2grey_setup(); +void pix_a_2grey_setup(); +void pix_add_setup(); +void pix_aging_setup(); +void pix_alpha_setup(); +void pix_background_setup(); +void pix_backlight_setup(); +void pix_biquad_setup(); +void pix_bitmask_setup(); +void pix_blob_setup(); +void pix_blur_setup(); +void pix_buf_setup(); +void pix_buffer_setup(); +void pix_buffer_read_setup(); +void pix_buffer_write_setup(); +void pix_chroma_key_setup(); +void pix_clearblock_setup(); +void pix_color_setup(); +void pix_coloralpha_setup(); +void pix_colorclassify_setup(); +void pix_colormatrix_setup(); +void pix_colorreduce_setup(); +void pix_compare_setup(); +void pix_composite_setup(); +void pix_contrast_setup(); +void pix_convert_setup(); +void pix_convolve_setup(); +void pix_coordinate_setup(); +void pix_crop_setup(); +void pix_cubemap_setup(); +void pix_curve_setup(); +void pix_data_setup(); +void pix_deinterlace_setup(); +void pix_delay_setup(); +void pix_diff_setup(); +void pix_dot_setup(); +void pix_draw_setup(); +void pix_dump_setup(); +void pix_duotone_setup(); +void pix_emboss_setup(); +void pix_equal_setup(); +void pix_film_setup(); +void pix_flip_setup(); +void pix_freeframe_setup(); +void pix_frei0r_setup(); +void pix_gain_setup(); +void pix_grey_setup(); +void pix_halftone_setup(); +void pix_histo_setup(); +void pix_hsv2rgb_setup(); +void pix_image_setup(); +void pix_imageInPlace_setup(); +void pix_info_setup(); +void pix_invert_setup(); +void pix_kaleidoscope_setup(); +void pix_levels_setup(); +void pix_lumaoffset_setup(); +void pix_mask_setup(); +void pix_mean_color_setup(); +void pix_metaimage_setup(); +void pix_mix_setup(); +void pix_motionblur_setup(); +void pix_movement_setup(); +void pix_movement2_setup(); +void pix_movie_setup(); +void pix_multiblob_setup(); +void pix_multiimage_setup(); +void pix_multiply_setup(); +void pix_multitexture_setup(); +void pix_noise_setup(); +void pix_normalize_setup(); +void pix_offset_setup(); +void pix_pix2sig_setup(); +void pix_posterize_setup(); +void pix_puzzle_setup(); +void pix_rds_setup(); +void pix_record_setup(); +void pix_rectangle_setup(); +void pix_refraction_setup(); +void pix_resize_setup(); +void pix_rgb2hsv_setup(); +void pix_rgba_setup(); +void pix_roi_setup(); +void pix_roll_setup(); +void pix_rtx_setup(); +void pix_scanline_setup(); +void pix_set_setup(); +void pix_share_read_setup(); +void pix_share_write_setup(); +void pix_sig2pix_setup(); +void pix_snap_setup(); +void pix_snap2tex_setup(); +void pix_subtract_setup(); +void pix_tIIR_setup(); +void pix_tIIRf_setup(); +void pix_takealpha_setup(); +void pix_test_setup(); +void pix_texture_setup(); +void pix_threshold_setup(); +void pix_threshold_bernsen_setup(); +void pix_video_setup(); +void pix_vpaint_setup(); +void pix_write_setup(); +void pix_writer_setup(); +void pix_yuv_setup(); +void pix_zoom_setup(); +void vertex_add_setup(); +void vertex_combine_setup(); +void vertex_draw_setup(); +void vertex_grid_setup(); +void vertex_info_setup(); +void vertex_model_setup(); +void vertex_mul_setup(); +void vertex_offset_setup(); +void vertex_quad_setup(); +void vertex_scale_setup(); +void vertex_set_setup(); +void vertex_tabread_setup(); +void GEMglAccum_setup(); +void GEMglActiveTexture_setup(); +void GEMglActiveTextureARB_setup(); +void GEMglAlphaFunc_setup(); +void GEMglAreTexturesResident_setup(); +void GEMglArrayElement_setup(); +void GEMglBegin_setup(); +void GEMglBindProgramARB_setup(); +void GEMglBindTexture_setup(); +void GEMglBitmap_setup(); +void GEMglBlendEquation_setup(); +void GEMglBlendFunc_setup(); +void GEMglCallList_setup(); +void GEMglClear_setup(); +void GEMglClearAccum_setup(); +void GEMglClearColor_setup(); +void GEMglClearDepth_setup(); +void GEMglClearIndex_setup(); +void GEMglClearStencil_setup(); +void GEMglClipPlane_setup(); +void GEMglColor3b_setup(); +void GEMglColor3bv_setup(); +void GEMglColor3d_setup(); +void GEMglColor3dv_setup(); +void GEMglColor3f_setup(); +void GEMglColor3fv_setup(); +void GEMglColor3i_setup(); +void GEMglColor3iv_setup(); +void GEMglColor3s_setup(); +void GEMglColor3sv_setup(); +void GEMglColor3ub_setup(); +void GEMglColor3ubv_setup(); +void GEMglColor3ui_setup(); +void GEMglColor3uiv_setup(); +void GEMglColor3us_setup(); +void GEMglColor3usv_setup(); +void GEMglColor4b_setup(); +void GEMglColor4bv_setup(); +void GEMglColor4d_setup(); +void GEMglColor4dv_setup(); +void GEMglColor4f_setup(); +void GEMglColor4fv_setup(); +void GEMglColor4i_setup(); +void GEMglColor4iv_setup(); +void GEMglColor4s_setup(); +void GEMglColor4sv_setup(); +void GEMglColor4ub_setup(); +void GEMglColor4ubv_setup(); +void GEMglColor4ui_setup(); +void GEMglColor4uiv_setup(); +void GEMglColor4us_setup(); +void GEMglColor4usv_setup(); +void GEMglColorMask_setup(); +void GEMglColorMaterial_setup(); +void GEMglCopyPixels_setup(); +void GEMglCopyTexImage1D_setup(); +void GEMglCopyTexImage2D_setup(); +void GEMglCopyTexSubImage1D_setup(); +void GEMglCopyTexSubImage2D_setup(); +void GEMglCullFace_setup(); +void GEMglDeleteTextures_setup(); +void GEMglDepthFunc_setup(); +void GEMglDepthMask_setup(); +void GEMglDepthRange_setup(); +void GEMglDisable_setup(); +void GEMglDisableClientState_setup(); +void GEMglDrawArrays_setup(); +void GEMglDrawBuffer_setup(); +void GEMglDrawElements_setup(); +void GEMglEdgeFlag_setup(); +void GEMglEnable_setup(); +void GEMglEnableClientState_setup(); +void GEMglEnd_setup(); +void GEMglEndList_setup(); +void GEMglEvalCoord1d_setup(); +void GEMglEvalCoord1dv_setup(); +void GEMglEvalCoord1f_setup(); +void GEMglEvalCoord1fv_setup(); +void GEMglEvalCoord2d_setup(); +void GEMglEvalCoord2dv_setup(); +void GEMglEvalCoord2f_setup(); +void GEMglEvalCoord2fv_setup(); +void GEMglEvalMesh1_setup(); +void GEMglEvalMesh2_setup(); +void GEMglEvalPoint1_setup(); +void GEMglEvalPoint2_setup(); +void GEMglFeedbackBuffer_setup(); +void GEMglFinish_setup(); +void GEMglFlush_setup(); +void GEMglFogf_setup(); +void GEMglFogfv_setup(); +void GEMglFogi_setup(); +void GEMglFogiv_setup(); +void GEMglFrontFace_setup(); +void GEMglFrustum_setup(); +void GEMglGenLists_setup(); +void GEMglGenProgramsARB_setup(); +void GEMglGenTextures_setup(); +void GEMglGenerateMipmap_setup(); +void GEMglGetError_setup(); +void GEMglGetFloatv_setup(); +void GEMglGetIntegerv_setup(); +void GEMglGetMapdv_setup(); +void GEMglGetMapfv_setup(); +void GEMglGetMapiv_setup(); +void GEMglGetPointerv_setup(); +void GEMglGetString_setup(); +void GEMglHint_setup(); +void GEMglIndexMask_setup(); +void GEMglIndexd_setup(); +void GEMglIndexdv_setup(); +void GEMglIndexf_setup(); +void GEMglIndexfv_setup(); +void GEMglIndexi_setup(); +void GEMglIndexiv_setup(); +void GEMglIndexs_setup(); +void GEMglIndexsv_setup(); +void GEMglIndexub_setup(); +void GEMglIndexubv_setup(); +void GEMglInitNames_setup(); +void GEMglIsEnabled_setup(); +void GEMglIsList_setup(); +void GEMglIsTexture_setup(); +void GEMglLightModelf_setup(); +void GEMglLightModeli_setup(); +void GEMglLightf_setup(); +void GEMglLighti_setup(); +void GEMglLineStipple_setup(); +void GEMglLineWidth_setup(); +void GEMglLoadIdentity_setup(); +void GEMglLoadMatrixd_setup(); +void GEMglLoadMatrixf_setup(); +void GEMglLoadName_setup(); +void GEMglLoadTransposeMatrixd_setup(); +void GEMglLoadTransposeMatrixf_setup(); +void GEMglLogicOp_setup(); +void GEMglMap1d_setup(); +void GEMglMap1f_setup(); +void GEMglMap2d_setup(); +void GEMglMap2f_setup(); +void GEMglMapGrid1d_setup(); +void GEMglMapGrid1f_setup(); +void GEMglMapGrid2d_setup(); +void GEMglMapGrid2f_setup(); +void GEMglMaterialf_setup(); +void GEMglMaterialfv_setup(); +void GEMglMateriali_setup(); +void GEMglMatrixMode_setup(); +void GEMglMultMatrixd_setup(); +void GEMglMultMatrixf_setup(); +void GEMglMultTransposeMatrixd_setup(); +void GEMglMultTransposeMatrixf_setup(); +void GEMglMultiTexCoord2f_setup(); +void GEMglMultiTexCoord2fARB_setup(); +void GEMglNewList_setup(); +void GEMglNormal3b_setup(); +void GEMglNormal3bv_setup(); +void GEMglNormal3d_setup(); +void GEMglNormal3dv_setup(); +void GEMglNormal3f_setup(); +void GEMglNormal3fv_setup(); +void GEMglNormal3i_setup(); +void GEMglNormal3iv_setup(); +void GEMglNormal3s_setup(); +void GEMglNormal3sv_setup(); +void GEMglOrtho_setup(); +void GEMglPassThrough_setup(); +void GEMglPixelStoref_setup(); +void GEMglPixelStorei_setup(); +void GEMglPixelTransferf_setup(); +void GEMglPixelTransferi_setup(); +void GEMglPixelZoom_setup(); +void GEMglPointSize_setup(); +void GEMglPolygonMode_setup(); +void GEMglPolygonOffset_setup(); +void GEMglPopAttrib_setup(); +void GEMglPopClientAttrib_setup(); +void GEMglPopMatrix_setup(); +void GEMglPopName_setup(); +void GEMglPrioritizeTextures_setup(); +void GEMglProgramEnvParameter4dARB_setup(); +void GEMglProgramEnvParameter4fvARB_setup(); +void GEMglProgramLocalParameter4fvARB_setup(); +void GEMglProgramStringARB_setup(); +void GEMglPushAttrib_setup(); +void GEMglPushClientAttrib_setup(); +void GEMglPushMatrix_setup(); +void GEMglPushName_setup(); +void GEMglRasterPos2d_setup(); +void GEMglRasterPos2dv_setup(); +void GEMglRasterPos2f_setup(); +void GEMglRasterPos2fv_setup(); +void GEMglRasterPos2i_setup(); +void GEMglRasterPos2iv_setup(); +void GEMglRasterPos2s_setup(); +void GEMglRasterPos2sv_setup(); +void GEMglRasterPos3d_setup(); +void GEMglRasterPos3dv_setup(); +void GEMglRasterPos3f_setup(); +void GEMglRasterPos3fv_setup(); +void GEMglRasterPos3i_setup(); +void GEMglRasterPos3iv_setup(); +void GEMglRasterPos3s_setup(); +void GEMglRasterPos3sv_setup(); +void GEMglRasterPos4d_setup(); +void GEMglRasterPos4dv_setup(); +void GEMglRasterPos4f_setup(); +void GEMglRasterPos4fv_setup(); +void GEMglRasterPos4i_setup(); +void GEMglRasterPos4iv_setup(); +void GEMglRasterPos4s_setup(); +void GEMglRasterPos4sv_setup(); +void GEMglRectd_setup(); +void GEMglRectf_setup(); +void GEMglRecti_setup(); +void GEMglRects_setup(); +void GEMglRenderMode_setup(); +void GEMglReportError_setup(); +void GEMglRotated_setup(); +void GEMglRotatef_setup(); +void GEMglScaled_setup(); +void GEMglScalef_setup(); +void GEMglScissor_setup(); +void GEMglSelectBuffer_setup(); +void GEMglShadeModel_setup(); +void GEMglStencilFunc_setup(); +void GEMglStencilMask_setup(); +void GEMglStencilOp_setup(); +void GEMglTexCoord1d_setup(); +void GEMglTexCoord1dv_setup(); +void GEMglTexCoord1f_setup(); +void GEMglTexCoord1fv_setup(); +void GEMglTexCoord1i_setup(); +void GEMglTexCoord1iv_setup(); +void GEMglTexCoord1s_setup(); +void GEMglTexCoord1sv_setup(); +void GEMglTexCoord2d_setup(); +void GEMglTexCoord2dv_setup(); +void GEMglTexCoord2f_setup(); +void GEMglTexCoord2fv_setup(); +void GEMglTexCoord2i_setup(); +void GEMglTexCoord2iv_setup(); +void GEMglTexCoord2s_setup(); +void GEMglTexCoord2sv_setup(); +void GEMglTexCoord3d_setup(); +void GEMglTexCoord3dv_setup(); +void GEMglTexCoord3f_setup(); +void GEMglTexCoord3fv_setup(); +void GEMglTexCoord3i_setup(); +void GEMglTexCoord3iv_setup(); +void GEMglTexCoord3s_setup(); +void GEMglTexCoord3sv_setup(); +void GEMglTexCoord4d_setup(); +void GEMglTexCoord4dv_setup(); +void GEMglTexCoord4f_setup(); +void GEMglTexCoord4fv_setup(); +void GEMglTexCoord4i_setup(); +void GEMglTexCoord4iv_setup(); +void GEMglTexCoord4s_setup(); +void GEMglTexCoord4sv_setup(); +void GEMglTexEnvf_setup(); +void GEMglTexEnvi_setup(); +void GEMglTexGend_setup(); +void GEMglTexGenf_setup(); +void GEMglTexGenfv_setup(); +void GEMglTexGeni_setup(); +void GEMglTexImage2D_setup(); +void GEMglTexParameterf_setup(); +void GEMglTexParameteri_setup(); +void GEMglTexSubImage1D_setup(); +void GEMglTexSubImage2D_setup(); +void GEMglTranslated_setup(); +void GEMglTranslatef_setup(); +void GEMglUniform1f_setup(); +void GEMglUniform1fARB_setup(); +void GEMglUseProgramObjectARB_setup(); +void GEMglVertex2d_setup(); +void GEMglVertex2dv_setup(); +void GEMglVertex2f_setup(); +void GEMglVertex2fv_setup(); +void GEMglVertex2i_setup(); +void GEMglVertex2iv_setup(); +void GEMglVertex2s_setup(); +void GEMglVertex2sv_setup(); +void GEMglVertex3d_setup(); +void GEMglVertex3dv_setup(); +void GEMglVertex3f_setup(); +void GEMglVertex3fv_setup(); +void GEMglVertex3i_setup(); +void GEMglVertex3iv_setup(); +void GEMglVertex3s_setup(); +void GEMglVertex3sv_setup(); +void GEMglVertex4d_setup(); +void GEMglVertex4dv_setup(); +void GEMglVertex4f_setup(); +void GEMglVertex4fv_setup(); +void GEMglVertex4i_setup(); +void GEMglVertex4iv_setup(); +void GEMglVertex4s_setup(); +void GEMglVertex4sv_setup(); +void GEMglViewport_setup(); +void GEMgluLookAt_setup(); +void GEMgluPerspective_setup(); +void GLdefine_setup(); + // pd-extra objects functions declaration void bob_tilde_setup(); void bonk_tilde_setup(); @@ -1129,6 +1668,562 @@ void Setup::initialiseELSE() fm_tilde_setup(); } +void Setup::initialiseGem() +{ + Gem_setup(); + gemcubeframebuffer_setup(); + gemframebuffer_setup(); + gemhead_setup(); + gemkeyboard_setup(); + gemkeyname_setup(); + gemlist_setup(); + gemlist_info_setup(); + gemlist_matrix_setup(); + gemmanager_setup(); + gemmouse_setup(); + gemreceive_setup(); + gemwin_setup(); + modelfiler_setup(); + render_trigger_setup(); + GemSplash_setup(); + circle_setup(); + colorSquare_setup(); + cone_setup(); + cube_setup(); + cuboid_setup(); + curve_setup(); + curve3d_setup(); + cylinder_setup(); + disk_setup(); + gemvertexbuffer_setup(); + imageVert_setup(); + mesh_line_setup(); + mesh_square_setup(); + model_setup(); + multimodel_setup(); + newWave_setup(); + polygon_setup(); + pqtorusknots_setup(); + primTri_setup(); + rectangle_setup(); + ripple_setup(); + rubber_setup(); + scopeXYZ_setup(); + slideSquares_setup(); + sphere_setup(); + sphere3d_setup(); + square_setup(); + surface3d_setup(); + teapot_setup(); + /* + text2d_setup(); + text3d_setup(); + textextruded_setup(); + textoutline_setup(); */ + torus_setup(); + trapezoid_setup(); + triangle_setup(); + tube_setup(); + accumrotate_setup(); + alpha_setup(); + ambient_setup(); + ambientRGB_setup(); + camera_setup(); + color_setup(); + colorRGB_setup(); + depth_setup(); + diffuse_setup(); + diffuseRGB_setup(); + emission_setup(); + emissionRGB_setup(); + fragment_program_setup(); + glsl_fragment_setup(); + glsl_geometry_setup(); + glsl_program_setup(); + glsl_tesscontrol_setup(); + glsl_tesseval_setup(); + glsl_vertex_setup(); + linear_path_setup(); + ortho_setup(); + polygon_smooth_setup(); + rotate_setup(); + rotateXYZ_setup(); + scale_setup(); + scaleXYZ_setup(); + separator_setup(); + shearXY_setup(); + shearXZ_setup(); + shearYX_setup(); + shearYZ_setup(); + shearZX_setup(); + shearZY_setup(); + shininess_setup(); + specular_setup(); + specularRGB_setup(); + spline_path_setup(); + translate_setup(); + translateXYZ_setup(); + vertex_program_setup(); + light_setup(); + spot_light_setup(); + world_light_setup(); + + /* + gemmacwindow_setup(); + gemglfw2window_setup(); + gemglfw3window_setup(); + gemglutwindow_setup(); + gemglxwindow_setup(); + gemsdl2window_setup(); + gemsdlwindow_setup(); + gemw32window_setup(); */ + + + part_color_setup(); + part_damp_setup(); + part_draw_setup(); + part_follow_setup(); + part_gravity_setup(); + part_head_setup(); + part_info_setup(); + part_killold_setup(); + part_killslow_setup(); + part_orbitpoint_setup(); + part_render_setup(); + part_sink_setup(); + part_size_setup(); + part_source_setup(); + part_targetcolor_setup(); + part_targetsize_setup(); + part_velcone_setup(); + part_velocity_setup(); + part_velsphere_setup(); + part_vertex_setup(); + + pix_2grey_setup(); + pix_a_2grey_setup(); + pix_add_setup(); + pix_aging_setup(); + pix_alpha_setup(); + pix_background_setup(); + pix_backlight_setup(); + pix_biquad_setup(); + pix_bitmask_setup(); + pix_blob_setup(); + pix_blur_setup(); + pix_buf_setup(); + pix_buffer_setup(); + pix_buffer_read_setup(); + pix_buffer_write_setup(); + pix_chroma_key_setup(); + pix_clearblock_setup(); + pix_color_setup(); + pix_coloralpha_setup(); + pix_colorclassify_setup(); + pix_colormatrix_setup(); + pix_colorreduce_setup(); + pix_compare_setup(); + pix_composite_setup(); + pix_contrast_setup(); + pix_convert_setup(); + pix_convolve_setup(); + pix_coordinate_setup(); + pix_crop_setup(); + pix_cubemap_setup(); + pix_curve_setup(); + pix_data_setup(); + pix_deinterlace_setup(); + pix_delay_setup(); + pix_diff_setup(); + pix_dot_setup(); + pix_draw_setup(); + pix_dump_setup(); + pix_duotone_setup(); + pix_emboss_setup(); + pix_equal_setup(); + pix_film_setup(); + pix_flip_setup(); + pix_freeframe_setup(); + pix_frei0r_setup(); + pix_gain_setup(); + pix_grey_setup(); + pix_halftone_setup(); + pix_histo_setup(); + pix_hsv2rgb_setup(); + pix_image_setup(); + pix_imageInPlace_setup(); + pix_info_setup(); + pix_invert_setup(); + pix_kaleidoscope_setup(); + pix_levels_setup(); + pix_lumaoffset_setup(); + pix_mask_setup(); + pix_mean_color_setup(); + pix_metaimage_setup(); + pix_mix_setup(); + pix_motionblur_setup(); + pix_movement_setup(); + pix_movement2_setup(); + pix_movie_setup(); + pix_multiblob_setup(); + pix_multiimage_setup(); + pix_multiply_setup(); + pix_multitexture_setup(); + pix_noise_setup(); + pix_normalize_setup(); + pix_offset_setup(); + pix_pix2sig_setup(); + pix_posterize_setup(); + pix_puzzle_setup(); + pix_rds_setup(); + pix_record_setup(); + pix_rectangle_setup(); + pix_refraction_setup(); + pix_resize_setup(); + pix_rgb2hsv_setup(); + pix_rgba_setup(); + pix_roi_setup(); + pix_roll_setup(); + pix_rtx_setup(); + pix_scanline_setup(); + pix_set_setup(); + pix_share_read_setup(); + pix_share_write_setup(); + pix_sig2pix_setup(); + pix_snap_setup(); + pix_snap2tex_setup(); + pix_subtract_setup(); + pix_tIIR_setup(); + pix_tIIRf_setup(); + pix_takealpha_setup(); + pix_test_setup(); + pix_texture_setup(); + pix_threshold_setup(); + pix_threshold_bernsen_setup(); + pix_video_setup(); + pix_vpaint_setup(); + pix_write_setup(); + pix_writer_setup(); + pix_yuv_setup(); + pix_zoom_setup(); + vertex_add_setup(); + vertex_combine_setup(); + vertex_draw_setup(); + vertex_grid_setup(); + vertex_info_setup(); + //vertex_model_setup(); + vertex_mul_setup(); + vertex_offset_setup(); + vertex_quad_setup(); + vertex_scale_setup(); + vertex_set_setup(); + vertex_tabread_setup(); + GEMglAccum_setup(); + GEMglActiveTexture_setup(); + GEMglActiveTextureARB_setup(); + GEMglAlphaFunc_setup(); + GEMglAreTexturesResident_setup(); + GEMglArrayElement_setup(); + GEMglBegin_setup(); + GEMglBindProgramARB_setup(); + GEMglBindTexture_setup(); + GEMglBitmap_setup(); + GEMglBlendEquation_setup(); + GEMglBlendFunc_setup(); + GEMglCallList_setup(); + GEMglClear_setup(); + GEMglClearAccum_setup(); + GEMglClearColor_setup(); + GEMglClearDepth_setup(); + GEMglClearIndex_setup(); + GEMglClearStencil_setup(); + GEMglClipPlane_setup(); + GEMglColor3b_setup(); + GEMglColor3bv_setup(); + GEMglColor3d_setup(); + GEMglColor3dv_setup(); + GEMglColor3f_setup(); + GEMglColor3fv_setup(); + GEMglColor3i_setup(); + GEMglColor3iv_setup(); + GEMglColor3s_setup(); + GEMglColor3sv_setup(); + GEMglColor3ub_setup(); + GEMglColor3ubv_setup(); + GEMglColor3ui_setup(); + GEMglColor3uiv_setup(); + GEMglColor3us_setup(); + GEMglColor3usv_setup(); + GEMglColor4b_setup(); + GEMglColor4bv_setup(); + GEMglColor4d_setup(); + GEMglColor4dv_setup(); + GEMglColor4f_setup(); + GEMglColor4fv_setup(); + GEMglColor4i_setup(); + GEMglColor4iv_setup(); + GEMglColor4s_setup(); + GEMglColor4sv_setup(); + GEMglColor4ub_setup(); + GEMglColor4ubv_setup(); + GEMglColor4ui_setup(); + GEMglColor4uiv_setup(); + GEMglColor4us_setup(); + GEMglColor4usv_setup(); + GEMglColorMask_setup(); + GEMglColorMaterial_setup(); + GEMglCopyPixels_setup(); + GEMglCopyTexImage1D_setup(); + GEMglCopyTexImage2D_setup(); + GEMglCopyTexSubImage1D_setup(); + GEMglCopyTexSubImage2D_setup(); + GEMglCullFace_setup(); + GEMglDeleteTextures_setup(); + GEMglDepthFunc_setup(); + GEMglDepthMask_setup(); + GEMglDepthRange_setup(); + GEMglDisable_setup(); + GEMglDisableClientState_setup(); + GEMglDrawArrays_setup(); + GEMglDrawBuffer_setup(); + GEMglDrawElements_setup(); + GEMglEdgeFlag_setup(); + GEMglEnable_setup(); + GEMglEnableClientState_setup(); + GEMglEnd_setup(); + GEMglEndList_setup(); + GEMglEvalCoord1d_setup(); + GEMglEvalCoord1dv_setup(); + GEMglEvalCoord1f_setup(); + GEMglEvalCoord1fv_setup(); + GEMglEvalCoord2d_setup(); + GEMglEvalCoord2dv_setup(); + GEMglEvalCoord2f_setup(); + GEMglEvalCoord2fv_setup(); + GEMglEvalMesh1_setup(); + GEMglEvalMesh2_setup(); + GEMglEvalPoint1_setup(); + GEMglEvalPoint2_setup(); + GEMglFeedbackBuffer_setup(); + GEMglFinish_setup(); + GEMglFlush_setup(); + GEMglFogf_setup(); + GEMglFogfv_setup(); + GEMglFogi_setup(); + GEMglFogiv_setup(); + GEMglFrontFace_setup(); + GEMglFrustum_setup(); + GEMglGenLists_setup(); + GEMglGenProgramsARB_setup(); + GEMglGenTextures_setup(); + GEMglGenerateMipmap_setup(); + GEMglGetError_setup(); + GEMglGetFloatv_setup(); + GEMglGetIntegerv_setup(); + GEMglGetMapdv_setup(); + GEMglGetMapfv_setup(); + GEMglGetMapiv_setup(); + GEMglGetPointerv_setup(); + GEMglGetString_setup(); + GEMglHint_setup(); + GEMglIndexMask_setup(); + GEMglIndexd_setup(); + GEMglIndexdv_setup(); + GEMglIndexf_setup(); + GEMglIndexfv_setup(); + GEMglIndexi_setup(); + GEMglIndexiv_setup(); + GEMglIndexs_setup(); + GEMglIndexsv_setup(); + GEMglIndexub_setup(); + GEMglIndexubv_setup(); + GEMglInitNames_setup(); + GEMglIsEnabled_setup(); + GEMglIsList_setup(); + GEMglIsTexture_setup(); + GEMglLightModelf_setup(); + GEMglLightModeli_setup(); + GEMglLightf_setup(); + GEMglLighti_setup(); + GEMglLineStipple_setup(); + GEMglLineWidth_setup(); + GEMglLoadIdentity_setup(); + GEMglLoadMatrixd_setup(); + GEMglLoadMatrixf_setup(); + GEMglLoadName_setup(); + GEMglLoadTransposeMatrixd_setup(); + GEMglLoadTransposeMatrixf_setup(); + GEMglLogicOp_setup(); + GEMglMap1d_setup(); + GEMglMap1f_setup(); + GEMglMap2d_setup(); + GEMglMap2f_setup(); + GEMglMapGrid1d_setup(); + GEMglMapGrid1f_setup(); + GEMglMapGrid2d_setup(); + GEMglMapGrid2f_setup(); + GEMglMaterialf_setup(); + GEMglMaterialfv_setup(); + GEMglMateriali_setup(); + GEMglMatrixMode_setup(); + GEMglMultMatrixd_setup(); + GEMglMultMatrixf_setup(); + GEMglMultTransposeMatrixd_setup(); + GEMglMultTransposeMatrixf_setup(); + GEMglMultiTexCoord2f_setup(); + GEMglMultiTexCoord2fARB_setup(); + GEMglNewList_setup(); + GEMglNormal3b_setup(); + GEMglNormal3bv_setup(); + GEMglNormal3d_setup(); + GEMglNormal3dv_setup(); + GEMglNormal3f_setup(); + GEMglNormal3fv_setup(); + GEMglNormal3i_setup(); + GEMglNormal3iv_setup(); + GEMglNormal3s_setup(); + GEMglNormal3sv_setup(); + GEMglOrtho_setup(); + GEMglPassThrough_setup(); + GEMglPixelStoref_setup(); + GEMglPixelStorei_setup(); + GEMglPixelTransferf_setup(); + GEMglPixelTransferi_setup(); + GEMglPixelZoom_setup(); + GEMglPointSize_setup(); + GEMglPolygonMode_setup(); + GEMglPolygonOffset_setup(); + GEMglPopAttrib_setup(); + GEMglPopClientAttrib_setup(); + GEMglPopMatrix_setup(); + GEMglPopName_setup(); + GEMglPrioritizeTextures_setup(); + GEMglProgramEnvParameter4dARB_setup(); + GEMglProgramEnvParameter4fvARB_setup(); + GEMglProgramLocalParameter4fvARB_setup(); + GEMglProgramStringARB_setup(); + GEMglPushAttrib_setup(); + GEMglPushClientAttrib_setup(); + GEMglPushMatrix_setup(); + GEMglPushName_setup(); + GEMglRasterPos2d_setup(); + GEMglRasterPos2dv_setup(); + GEMglRasterPos2f_setup(); + GEMglRasterPos2fv_setup(); + GEMglRasterPos2i_setup(); + GEMglRasterPos2iv_setup(); + GEMglRasterPos2s_setup(); + GEMglRasterPos2sv_setup(); + GEMglRasterPos3d_setup(); + GEMglRasterPos3dv_setup(); + GEMglRasterPos3f_setup(); + GEMglRasterPos3fv_setup(); + GEMglRasterPos3i_setup(); + GEMglRasterPos3iv_setup(); + GEMglRasterPos3s_setup(); + GEMglRasterPos3sv_setup(); + GEMglRasterPos4d_setup(); + GEMglRasterPos4dv_setup(); + GEMglRasterPos4f_setup(); + GEMglRasterPos4fv_setup(); + GEMglRasterPos4i_setup(); + GEMglRasterPos4iv_setup(); + GEMglRasterPos4s_setup(); + GEMglRasterPos4sv_setup(); + GEMglRectd_setup(); + GEMglRectf_setup(); + GEMglRecti_setup(); + GEMglRects_setup(); + GEMglRenderMode_setup(); + GEMglReportError_setup(); + GEMglRotated_setup(); + GEMglRotatef_setup(); + GEMglScaled_setup(); + GEMglScalef_setup(); + GEMglScissor_setup(); + GEMglSelectBuffer_setup(); + GEMglShadeModel_setup(); + GEMglStencilFunc_setup(); + GEMglStencilMask_setup(); + GEMglStencilOp_setup(); + GEMglTexCoord1d_setup(); + GEMglTexCoord1dv_setup(); + GEMglTexCoord1f_setup(); + GEMglTexCoord1fv_setup(); + GEMglTexCoord1i_setup(); + GEMglTexCoord1iv_setup(); + GEMglTexCoord1s_setup(); + GEMglTexCoord1sv_setup(); + GEMglTexCoord2d_setup(); + GEMglTexCoord2dv_setup(); + GEMglTexCoord2f_setup(); + GEMglTexCoord2fv_setup(); + GEMglTexCoord2i_setup(); + GEMglTexCoord2iv_setup(); + GEMglTexCoord2s_setup(); + GEMglTexCoord2sv_setup(); + GEMglTexCoord3d_setup(); + GEMglTexCoord3dv_setup(); + GEMglTexCoord3f_setup(); + GEMglTexCoord3fv_setup(); + GEMglTexCoord3i_setup(); + GEMglTexCoord3iv_setup(); + GEMglTexCoord3s_setup(); + GEMglTexCoord3sv_setup(); + GEMglTexCoord4d_setup(); + GEMglTexCoord4dv_setup(); + GEMglTexCoord4f_setup(); + GEMglTexCoord4fv_setup(); + GEMglTexCoord4i_setup(); + GEMglTexCoord4iv_setup(); + GEMglTexCoord4s_setup(); + GEMglTexCoord4sv_setup(); + GEMglTexEnvf_setup(); + GEMglTexEnvi_setup(); + GEMglTexGend_setup(); + GEMglTexGenf_setup(); + GEMglTexGenfv_setup(); + GEMglTexGeni_setup(); + GEMglTexImage2D_setup(); + GEMglTexParameterf_setup(); + GEMglTexParameteri_setup(); + GEMglTexSubImage1D_setup(); + GEMglTexSubImage2D_setup(); + GEMglTranslated_setup(); + GEMglTranslatef_setup(); + GEMglUniform1f_setup(); + GEMglUniform1fARB_setup(); + GEMglUseProgramObjectARB_setup(); + GEMglVertex2d_setup(); + GEMglVertex2dv_setup(); + GEMglVertex2f_setup(); + GEMglVertex2fv_setup(); + GEMglVertex2i_setup(); + GEMglVertex2iv_setup(); + GEMglVertex2s_setup(); + GEMglVertex2sv_setup(); + GEMglVertex3d_setup(); + GEMglVertex3dv_setup(); + GEMglVertex3f_setup(); + GEMglVertex3fv_setup(); + GEMglVertex3i_setup(); + GEMglVertex3iv_setup(); + GEMglVertex3s_setup(); + GEMglVertex3sv_setup(); + GEMglVertex4d_setup(); + GEMglVertex4dv_setup(); + GEMglVertex4f_setup(); + GEMglVertex4fv_setup(); + GEMglVertex4i_setup(); + GEMglVertex4iv_setup(); + GEMglVertex4s_setup(); + GEMglVertex4sv_setup(); + GEMglViewport_setup(); + GEMgluLookAt_setup(); + GEMgluPerspective_setup(); + GLdefine_setup(); +} + void Setup::initialiseCyclone() { cyclone_setup(); diff --git a/Source/Pd/Setup.h b/Source/Pd/Setup.h index ef8f5b15bc..c7395628ea 100644 --- a/Source/Pd/Setup.h +++ b/Source/Pd/Setup.h @@ -35,6 +35,7 @@ struct Setup { static void initialisePdLua(char const* datadir, char* vers, int vers_len, void(*register_class_callback)(const char*)); static void initialiseELSE(); static void initialiseCyclone(); + static void initialiseGem(); static void* createMIDIHook(void* ptr, t_plugdata_noteonhook hook_noteon, From 54b82f10e8e4028e23a99d4aeafe4a5c0afbe2e6 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 01:38:49 +0100 Subject: [PATCH 0002/1030] Updated submodule --- .gitmodules | 3 +++ Libraries/Gem | 1 + 2 files changed, 4 insertions(+) create mode 160000 Libraries/Gem diff --git a/.gitmodules b/.gitmodules index fab54093e7..d2ed65c23b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -45,3 +45,6 @@ [submodule "Gem"] path = Gem url = https://github.com/plugdata-team/plugdata-gem.git +[submodule "Libraries/Gem"] + path = Libraries/Gem + url = https://github.com/plugdata-team/plugdata-gem.git diff --git a/Libraries/Gem b/Libraries/Gem new file mode 160000 index 0000000000..ae972171cf --- /dev/null +++ b/Libraries/Gem @@ -0,0 +1 @@ +Subproject commit ae972171cfd5c38c5319b89e8810eba0d997922f From 3a82437a25581056018dfef0c97eca86ceb7f2f6 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 01:41:41 +0100 Subject: [PATCH 0003/1030] Removed duplicate submodule --- .gitmodules | 3 --- Gem | 1 - 2 files changed, 4 deletions(-) delete mode 160000 Gem diff --git a/.gitmodules b/.gitmodules index d2ed65c23b..43a4ef196e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -42,9 +42,6 @@ [submodule "Libraries/melatonin_blur"] path = Libraries/melatonin_blur url = https://github.com/sudara/melatonin_blur.git -[submodule "Gem"] - path = Gem - url = https://github.com/plugdata-team/plugdata-gem.git [submodule "Libraries/Gem"] path = Libraries/Gem url = https://github.com/plugdata-team/plugdata-gem.git diff --git a/Gem b/Gem deleted file mode 160000 index ae972171cf..0000000000 --- a/Gem +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ae972171cfd5c38c5319b89e8810eba0d997922f From 32cc46897c9260424c998a6b124e209289303c77 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 01:47:54 +0100 Subject: [PATCH 0004/1030] Cleaned up, updated submodule --- Libraries/CMakeLists.txt | 4 ++-- Libraries/Gem | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index f9d5411e75..5aaf147e1b 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -385,8 +385,8 @@ source_group(ofelia FILES ${OFELIA_SOURCES}) # ------------------------------------------------------------------------------# # TARGETS # ------------------------------------------------------------------------------# -add_library(externals STATIC ${ELSE_SOURCES} ${CYCLONE_SOURCES} ${GEM_SOURCES} ${PDLUA_SOURCES} ${AUBIO_SOURCES} ${OFELIA_SOURCES}) -add_library(externals-multi STATIC ${ELSE_SOURCES} ${CYCLONE_SOURCES} ${GEM_SOURCES} ${PDLUA_SOURCES} ${AUBIO_SOURCES} ${OFELIA_SOURCES}) +add_library(externals STATIC ${ELSE_SOURCES} ${CYCLONE_SOURCES} ${PDLUA_SOURCES} ${AUBIO_SOURCES} ${OFELIA_SOURCES}) +add_library(externals-multi STATIC ${ELSE_SOURCES} ${CYCLONE_SOURCES} ${PDLUA_SOURCES} ${AUBIO_SOURCES} ${OFELIA_SOURCES}) # ag: pdlua needs PLUGDATA symbol at compile time for PlugData integration. target_compile_definitions(externals PRIVATE ${LIBPD_COMPILE_DEFINITIONS} PLUGDATA=1) diff --git a/Libraries/Gem b/Libraries/Gem index ae972171cf..a7b7f6ff78 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit ae972171cfd5c38c5319b89e8810eba0d997922f +Subproject commit a7b7f6ff78cb1c97712e65dfc867d4af19c4c27d From 014c604c3c3667eb08f533129959feac85f09d27 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 01:54:58 +0100 Subject: [PATCH 0005/1030] Small fixes --- CMakeLists.txt | 4 ++-- Libraries/Gem | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a0ce6b1ac..1b2270cf1b 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -544,8 +544,8 @@ set_property(TARGET Tests PROPERTY VISIBILITY_INLINES_HIDDEN ON) endif() if(APPLE) -# 2x speedup for debug builds in xcode -SET_TARGET_PROPERTIES(myTarget PROPERTIES XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH[variant=Debug] "YES") +# 2x speedup for standalone debug builds in xcode +SET_TARGET_PROPERTIES(plugdata_standalone PROPERTIES XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH[variant=Debug] "YES") endif() if(MSVC) diff --git a/Libraries/Gem b/Libraries/Gem index a7b7f6ff78..3779e3af86 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit a7b7f6ff78cb1c97712e65dfc867d4af19c4c27d +Subproject commit 3779e3af86b6926899bec5bce0715719a4e5b9db From d98badd0bc0aef40b9502ee997bd305d52157bbd Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 01:58:55 +0100 Subject: [PATCH 0006/1030] Updated submodule --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 3779e3af86..6520cda048 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 3779e3af86b6926899bec5bce0715719a4e5b9db +Subproject commit 6520cda0485b31abe4edfb39281e27755cedd933 From f9276acffbd68577c1ab00dee6f574f5bc2d9788 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 02:00:44 +0100 Subject: [PATCH 0007/1030] Updated submodule --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 6520cda048..fce32d6d7d 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 6520cda0485b31abe4edfb39281e27755cedd933 +Subproject commit fce32d6d7d042aff4d071de83d21729eab8dd8de From 36ef84db58f6a34852ba4f34e50c249c25a433d9 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 02:21:33 +0100 Subject: [PATCH 0008/1030] Linux fixes --- Libraries/CMakeLists.txt | 8 ++++---- Libraries/Gem | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 5aaf147e1b..2c297ba4fb 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -431,14 +431,14 @@ if("${CMAKE_SYSTEM}" MATCHES "Linux") add_library(pd INTERFACE) add_library(pd-multi INTERFACE) find_library(MATH_LIB m) - target_link_libraries(externals ${externals_libs}) - target_link_libraries(externals-multi ${externals_libs}) + target_link_libraries(externals ${externals_libs} Gem) + target_link_libraries(externals-multi ${externals_libs} Gem) elseif(MSVC) add_library(pd SHARED $ $) add_library(pd-multi STATIC $ $) - target_link_libraries(pd PUBLIC pthreadVC3 ws2_32 ${externals_libs}) - target_link_libraries(pd-multi PUBLIC pthreadVC3 ws2_32 ${externals_libs}) + target_link_libraries(pd PUBLIC pthreadVC3 ws2_32 ${externals_libs} Gem) + target_link_libraries(pd-multi PUBLIC pthreadVC3 ws2_32 ${externals_libs} Gem) add_custom_command(TARGET pd POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy diff --git a/Libraries/Gem b/Libraries/Gem index fce32d6d7d..558c73ca32 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit fce32d6d7d042aff4d071de83d21729eab8dd8de +Subproject commit 558c73ca320e98f1452a85cfa901c59715a46005 From d192706d16775606a708186a28f63a4fd0c46fe7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 02:25:53 +0100 Subject: [PATCH 0009/1030] Suppress unncessary warning --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 558c73ca32..4006fcad0f 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 558c73ca320e98f1452a85cfa901c59715a46005 +Subproject commit 4006fcad0f6a278847fc2792aa5ee8053da335fc From dc0264441619d1fb4a44cc7085bac8b1b58abd98 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 02:49:50 +0100 Subject: [PATCH 0010/1030] Fixed multi-instance compilation --- Libraries/CMakeLists.txt | 6 +++--- Libraries/Gem | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 2c297ba4fb..16f508c6cf 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -432,13 +432,13 @@ if("${CMAKE_SYSTEM}" MATCHES "Linux") add_library(pd-multi INTERFACE) find_library(MATH_LIB m) target_link_libraries(externals ${externals_libs} Gem) - target_link_libraries(externals-multi ${externals_libs} Gem) + target_link_libraries(externals-multi ${externals_libs} Gem-multi) elseif(MSVC) add_library(pd SHARED $ $) add_library(pd-multi STATIC $ $) target_link_libraries(pd PUBLIC pthreadVC3 ws2_32 ${externals_libs} Gem) - target_link_libraries(pd-multi PUBLIC pthreadVC3 ws2_32 ${externals_libs} Gem) + target_link_libraries(pd-multi PUBLIC pthreadVC3 ws2_32 ${externals_libs} Gem-multi) add_custom_command(TARGET pd POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy @@ -446,7 +446,7 @@ elseif(MSVC) ${CMAKE_SOURCE_DIR}/Plugins/Standalone/pd.dll) elseif(APPLE) target_link_libraries(externals ${externals_libs} Gem) - target_link_libraries(externals-multi ${externals_libs} Gem) + target_link_libraries(externals-multi ${externals_libs} Gem-multi) endif() # PTHREAD diff --git a/Libraries/Gem b/Libraries/Gem index 4006fcad0f..0138594d06 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 4006fcad0f6a278847fc2792aa5ee8053da335fc +Subproject commit 0138594d06f4e0f75f70345acb771ac8b98ab0ea From 2d46fa33a3c78080543570060203a184837d960d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 02:50:12 +0100 Subject: [PATCH 0011/1030] Copy gem abstractions and helpfiles --- Resources/Scripts/package_resources.py | 22 +++++++++++++++------- Source/Utility/Config.h | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index 50dc7f5829..f36ad0e5dc 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -46,10 +46,10 @@ def split(a, n): def splitFile(file, num_files): with open(file, 'rb') as fd: data_in = split(fd.read(), num_files) - count = 0; + count = 0 for entry in data_in: - name = os.path.splitext(file)[0]; - extension = os.path.splitext(file)[1]; + name = os.path.splitext(file)[0] + extension = os.path.splitext(file)[1] filename = name + "_" + str(count) + extension with open(filename, "wb") as fd: fd.write(entry) @@ -67,6 +67,7 @@ def splitFile(file, num_files): makeDir("Abstractions") makeDir("Abstractions/else") makeDir("Abstractions/cyclone") +makeDir("Abstractions/Gem") copyDir("../../Libraries/pure-data/doc", "./Documentation") globCopy("../../Libraries/pure-data/extra/*.pd", "./Abstractions") @@ -101,13 +102,13 @@ def splitFile(file, num_files): makeDir("Extra") makeDir("Extra/GS") -copyDir("../../Libraries/pd-else/Documentation/extra_files", "Extra/else"); -copyFile("../../Libraries/pd-else/Documentation/README.pdf", "Extra/else"); -copyDir("../../Libraries/pd-else/Code_source/Compiled/audio/sfont~/sf", "Extra/else/sf"); +copyDir("../../Libraries/pd-else/Documentation/extra_files", "Extra/else") +copyFile("../../Libraries/pd-else/Documentation/README.pdf", "Extra/else") +copyDir("../../Libraries/pd-else/Code_source/Compiled/audio/sfont~/sf", "Extra/else/sf") copyDir("../Patches/Presets", "./Extra/Presets") copyDir("../Patches/Palettes", "./Extra/palette") copyDir("../Documentation/Manual", "./Extra/Manual") -globCopy("../../Libraries/pure-data/doc/sound/*", "Extra/else"); +globCopy("../../Libraries/pure-data/doc/sound/*", "Extra/else") # pd-lua makeDir("Extra/pdlua") @@ -123,6 +124,13 @@ def splitFile(file, num_files): for src in ["pdlua"]: copyDir(pdlua_srcdir+src, "./Documentation/13.pdlua/"+src) +copyDir("../../Libraries/Gem/help", "Documentation/14.gem") +copyDir("../../Libraries/Gem/examples", "Documentation/14.gem/examples") +copyDir("../../Libraries/Gem/doc", "Documentation/14.gem/examples/Documentation") +globCopy("../../Libraries/Gem/abstractions/*.pd", "Abstractions/Gem/") +globMove("Abstractions/Gem/*-help.pd", "Documentation/14.gem/") + + changeWorkingDir("./..") makeArchive("Filesystem", "./", "./plugdata_version") diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index 3b8d39f572..89c8036c81 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -38,7 +38,7 @@ struct ProjectInfo { static inline File const appDataDir = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory).getChildFile("plugdata"); - static inline String const versionSuffix = "-3"; + static inline String const versionSuffix = "-gem"; static inline File const versionDataDir = appDataDir.getChildFile("Versions").getChildFile(ProjectInfo::versionString + versionSuffix); }; From ac8422499ca65ae59f40756299338709019478d3 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 03:29:50 +0100 Subject: [PATCH 0012/1030] Improved multi-instance support --- .github/workflows/cmake.yml | 2 +- Libraries/Gem | 2 +- Source/Objects/Gem.h | 60 ++++++++++++++++++------------------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 626c84153f..ed41110362 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -219,7 +219,7 @@ jobs: - name: Install Dependencies (apt) if: ${{ matrix.pacman == 'apt' }} - run: apt update && DEBIAN_FRONTEND=noninteractive TZ="Europe/Amsterdam" apt install -y cmake git wget bzip2 build-essential libasound2-dev libjack-jackd2-dev curl libcurl4-openssl-dev libfreetype6-dev libx11-dev libxi-dev libxcomposite-dev libxcursor-dev libxcursor-dev libxext-dev libxrandr-dev libxinerama-dev ccache python3 python3-pip + run: apt update && DEBIAN_FRONTEND=noninteractive TZ="Europe/Amsterdam" apt install -y cmake git wget bzip2 build-essential libasound2-dev libjack-jackd2-dev curl libcurl4-openssl-dev libfreetype6-dev libx11-dev libxi-dev libxcomposite-dev libxcursor-dev libxcursor-dev libxext-dev libxrandr-dev libxinerama-dev ccache python3 python3-pip libfreeglut-dev - name: Install Dependencies (zypper) if: ${{ matrix.pacman == 'zypper' }} diff --git a/Libraries/Gem b/Libraries/Gem index 0138594d06..d19ecde7d9 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 0138594d06f4e0f75f70345acb771ac8b98ab0ea +Subproject commit d19ecde7d981154b948ff6c21abc0da53bfc3f43 diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index c3f5f26eaa..399f4efa8d 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -31,6 +31,8 @@ class GemJUCEWindow final : public OpenGLAppComponent auto pixelFormat = OpenGLPixelFormat(8, 8, 16, 8); pixelFormat.multisamplingLevel = 2; openGLContext.setPixelFormat(pixelFormat); + + instance = libpd_this_instance(); } ~GemJUCEWindow() override @@ -50,6 +52,7 @@ class GemJUCEWindow final : public OpenGLAppComponent void render() override { + libpd_set_instance(instance); OpenGLHelpers::clear(Colours::black); sys_lock(); @@ -60,10 +63,7 @@ class GemJUCEWindow final : public OpenGLAppComponent void resized() override { - const ScopedLock lock (mutex); - bounds = getLocalBounds(); - - triggerResizeEvent(bounds.getWidth(), bounds.getHeight()); + triggerResizeEvent(getWidth(), getHeight()); } void paint (Graphics&) override @@ -97,13 +97,10 @@ class GemJUCEWindow final : public OpenGLAppComponent bool keyPressed(KeyPress const& key) override { //triggerKeyboardEvent(key.getTextDescription().toRawUTF8(), key.getKeyCode(), 1); + return false; } -private: - - Rectangle bounds; - CriticalSection mutex; - + t_pdinstance* instance; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GemJUCEWindow) }; @@ -113,25 +110,24 @@ bool initGemWin(void) return true; } -std::unique_ptr gemJUCEWindow; +std::map> gemJUCEWindow; int createGemWindow(WindowInfo& info, WindowHints& hints) { - if(info.window) + if(auto* window = info.getWindow()) { - info.window->removeFromDesktop(); - delete info.window; + window->removeFromDesktop(); + gemJUCEWindow[window->instance].reset(nullptr); } auto* window = new GemJUCEWindow(); - gemJUCEWindow.reset(window); - info.window = window; + gemJUCEWindow[window->instance].reset(window); + info.window[window->instance] = window; - info.window->addToDesktop(ComponentPeer::windowHasTitleBar | ComponentPeer::windowIsResizable | ComponentPeer::windowHasDropShadow); + window->addToDesktop(ComponentPeer::windowHasTitleBar | ComponentPeer::windowIsResizable | ComponentPeer::windowHasDropShadow); window->setVisible(true); window->openGLContext.makeActive(); - info.context = &window->openGLContext; - //info.have_constContext = 1; + info.context[window->instance] = &window->openGLContext; hints.real_w = window->getWidth(); hints.real_h = window->getHeight(); @@ -139,27 +135,28 @@ int createGemWindow(WindowInfo& info, WindowHints& hints) return 1; } void destroyGemWindow(WindowInfo& info) { - if(info.window) { - info.window->removeFromDesktop(); - delete info.window; + if(auto* window = info.getWindow()) { + window->removeFromDesktop(); + gemJUCEWindow[window->instance].reset(nullptr); } } void initWin_sharedContext(WindowInfo& info, WindowHints& hints) { - //info.have_constContext = 1; - if(info.window) info.window->openGLContext.makeActive(); + if(auto* window = info.getWindow()) { + window->openGLContext.makeActive(); + } } // Rendering void gemWinSwapBuffers(WindowInfo& info) { - if (info.context) { - info.context->makeActive(); - info.context->swapBuffers(); + if (auto* context = info.getContext()) { + context->makeActive(); + context->swapBuffers(); } } void gemWinMakeCurrent(WindowInfo& info) { - if (info.context) { - info.context->makeActive(); + if (auto* context = info.getContext()) { + context->makeActive(); } } @@ -170,12 +167,15 @@ void dispatchGemWindowMessages(WindowInfo& info) { // Window behaviour int cursorGemWindow(WindowInfo& info, int state) { - if(info.window) info.window->setMouseCursor(state ? MouseCursor::NormalCursor : MouseCursor::NoCursor); + if(auto* window = info.getWindow()) { + window->setMouseCursor(state ? MouseCursor::NormalCursor : MouseCursor::NoCursor); + } + return state; } int topmostGemWindow(WindowInfo& info, int state) { - if(info.window && state) info.window->toFront(true); + if(info.getWindow() && state) info.getWindow()->toFront(true); return state; } From d2063507b06da0dddc61bca7b8bb1fa0c4d472f3 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 03:37:17 +0100 Subject: [PATCH 0013/1030] Fix ubuntu and debian github action builds --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index ed41110362..0b78b99090 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -219,7 +219,7 @@ jobs: - name: Install Dependencies (apt) if: ${{ matrix.pacman == 'apt' }} - run: apt update && DEBIAN_FRONTEND=noninteractive TZ="Europe/Amsterdam" apt install -y cmake git wget bzip2 build-essential libasound2-dev libjack-jackd2-dev curl libcurl4-openssl-dev libfreetype6-dev libx11-dev libxi-dev libxcomposite-dev libxcursor-dev libxcursor-dev libxext-dev libxrandr-dev libxinerama-dev ccache python3 python3-pip libfreeglut-dev + run: apt update && DEBIAN_FRONTEND=noninteractive TZ="Europe/Amsterdam" apt install -y cmake git wget bzip2 build-essential libasound2-dev libjack-jackd2-dev curl libcurl4-openssl-dev libfreetype6-dev libx11-dev libxi-dev libxcomposite-dev libxcursor-dev libxcursor-dev libxext-dev libxrandr-dev libxinerama-dev ccache python3 python3-pip freeglut3-dev - name: Install Dependencies (zypper) if: ${{ matrix.pacman == 'zypper' }} From 87cecf9c9da972b4983205bc767721cfcbea2e6a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 04:04:11 +0100 Subject: [PATCH 0014/1030] JUCE fix, Linux fix --- Libraries/JUCE | 2 +- Source/Objects/Gem.h | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index 886a8e3ee8..3f0642c35b 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit 886a8e3ee88cd7b9f78bb60a33f504202ce9558b +Subproject commit 3f0642c35be6240b2f14ebbbe0f972804fe34210 diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 399f4efa8d..33100337a3 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -126,11 +126,17 @@ int createGemWindow(WindowInfo& info, WindowHints& hints) window->addToDesktop(ComponentPeer::windowHasTitleBar | ComponentPeer::windowIsResizable | ComponentPeer::windowHasDropShadow); window->setVisible(true); + + window->openGLContext.executeOnGLThread([](OpenGLContext& context){ + context.makeActive(); + }, true); + window->openGLContext.makeActive(); info.context[window->instance] = &window->openGLContext; hints.real_w = window->getWidth(); hints.real_h = window->getHeight(); + return 1; } From 557f3b31b3d1d13ab7963bb3c05b02f8da4c720c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 04:08:43 +0100 Subject: [PATCH 0015/1030] macOS compilation fix --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index d19ecde7d9..fc5649d851 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit d19ecde7d981154b948ff6c21abc0da53bfc3f43 +Subproject commit fc5649d8511caa07244cdb44babde8511ef97c50 From aecf0a039284b6d0fc5ffe5eff3cc74664e03e84 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 04:14:03 +0100 Subject: [PATCH 0016/1030] Small fixes --- Libraries/Gem | 2 +- Source/Objects/Gem.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index fc5649d851..098772429e 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit fc5649d8511caa07244cdb44babde8511ef97c50 +Subproject commit 098772429e3e5ff646ef8c5a8080c42370082479 diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 33100337a3..f9c96c16a2 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -25,7 +25,7 @@ class GemJUCEWindow final : public OpenGLAppComponent GemJUCEWindow() { setSize (800, 600); - //openGLContext.setSwapInterval(0); + openGLContext.setSwapInterval(1); openGLContext.setMultisamplingEnabled(true); auto pixelFormat = OpenGLPixelFormat(8, 8, 16, 8); From aafd897e7641154cd07c5b2bca240eb2818df684 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 05:06:31 +0100 Subject: [PATCH 0017/1030] Make Gem work more similar to how it works in pd-vanilla --- Libraries/Gem | 2 +- Libraries/JUCE | 2 +- Source/Objects/Gem.h | 37 ++++++++++--------------------------- 3 files changed, 12 insertions(+), 29 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index 098772429e..0c74b89c20 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 098772429e3e5ff646ef8c5a8080c42370082479 +Subproject commit 0c74b89c2084b71deeb6d437f1508287fca43c07 diff --git a/Libraries/JUCE b/Libraries/JUCE index 3f0642c35b..1bba9e5d20 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit 3f0642c35be6240b2f14ebbbe0f972804fe34210 +Subproject commit 1bba9e5d20ec06d441e534060662e7017c44a9b1 diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index f9c96c16a2..615f8a6e00 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -18,49 +18,35 @@ void triggerWheelEvent(int axis, int value); void triggerKeyboardEvent(const char *string, int value, int state); void triggerResizeEvent(int xSize, int ySize); -class GemJUCEWindow final : public OpenGLAppComponent +class GemJUCEWindow final : public Component { public: //============================================================================== GemJUCEWindow() { setSize (800, 600); - openGLContext.setSwapInterval(1); + + setOpaque (true); + //openGLContext.setRenderer (this); + openGLContext.setSwapInterval(0); openGLContext.setMultisamplingEnabled(true); auto pixelFormat = OpenGLPixelFormat(8, 8, 16, 8); pixelFormat.multisamplingLevel = 2; openGLContext.setPixelFormat(pixelFormat); + openGLContext.attachTo (*this); + //openGLContext.setContinuousRepainting (true); + instance = libpd_this_instance(); } ~GemJUCEWindow() override { - shutdownOpenGL(); - } - - void initialise() override - { - openGLContext.makeActive(); - initGemWindow(); } - void shutdown() override - { - } + //dequeueEvents(); -> restore changes - void render() override - { - libpd_set_instance(instance); - OpenGLHelpers::clear(Colours::black); - - sys_lock(); - dequeueEvents(); - performGemRender(); - sys_unlock(); - } - void resized() override { triggerResizeEvent(getWidth(), getHeight()); @@ -100,6 +86,7 @@ class GemJUCEWindow final : public OpenGLAppComponent return false; } + OpenGLContext openGLContext; t_pdinstance* instance; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GemJUCEWindow) }; @@ -127,10 +114,6 @@ int createGemWindow(WindowInfo& info, WindowHints& hints) window->addToDesktop(ComponentPeer::windowHasTitleBar | ComponentPeer::windowIsResizable | ComponentPeer::windowHasDropShadow); window->setVisible(true); - window->openGLContext.executeOnGLThread([](OpenGLContext& context){ - context.makeActive(); - }, true); - window->openGLContext.makeActive(); info.context[window->instance] = &window->openGLContext; From 1c16f315a684df0c45d5c1d678c5fb79685c51cc Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 05:35:42 +0100 Subject: [PATCH 0018/1030] Small fixes --- Libraries/JUCE | 2 +- Source/Objects/Gem.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index 1bba9e5d20..3fa91012aa 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit 1bba9e5d20ec06d441e534060662e7017c44a9b1 +Subproject commit 3fa91012aa5c1c65933ba8fa90984c0c5df293e0 diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 615f8a6e00..247ba40db4 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -28,7 +28,7 @@ class GemJUCEWindow final : public Component setOpaque (true); //openGLContext.setRenderer (this); - openGLContext.setSwapInterval(0); + openGLContext.setSwapInterval(1); openGLContext.setMultisamplingEnabled(true); auto pixelFormat = OpenGLPixelFormat(8, 8, 16, 8); @@ -37,6 +37,8 @@ class GemJUCEWindow final : public Component openGLContext.attachTo (*this); //openGLContext.setContinuousRepainting (true); + + instance = libpd_this_instance(); } @@ -45,8 +47,6 @@ class GemJUCEWindow final : public Component { } - //dequeueEvents(); -> restore changes - void resized() override { triggerResizeEvent(getWidth(), getHeight()); From 202c81ab320e1785dad9179cad8ac084dd8ff7d3 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 14:44:55 +0100 Subject: [PATCH 0019/1030] Fixes for Linux --- CMakeLists.txt | 3 +-- Source/Objects/Gem.h | 36 ++++++++++++++++++++++-------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b2270cf1b..6d0ed5737a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,8 +226,7 @@ target_link_libraries(juce juce::juce_cryptography juce::juce_opengl PUBLIC - juce::juce_recommended_lto_flags - #juce::juce_recommended_warning_flags + $:juce::juce_recommended_lto_flags ) set(libs diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 247ba40db4..eb6310c144 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -27,8 +27,7 @@ class GemJUCEWindow final : public Component setSize (800, 600); setOpaque (true); - //openGLContext.setRenderer (this); - openGLContext.setSwapInterval(1); + openGLContext.setSwapInterval(0); openGLContext.setMultisamplingEnabled(true); auto pixelFormat = OpenGLPixelFormat(8, 8, 16, 8); @@ -36,10 +35,7 @@ class GemJUCEWindow final : public Component openGLContext.setPixelFormat(pixelFormat); openGLContext.attachTo (*this); - //openGLContext.setContinuousRepainting (true); - - instance = libpd_this_instance(); } @@ -101,25 +97,37 @@ std::map> gemJUCEWindow; int createGemWindow(WindowInfo& info, WindowHints& hints) { - if(auto* window = info.getWindow()) - { - window->removeFromDesktop(); - gemJUCEWindow[window->instance].reset(nullptr); - } + // Problem: this call can either come from the message thread, or the pd/audio thread + // As a result, on Linux, we need to make sure that only one thread has the GL context set as active, otherwise things go very wrong auto* window = new GemJUCEWindow(); + window->addToDesktop(ComponentPeer::windowHasTitleBar | ComponentPeer::windowIsResizable | ComponentPeer::windowHasDropShadow); + window->setVisible(true); + gemJUCEWindow[window->instance].reset(window); info.window[window->instance] = window; - window->addToDesktop(ComponentPeer::windowHasTitleBar | ComponentPeer::windowIsResizable | ComponentPeer::windowHasDropShadow); - window->setVisible(true); - + #if JUCE_LINUX + // Make sure only audio thread has the context set as active + window->openGLContext.executeOnGLThread([](OpenGLContext& context){ + // We get unpredictable behaviour if the context is active on multiple threads + OpenGLContext::deactivateCurrentContext(); + }, true); + #endif + window->openGLContext.makeActive(); + info.context[window->instance] = &window->openGLContext; hints.real_w = window->getWidth(); hints.real_h = window->getHeight(); - + + // Make sure only audio thread has the context set as active + // We call async here, because if this call comes from the message thread already, + // we need to keep the context active until GLEW is initialised. Bit of a hack though + MessageManager::callAsync([window](){ + OpenGLContext::deactivateCurrentContext(); + }); return 1; } From 0d44ee1e4833bbb149f328ecc1b8fb3915c98a4a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 15:50:14 +0100 Subject: [PATCH 0020/1030] Remove ofelia compatibility, because it can never work as well as Gem anyway --- .gitmodules | 3 - CMakeLists.txt | 5 +- Libraries/CMakeLists.txt | 6 +- Libraries/plugdata-ofelia | 1 - Source/Dialogs/Deken.h | 16 ----- Source/Pd/Instance.cpp | 7 -- Source/Pd/Instance.h | 3 - Source/Pd/Ofelia.cpp | 15 ---- Source/Pd/Ofelia.h | 147 -------------------------------------- 9 files changed, 5 insertions(+), 198 deletions(-) delete mode 160000 Libraries/plugdata-ofelia delete mode 100644 Source/Pd/Ofelia.cpp delete mode 100644 Source/Pd/Ofelia.h diff --git a/.gitmodules b/.gitmodules index 43a4ef196e..c2296b90e3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,9 +22,6 @@ [submodule "Libraries/cpath"] path = Libraries/cpath url = https://github.com/BraedonWooding/cpath -[submodule "Libraries/plugdata-ofelia"] - path = Libraries/plugdata-ofelia - url = https://github.com/plugdata-team/plugdata-ofelia [submodule "Libraries/pd-else"] path = Libraries/pd-else url = https://github.com/timothyschoen/pd-else.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d0ed5737a..c285566090 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,10 +225,11 @@ target_link_libraries(juce juce::juce_dsp juce::juce_cryptography juce::juce_opengl - PUBLIC - $:juce::juce_recommended_lto_flags ) +target_compile_options(juce PUBLIC $<$:${JUCE_LTO_FLAGS}>) + + set(libs juce PlugDataBinaryData diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 16f508c6cf..932565c471 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -380,13 +380,11 @@ list(APPEND ELSE_SOURCES ${SFONT_DIR}/sfont~.c ) -file(GLOB OFELIA_SOURCES ./plugdata-ofelia/Source/Objects/*.cpp ./plugdata-ofelia/Source/Objects/*.h ./plugdata-ofelia/Source/Shared/*.cpp ./plugdata-ofelia/Source/Shared/*.h) -source_group(ofelia FILES ${OFELIA_SOURCES}) # ------------------------------------------------------------------------------# # TARGETS # ------------------------------------------------------------------------------# -add_library(externals STATIC ${ELSE_SOURCES} ${CYCLONE_SOURCES} ${PDLUA_SOURCES} ${AUBIO_SOURCES} ${OFELIA_SOURCES}) -add_library(externals-multi STATIC ${ELSE_SOURCES} ${CYCLONE_SOURCES} ${PDLUA_SOURCES} ${AUBIO_SOURCES} ${OFELIA_SOURCES}) +add_library(externals STATIC ${ELSE_SOURCES} ${CYCLONE_SOURCES} ${PDLUA_SOURCES} ${AUBIO_SOURCES}) +add_library(externals-multi STATIC ${ELSE_SOURCES} ${CYCLONE_SOURCES} ${PDLUA_SOURCES} ${AUBIO_SOURCES}) # ag: pdlua needs PLUGDATA symbol at compile time for PlugData integration. target_compile_definitions(externals PRIVATE ${LIBPD_COMPILE_DEFINITIONS} PLUGDATA=1) diff --git a/Libraries/plugdata-ofelia b/Libraries/plugdata-ofelia deleted file mode 160000 index 010afc52e8..0000000000 --- a/Libraries/plugdata-ofelia +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 010afc52e893b8b659aaff2c4abb1341e8f16b44 diff --git a/Source/Dialogs/Deken.h b/Source/Dialogs/Deken.h index e9eaca32c6..c9d596f896 100644 --- a/Source/Dialogs/Deken.h +++ b/Source/Dialogs/Deken.h @@ -264,22 +264,6 @@ class PackageManager : public Thread } } -#if JUCE_MAC - packages.add(PackageInfo("plugdata-ofelia", - "cuinjune and timothyschoen", - "0", - "https://github.com/plugdata-team/plugdata-ofelia/releases/download/v0.4.0-test4/plugdata-ofelia-macos.zip", - "Ofelia graphics environment for plugdata", - "v4.0.0-test4", { "ofelia" })); -#elif JUCE_WINDOWS - packages.add(PackageInfo("plugdata-ofelia", - "cuinjune and timothyschoen", - "0", - "https://github.com/plugdata-team/plugdata-ofelia/releases/download/v0.4.0-test4/plugdata-ofelia-windows.zip", - "Ofelia graphics environment for plugdata", - "v4.0.0-test4", { "ofelia" })); -#endif - return packages; } diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 0ac5fdbc5a..fe8fed51ef 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -303,13 +303,6 @@ void Instance::initialisePd(String& pdlua_version) initialised = true; } - // Hack to make sure ofelia doesn't get initialised during plugin validation, as this can cause problems - MessageManager::callAsync([_this = juce::WeakReference(this)]() { - if (!_this.get()) - return; - _this->ofelia = std::make_unique(static_cast(_this->instance)); - }); - setThis(); // ag: need to do this here to suppress noise from chatty externals diff --git a/Source/Pd/Instance.h b/Source/Pd/Instance.h index 0ebd694b11..aaaea92e79 100644 --- a/Source/Pd/Instance.h +++ b/Source/Pd/Instance.h @@ -15,7 +15,6 @@ extern "C" { #include #include "Utility/StringUtils.h" #include "Patch.h" -#include "Ofelia.h" class ObjectImplementationManager; @@ -468,8 +467,6 @@ class Instance { StringUtils fastStringWidth; // For formatting console messages more quickly }; - std::unique_ptr ofelia; - ConsoleHandler consoleHandler; JUCE_DECLARE_WEAK_REFERENCEABLE(Instance) diff --git a/Source/Pd/Ofelia.cpp b/Source/Pd/Ofelia.cpp deleted file mode 100644 index 48caef8cf4..0000000000 --- a/Source/Pd/Ofelia.cpp +++ /dev/null @@ -1,15 +0,0 @@ -/* - // Copyright (c) 2021-2022 Timothy Schoen. - // For information on usage and redistribution, and for a DISCLAIMER OF ALL - // WARRANTIES, see the file, "LICENSE.txt," in this distribution. - */ - -#include "Ofelia.h" - -#if defined(_WIN32) -# define DLL_EXPORT __declspec(dllexport) -#elif defined(__GNUC__) -# define DLL_EXPORT __attribute__((visibility("default"))) -#else -# define DLL_EXPORT -#endif diff --git a/Source/Pd/Ofelia.h b/Source/Pd/Ofelia.h deleted file mode 100644 index 0929f102fa..0000000000 --- a/Source/Pd/Ofelia.h +++ /dev/null @@ -1,147 +0,0 @@ -/* - // Copyright (c) 2021-2022 Timothy Schoen. - // For information on usage and redistribution, and for a DISCLAIMER OF ALL - // WARRANTIES, see the file, "LICENSE.txt," in this distribution. - */ - -#pragma once - -#include -#include "Utility/Config.h" -#include "Utility/OSUtils.h" - -extern "C" { -#include -#include -#include "Pd/Interface.h" - -void ofelia_setup(); -} - -#include "../Libraries/plugdata-ofelia/Source/Objects/ofxOfeliaMessageManager.h" - -namespace pd { - -class Ofelia : public Thread { -public: - static inline File ofeliaExecutable = File(); - - Ofelia(t_pdinstance* pdthis) - : Thread("Ofelia Thread") - , pdinstance(pdthis) - { - setup(); - startThread(); - } - - ~Ofelia() - { - shouldQuit = true; - ofeliaProcess.kill(); - stopThread(-1); - } - -private: - void setup() - { - auto ofeliaExecutable = findOfeliaExecutable(); - if (!ofeliaInitialised && ofeliaExecutable.existsAsFile()) { - ofeliaInitialised = true; - sys_lock(); - pd_globallock(); - set_class_prefix(gensym("ofelia")); - ofelia_setup(); - - set_class_prefix(nullptr); - pd_globalunlock(); - sys_unlock(); - } - } - - void run() override - { - while (!shouldQuit) { - auto ofeliaExecutable = findOfeliaExecutable(); - - if (!ofeliaExecutable.existsAsFile()) { - for (int i = 0; i < 10; i++) { - Time::waitForMillisecondCounter(Time::getMillisecondCounter() + 500); - if (shouldQuit) - break; - } - continue; - } - - ofeliaExecutable.setExecutePermission(true); - - libpd_set_instance(pdinstance); - - setup(); - - // Initialise threading system for ofelia - auto* messageManager = ofxOfeliaMessageManager::initialise(); - - int uniquePortNumber = Random().nextInt({ 20000, 50000 }); - - ofeliaProcess.start(ofeliaExecutable.getFullPathName() + " " + String(uniquePortNumber)); - - bool success = messageManager->bind(uniquePortNumber); - - if (!success) { - ofeliaProcess.kill(); - continue; - } - -#if JUCE_DEBUG - - auto err = ofeliaProcess.readAllProcessOutput(); - std::cerr << err << std::endl; - - // When debugging ofelia, it will falsly report that the process has finished - // Instead we wait forever - while (!shouldQuit) { - Time::waitForMillisecondCounter(Time::getMillisecondCounter() + 3000); - } -#else - ofeliaProcess.waitForProcessToFinish(-1); - ofeliaProcess.kill(); // just to be sure -#endif - } - } - - File findOfeliaExecutable() - { - if (ofeliaExecutable.existsAsFile()) { - return ofeliaExecutable; - } - - libpd_set_instance(pdinstance); - - char* p[1024]; - int numItems; - pd::Interface::getSearchPaths(p, &numItems); - auto paths = StringArray(p, numItems); - - for (auto& dir : paths) { - auto ofeliaDir = File(dir).getChildFile("ofelia"); - - if (!ofeliaDir.isDirectory()) - continue; - - for (auto const& file : OSUtils::iterateDirectory(ofeliaDir, false, true)) { - if (file.getFileName() == "ofelia" || file.getFileName() == "ofelia.exe") { - return ofeliaExecutable = file; - } - } - } - - return ofeliaExecutable = File(); - } - - t_pdinstance* pdinstance; - std::atomic ofeliaInitialised = false; - std::atomic shouldQuit = false; - ChildProcess ofeliaProcess; -}; - -} From 8df579f242c122452f1a830be7ce7ba403f5834c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 16:06:43 +0100 Subject: [PATCH 0021/1030] Gem fix --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 0c74b89c20..0491b0a28c 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 0c74b89c2084b71deeb6d437f1508287fca43c07 +Subproject commit 0491b0a28ce7636092d5d1d67c4f2c29f870c3a7 From 39c80b0c25cd6656df96b0bce29d5495e72abf17 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 16:38:15 +0100 Subject: [PATCH 0022/1030] Threading fixes for Gem --- Libraries/Gem | 2 +- Source/Objects/Gem.h | 27 ++++++++++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index 0491b0a28c..2c19f1a5c6 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 0491b0a28ce7636092d5d1d67c4f2c29f870c3a7 +Subproject commit 2c19f1a5c6ea5bb84c79f1a2abea6dff2b44e75b diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index eb6310c144..5f9c55c165 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -7,11 +7,6 @@ #include #include -extern void performGemRender(); -extern void initGemWindow(); - -void dequeueEvents(void); - void triggerMotionEvent(int x, int y); void triggerButtonEvent(int which, int state, int x, int y); void triggerWheelEvent(int axis, int value); @@ -157,6 +152,17 @@ void gemWinMakeCurrent(WindowInfo& info) { } } +bool gemWinMakeCurrent() { + if(!gemJUCEWindow.contains(libpd_this_instance())) return false; + + if(auto& window = gemJUCEWindow.at(libpd_this_instance())) { + window->openGLContext.makeActive(); + return true; + } + + return false; +} + // We handle this manually with JUCE void dispatchGemWindowMessages(WindowInfo& info) { } @@ -176,3 +182,14 @@ int topmostGemWindow(WindowInfo& info, int state) if(info.getWindow() && state) info.getWindow()->toFront(true); return state; } + +void GemCallOnMessageThread(std::function callback) +{ + //MessageManager::callAsync(callback); + MessageManager::getInstance()->callFunctionOnMessageThread([](void* callback) -> void* { + auto& fn = *reinterpret_cast*>(callback); + fn(); + + return nullptr; + }, (void*)&callback); +} From 3785ace3ff1ba41e07541dd5d2ebafdbc25ef855 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 16:40:02 +0100 Subject: [PATCH 0023/1030] Compilation fix --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 2c19f1a5c6..617d569bf7 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 2c19f1a5c6ea5bb84c79f1a2abea6dff2b44e75b +Subproject commit 617d569bf7984b7574612a9f36a470579218c919 From 00a1f3c8fa7f4e18f65f5851c50f2b568404b3bc Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 16:40:46 +0100 Subject: [PATCH 0024/1030] Compilation fix --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 617d569bf7..62f0564db3 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 617d569bf7984b7574612a9f36a470579218c919 +Subproject commit 62f0564db3ca284acfd1e55225f199262f4a050d From 37d5f1a578af398a597abcf9defb968608433a6c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 16:42:14 +0100 Subject: [PATCH 0025/1030] Compilation fix --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 62f0564db3..454dff9db5 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 62f0564db3ca284acfd1e55225f199262f4a050d +Subproject commit 454dff9db5629e9d400cccfc25b60df79386cf61 From ffef0b52f4948d7c7631cdb1806a14bf1df514dc Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 17:14:49 +0100 Subject: [PATCH 0026/1030] Enable Gem text support --- Libraries/Gem | 2 +- Source/Pd/Setup.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index 454dff9db5..af4a5212f1 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 454dff9db5629e9d400cccfc25b60df79386cf61 +Subproject commit af4a5212f1810c15f9b34037622039dec08c3482 diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index cd588a3831..6c875bf861 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -1715,11 +1715,10 @@ void Setup::initialiseGem() square_setup(); surface3d_setup(); teapot_setup(); - /* text2d_setup(); text3d_setup(); textextruded_setup(); - textoutline_setup(); */ + textoutline_setup(); torus_setup(); trapezoid_setup(); triangle_setup(); From 9fc48479dd4d791652740066b64cbf3075cec961 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 17:33:53 +0100 Subject: [PATCH 0027/1030] Fixed naming conflict --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index af4a5212f1..ee15f6ad4b 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit af4a5212f1810c15f9b34037622039dec08c3482 +Subproject commit ee15f6ad4b8e5865eb718115ef13102f4d930f11 From be1862726577f9e9f48f21b01ef166aa33682655 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 17:47:58 +0100 Subject: [PATCH 0028/1030] Small Gem fixes --- Libraries/Gem | 2 +- Source/Pd/Instance.cpp | 2 +- Source/Pd/Library.h | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index ee15f6ad4b..b892ba6b4c 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit ee15f6ad4b8e5865eb718115ef13102f4d930f11 +Subproject commit b892ba6b4cdfb295747e2924b1a01a25b14e724f diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index fe8fed51ef..93f0a60265 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -287,7 +287,7 @@ void Instance::initialisePd(String& pdlua_version) set_class_prefix(gensym("gem")); - class_set_extern_dir(gensym(ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("14.gem").getFullPathName().toRawUTF8())); + class_set_extern_dir(gensym(ProjectInfo::appDataDir.getChildFile("Extra").getChildFile("Gem").getFullPathName().toRawUTF8())); pd::Setup::initialiseGem(); class_set_extern_dir(gensym("")); diff --git a/Source/Pd/Library.h b/Source/Pd/Library.h index cb2825aa50..6b30dd08cf 100644 --- a/Source/Pd/Library.h +++ b/Source/Pd/Library.h @@ -48,9 +48,11 @@ class Library : public FileSystemWatcher::Listener { ProjectInfo::appDataDir.getChildFile("Abstractions").getChildFile("else"), ProjectInfo::appDataDir.getChildFile("Abstractions").getChildFile("cyclone"), ProjectInfo::appDataDir.getChildFile("Abstractions").getChildFile("heavylib"), + ProjectInfo::appDataDir.getChildFile("Abstractions").getChildFile("Gem"), ProjectInfo::appDataDir.getChildFile("Abstractions"), ProjectInfo::appDataDir.getChildFile("Externals"), ProjectInfo::appDataDir.getChildFile("Extra").getChildFile("else"), + ProjectInfo::appDataDir.getChildFile("Extra").getChildFile("Gem"), ProjectInfo::appDataDir.getChildFile("Extra") }; From 383e4a35ae359749b6a4317cee455bca561f8a7c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 17:58:58 +0100 Subject: [PATCH 0029/1030] Windows fix --- Libraries/Gem | 2 +- Resources/Scripts/package_resources.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index b892ba6b4c..4899a142bf 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit b892ba6b4cdfb295747e2924b1a01a25b14e724f +Subproject commit 4899a142bf6069346d8e9b31e66ac92803cc580b diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index f36ad0e5dc..b0d18f527c 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -129,7 +129,7 @@ def splitFile(file, num_files): copyDir("../../Libraries/Gem/doc", "Documentation/14.gem/examples/Documentation") globCopy("../../Libraries/Gem/abstractions/*.pd", "Abstractions/Gem/") globMove("Abstractions/Gem/*-help.pd", "Documentation/14.gem/") - +makeDir("Extra/Gem") # user can put plugins in here changeWorkingDir("./..") From dc201e39c49ea2e44a2ed24e3a5a02e46eb3b07a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 18:21:25 +0100 Subject: [PATCH 0030/1030] Fixed gem window resizing, fixed linker issue --- Libraries/Gem | 2 +- Source/Objects/Gem.h | 30 ++++++++++++++++++------------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index 4899a142bf..14f4b021c7 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 4899a142bf6069346d8e9b31e66ac92803cc580b +Subproject commit 14f4b021c7d400472fb7e7e23ce4e7378d135c07 diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 5f9c55c165..bfd987cfe7 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -82,6 +82,17 @@ class GemJUCEWindow final : public Component JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GemJUCEWindow) }; +void GemCallOnMessageThread(std::function callback) +{ + //MessageManager::callAsync(callback); + MessageManager::getInstance()->callFunctionOnMessageThread([](void* callback) -> void* { + auto& fn = *reinterpret_cast*>(callback); + fn(); + + return nullptr; + }, (void*)&callback); +} + // window/context creation&destruction bool initGemWin(void) { @@ -120,7 +131,7 @@ int createGemWindow(WindowInfo& info, WindowHints& hints) // Make sure only audio thread has the context set as active // We call async here, because if this call comes from the message thread already, // we need to keep the context active until GLEW is initialised. Bit of a hack though - MessageManager::callAsync([window](){ + MessageManager::callAsync([](){ OpenGLContext::deactivateCurrentContext(); }); @@ -152,6 +163,12 @@ void gemWinMakeCurrent(WindowInfo& info) { } } +void gemWinResize(WindowInfo& info, int width, int height) { + MessageManager::callAsync([window = info.getWindow(), width, height](){ + if(window) window->setSize(width, height); + }); +} + bool gemWinMakeCurrent() { if(!gemJUCEWindow.contains(libpd_this_instance())) return false; @@ -182,14 +199,3 @@ int topmostGemWindow(WindowInfo& info, int state) if(info.getWindow() && state) info.getWindow()->toFront(true); return state; } - -void GemCallOnMessageThread(std::function callback) -{ - //MessageManager::callAsync(callback); - MessageManager::getInstance()->callFunctionOnMessageThread([](void* callback) -> void* { - auto& fn = *reinterpret_cast*>(callback); - fn(); - - return nullptr; - }, (void*)&callback); -} From b8ea8fc308489cc9ee38bfc8c02b828d629151b7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 18:44:45 +0100 Subject: [PATCH 0031/1030] Linker fix --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 14f4b021c7..4eb9329e0b 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 14f4b021c7d400472fb7e7e23ce4e7378d135c07 +Subproject commit 4eb9329e0ba7f49d3e141c85dbfb9bb14310f7e4 From b3546b64dbdb88219c2752db74f91fde6858d444 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 18:47:57 +0100 Subject: [PATCH 0032/1030] Install freetype on Windows runner --- .github/workflows/cmake.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 0b78b99090..53f4d1bc38 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -86,6 +86,11 @@ jobs: submodules: recursive fetch-depth: 0 + - name: Install FreeType + run: | + choco install freetype + refreshenv # Refresh the environment variables to include the newly installed package + - name: Create Build Environment run: cmake -E make_directory ${{github.workspace}}/build @@ -136,6 +141,11 @@ jobs: submodules: recursive fetch-depth: 0 + - name: Install FreeType + run: | + choco install freetype + refreshenv # Refresh the environment variables to include the newly installed package + - name: Create Build Environment run: cmake -E make_directory ${{github.workspace}}/build From 1f8741e7df7e18ab82fad00d9c3c36304289fc66 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 19:53:46 +0100 Subject: [PATCH 0033/1030] Trying to fix Linux issues --- CMakeLists.txt | 2 +- Libraries/Gem | 2 +- Source/Objects/Gem.h | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c285566090..884ab7bbdf 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,7 +227,7 @@ target_link_libraries(juce juce::juce_opengl ) -target_compile_options(juce PUBLIC $<$:${JUCE_LTO_FLAGS}>) +target_compile_options(juce PUBLIC $<$:${JUCE_LTO_FLAGS}>) set(libs diff --git a/Libraries/Gem b/Libraries/Gem index 4eb9329e0b..76f15bf027 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 4eb9329e0ba7f49d3e141c85dbfb9bb14310f7e4 +Subproject commit 76f15bf0276e044ce135f6fa1323257dbe31e867 diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index bfd987cfe7..40f6575622 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -13,6 +13,8 @@ void triggerWheelEvent(int axis, int value); void triggerKeyboardEvent(const char *string, int value, int state); void triggerResizeEvent(int xSize, int ySize); +void initGemWindow(); + class GemJUCEWindow final : public Component { public: @@ -153,6 +155,8 @@ void initWin_sharedContext(WindowInfo& info, WindowHints& hints) { // Rendering void gemWinSwapBuffers(WindowInfo& info) { if (auto* context = info.getContext()) { + initGemWindow(); + context->makeActive(); context->swapBuffers(); } From a3158a192fbf6baa3c21f3226bcb8e2f5a585a5d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 19:54:26 +0100 Subject: [PATCH 0034/1030] Compilation fix --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 76f15bf027..5a7037b903 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 76f15bf0276e044ce135f6fa1323257dbe31e867 +Subproject commit 5a7037b903ce7069b14d2b22c35d32cd7b28606d From 2a0b4cc1b8d81fdee3f875c6b807add3bd74fbca Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 20:11:36 +0100 Subject: [PATCH 0035/1030] Build system fix --- .github/workflows/cmake.yml | 10 ---------- Libraries/CMakeLists.txt | 12 ++++++------ Libraries/Gem | 2 +- Source/Pd/Instance.cpp | 3 ++- 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 53f4d1bc38..0b78b99090 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -86,11 +86,6 @@ jobs: submodules: recursive fetch-depth: 0 - - name: Install FreeType - run: | - choco install freetype - refreshenv # Refresh the environment variables to include the newly installed package - - name: Create Build Environment run: cmake -E make_directory ${{github.workspace}}/build @@ -141,11 +136,6 @@ jobs: submodules: recursive fetch-depth: 0 - - name: Install FreeType - run: | - choco install freetype - refreshenv # Refresh the environment variables to include the newly installed package - - name: Create Build Environment run: cmake -E make_directory ${{github.workspace}}/build diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 932565c471..923e9ed591 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -429,22 +429,22 @@ if("${CMAKE_SYSTEM}" MATCHES "Linux") add_library(pd INTERFACE) add_library(pd-multi INTERFACE) find_library(MATH_LIB m) - target_link_libraries(externals ${externals_libs} Gem) - target_link_libraries(externals-multi ${externals_libs} Gem-multi) + target_link_libraries(externals ${externals_libs} glew ftgl Gem) + target_link_libraries(externals-multi ${externals_libs} glew ftgl Gem-multi) elseif(MSVC) add_library(pd SHARED $ $) add_library(pd-multi STATIC $ $) - target_link_libraries(pd PUBLIC pthreadVC3 ws2_32 ${externals_libs} Gem) - target_link_libraries(pd-multi PUBLIC pthreadVC3 ws2_32 ${externals_libs} Gem-multi) + target_link_libraries(pd PUBLIC pthreadVC3 ws2_32 ${externals_libs}) + target_link_libraries(pd-multi PUBLIC pthreadVC3 ws2_32 ${externals_libs}) add_custom_command(TARGET pd POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_BINARY_DIR}/$/pd.dll" ${CMAKE_SOURCE_DIR}/Plugins/Standalone/pd.dll) elseif(APPLE) - target_link_libraries(externals ${externals_libs} Gem) - target_link_libraries(externals-multi ${externals_libs} Gem-multi) + target_link_libraries(externals ${externals_libs} glew ftgl Gem) + target_link_libraries(externals-multi ${externals_libs} glew ftgl Gem-multi) endif() # PTHREAD diff --git a/Libraries/Gem b/Libraries/Gem index 5a7037b903..1cd4d1f5c3 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 5a7037b903ce7069b14d2b22c35d32cd7b28606d +Subproject commit 1cd4d1f5c3005ebff2fddcedc1a9bfd308e77256 diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 93f0a60265..ee3bb77a1f 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -285,11 +285,12 @@ void Instance::initialisePd(String& pdlua_version) class_set_extern_dir(gensym("10.cyclone")); pd::Setup::initialiseCyclone(); +#if !JUCE_WINDOWS // Windows support will hopefully be added soon! set_class_prefix(gensym("gem")); class_set_extern_dir(gensym(ProjectInfo::appDataDir.getChildFile("Extra").getChildFile("Gem").getFullPathName().toRawUTF8())); pd::Setup::initialiseGem(); - +#endif class_set_extern_dir(gensym("")); set_class_prefix(nullptr); From b240a0ead9dbe6576ae439fee1df20c8d9549de1 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 20:22:44 +0100 Subject: [PATCH 0036/1030] Build system fix --- Libraries/CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 923e9ed591..3fd85ed3bc 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -429,8 +429,8 @@ if("${CMAKE_SYSTEM}" MATCHES "Linux") add_library(pd INTERFACE) add_library(pd-multi INTERFACE) find_library(MATH_LIB m) - target_link_libraries(externals ${externals_libs} glew ftgl Gem) - target_link_libraries(externals-multi ${externals_libs} glew ftgl Gem-multi) + target_link_libraries(externals ${externals_libs} Gem glew ftgl) + target_link_libraries(externals-multi ${externals_libs} Gem-multi glew ftgl) elseif(MSVC) add_library(pd SHARED $ $) add_library(pd-multi STATIC $ $) @@ -443,11 +443,11 @@ elseif(MSVC) "${CMAKE_CURRENT_BINARY_DIR}/$/pd.dll" ${CMAKE_SOURCE_DIR}/Plugins/Standalone/pd.dll) elseif(APPLE) - target_link_libraries(externals ${externals_libs} glew ftgl Gem) - target_link_libraries(externals-multi ${externals_libs} glew ftgl Gem-multi) + target_link_libraries(externals ${externals_libs} Gem glew ftgl) + target_link_libraries(externals-multi ${externals_libs} Gem-multi glew ftgl) endif() # PTHREAD # ------------------------------------------------------------------------------# set(THREADS_PREFER_PTHREAD_FLAG On) -set(CMAKE_THREAD_PREFER_PTHREAD True) +set(CMAKE_THREAD_PREFER_PTHREAD True) \ No newline at end of file From 133c013280573238245c43a2f253d9c7b49a98b9 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 13 Jan 2024 20:24:31 +0100 Subject: [PATCH 0037/1030] Disable gem on Windows for now --- Libraries/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 3fd85ed3bc..14ff583f5d 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -253,7 +253,9 @@ source_group(pdlua FILES ${PDLUA_SOURCES}) # ------------------------------------------------------------------------------# set(LIBPD_COMPILE_DEFINITIONS PD=1 USEAPI_DUMMY=1 PD_INTERNAL=1) +if(NOT MSVC) # not on Windows yet... add_subdirectory(Gem) +endif() if(ENABLE_SFIZZ) list(APPEND LIBPD_COMPILE_DEFINITIONS ENABLE_SFIZZ=1) From 2c714757c8b4957b98c49fb3da75780e089e0003 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 13:40:44 +0100 Subject: [PATCH 0038/1030] Small fixes, build freetype from source --- Libraries/CMakeLists.txt | 8 ++++---- Libraries/Gem | 2 +- Source/Objects/Gem.h | 4 ---- Source/Pd/MessageListener.h | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 14ff583f5d..5ae51c14f8 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -431,8 +431,8 @@ if("${CMAKE_SYSTEM}" MATCHES "Linux") add_library(pd INTERFACE) add_library(pd-multi INTERFACE) find_library(MATH_LIB m) - target_link_libraries(externals ${externals_libs} Gem glew ftgl) - target_link_libraries(externals-multi ${externals_libs} Gem-multi glew ftgl) + target_link_libraries(externals ${externals_libs} Gem ftgl glew freetype) + target_link_libraries(externals-multi ${externals_libs} Gem-multi ftgl glew freetype) elseif(MSVC) add_library(pd SHARED $ $) add_library(pd-multi STATIC $ $) @@ -445,8 +445,8 @@ elseif(MSVC) "${CMAKE_CURRENT_BINARY_DIR}/$/pd.dll" ${CMAKE_SOURCE_DIR}/Plugins/Standalone/pd.dll) elseif(APPLE) - target_link_libraries(externals ${externals_libs} Gem glew ftgl) - target_link_libraries(externals-multi ${externals_libs} Gem-multi glew ftgl) + target_link_libraries(externals ${externals_libs} Gem ftgl glew freetype) + target_link_libraries(externals-multi ${externals_libs} Gem-multi ftgl glew freetype) endif() # PTHREAD diff --git a/Libraries/Gem b/Libraries/Gem index 1cd4d1f5c3..62e3e94248 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 1cd4d1f5c3005ebff2fddcedc1a9bfd308e77256 +Subproject commit 62e3e942483c98266e166b76d27ca6fe69d71e51 diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 40f6575622..9616257ca9 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -184,10 +184,6 @@ bool gemWinMakeCurrent() { return false; } -// We handle this manually with JUCE -void dispatchGemWindowMessages(WindowInfo& info) { -} - // Window behaviour int cursorGemWindow(WindowInfo& info, int state) { diff --git a/Source/Pd/MessageListener.h b/Source/Pd/MessageListener.h index 316722869b..48e6fc26c9 100644 --- a/Source/Pd/MessageListener.h +++ b/Source/Pd/MessageListener.h @@ -149,7 +149,7 @@ class MessageDispatcher : private AsyncUpdater { } } - moodycamel::ConcurrentQueue messageQueue = moodycamel::ConcurrentQueue(32768); + moodycamel::ReaderWriterQueue messageQueue = moodycamel::ReaderWriterQueue(32768); std::map>> messageListeners; CriticalSection messageListenerLock; }; From f3725acd97b9fe21cc22eccf1bffc6c106028f51 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 16:07:59 +0100 Subject: [PATCH 0039/1030] Fixed namespace clash, improved build system --- Libraries/Gem | 2 +- .../cyclone_objects/binaries/control/scale.c | 4 +- Source/Objects/Gem.h | 56 +++++++++++++++++-- Source/Pd/Instance.cpp | 2 +- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index 62e3e94248..bddfe6ca21 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 62e3e942483c98266e166b76d27ca6fe69d71e51 +Subproject commit bddfe6ca214c260153f251995d4b4890316c4d02 diff --git a/Libraries/cyclone/cyclone_objects/binaries/control/scale.c b/Libraries/cyclone/cyclone_objects/binaries/control/scale.c index 9313570741..fe23c3d2bd 100644 --- a/Libraries/cyclone/cyclone_objects/binaries/control/scale.c +++ b/Libraries/cyclone/cyclone_objects/binaries/control/scale.c @@ -149,7 +149,9 @@ static void *scale_new(t_symbol *s, int argc, t_atom *argv) CYCLONE_OBJ_API void scale_setup(void) { t_class *c; - scale_class = class_new(gensym("scale"), (t_newmethod)scale_new, + + // Gem also has a [scale] object, and it needs to be in global namespace + scale_class = class_new(gensym("cyclone/scale"), (t_newmethod)scale_new, (t_method)scale_free,sizeof(t_scale),0,A_GIMME,0); c = scale_class; class_addfloat(c,(t_method)scale_ft); diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 9616257ca9..7af0feb321 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -13,18 +13,50 @@ void triggerWheelEvent(int axis, int value); void triggerKeyboardEvent(const char *string, int value, int state); void triggerResizeEvent(int xSize, int ySize); +void gemBeginExternalResize(); +void gemEndExternalResize(); + void initGemWindow(); -class GemJUCEWindow final : public Component +class GemJUCEWindow final : public DocumentWindow { + // Use a constrainer as a resize listener! + struct GemWindowResizeListener : public ComponentBoundsConstrainer + { + std::function beginResize, endResize; + + void resizeStart() + { + beginResize(); + } + + void resizeEnd() + { + endResize(); + } + }; public: //============================================================================== - GemJUCEWindow() + GemJUCEWindow() : DocumentWindow("Gem", Colours::black, DocumentWindow::minimiseButton | DocumentWindow::maximiseButton, false) { + instance = libpd_this_instance(); + setSize (800, 600); + resizeListener.beginResize = [this](){ + setThis(); + gemBeginExternalResize(); + }; + resizeListener.endResize = [this](){ + setThis(); + gemEndExternalResize(); + }; + + setConstrainer(&resizeListener); + setOpaque (true); openGLContext.setSwapInterval(0); + setResizable(true, false); openGLContext.setMultisamplingEnabled(true); auto pixelFormat = OpenGLPixelFormat(8, 8, 16, 8); @@ -32,8 +64,6 @@ class GemJUCEWindow final : public Component openGLContext.setPixelFormat(pixelFormat); openGLContext.attachTo (*this); - - instance = libpd_this_instance(); } ~GemJUCEWindow() override @@ -42,6 +72,7 @@ class GemJUCEWindow final : public Component void resized() override { + setThis(); triggerResizeEvent(getWidth(), getHeight()); } @@ -51,25 +82,30 @@ class GemJUCEWindow final : public Component void mouseDown(const MouseEvent& e) override { + setThis(); triggerButtonEvent(e.mods.isRightButtonDown(), 1, e.x, e.y); } void mouseUp(const MouseEvent& e) override { + setThis(); triggerButtonEvent(e.mods.isRightButtonDown(), 0, e.x, e.y); } void mouseMove(const MouseEvent& e) override { + setThis(); triggerMotionEvent(e.x, e.y); } void mouseDrag(const MouseEvent& e) override { + setThis(); triggerMotionEvent(e.x, e.y); } void mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) override { + setThis(); triggerWheelEvent(wheel.deltaX, wheel.deltaY); } @@ -79,6 +115,13 @@ class GemJUCEWindow final : public Component return false; } + void setThis() + { + libpd_set_instance(instance); + } + + + GemWindowResizeListener resizeListener; OpenGLContext openGLContext; t_pdinstance* instance; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GemJUCEWindow) @@ -109,7 +152,8 @@ int createGemWindow(WindowInfo& info, WindowHints& hints) // As a result, on Linux, we need to make sure that only one thread has the GL context set as active, otherwise things go very wrong auto* window = new GemJUCEWindow(); - window->addToDesktop(ComponentPeer::windowHasTitleBar | ComponentPeer::windowIsResizable | ComponentPeer::windowHasDropShadow); + window->setUsingNativeTitleBar(true); + window->addToDesktop(); window->setVisible(true); gemJUCEWindow[window->instance].reset(window); @@ -180,7 +224,7 @@ bool gemWinMakeCurrent() { window->openGLContext.makeActive(); return true; } - + return false; } diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index ee3bb77a1f..38090e17b1 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -286,7 +286,7 @@ void Instance::initialisePd(String& pdlua_version) pd::Setup::initialiseCyclone(); #if !JUCE_WINDOWS // Windows support will hopefully be added soon! - set_class_prefix(gensym("gem")); + set_class_prefix(gensym("Gem")); class_set_extern_dir(gensym(ProjectInfo::appDataDir.getChildFile("Extra").getChildFile("Gem").getFullPathName().toRawUTF8())); pd::Setup::initialiseGem(); From 9c53158800eaac0f3fb251f552456c51a55518e0 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 16:17:54 +0100 Subject: [PATCH 0040/1030] Build fix --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index bddfe6ca21..4b90b27450 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit bddfe6ca214c260153f251995d4b4890316c4d02 +Subproject commit 4b90b27450b808d6c1c4bfb0cdde25a1823f410b From af3bd743e16c596aa5c71a9fd763fbdc865812f8 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 16:46:03 +0100 Subject: [PATCH 0041/1030] Linux support fixes, hi-dpi fixes --- .github/workflows/cmake.yml | 2 +- Source/Objects/Gem.h | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 0b78b99090..65e216bad4 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -227,7 +227,7 @@ jobs: - name: Install Dependencies (pacman) if: ${{ matrix.pacman == 'pacman' }} - run: pacman -Sy && pacman -S --noconfirm cmake wget bzip2 git alsa-lib freetype2 libx11 libxcursor libxi libxext libxinerama libxrandr libxrender webkit2gtk cmake make gcc pkgconf python python-pip curl ccache && pacman --noconfirm -Syu + run: pacman -Sy && pacman -S --noconfirm cmake wget bzip2 git alsa-lib freetype2 libx11 libxcursor libxi libxext libxinerama libxrandr libxrender webkit2gtk cmake make gcc pkgconf python python-pip curl ccache freeglut && pacman --noconfirm -Syu - uses: actions/checkout@v3 with: diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 7af0feb321..e222e27db9 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -18,8 +18,9 @@ void gemEndExternalResize(); void initGemWindow(); -class GemJUCEWindow final : public DocumentWindow +class GemJUCEWindow final : public Component { + // Use a constrainer as a resize listener! struct GemWindowResizeListener : public ComponentBoundsConstrainer { @@ -35,9 +36,13 @@ class GemJUCEWindow final : public DocumentWindow endResize(); } }; + + ResizableCornerComponent resizer; + GemWindowResizeListener resizeListener; + public: //============================================================================== - GemJUCEWindow() : DocumentWindow("Gem", Colours::black, DocumentWindow::minimiseButton | DocumentWindow::maximiseButton, false) + GemJUCEWindow() : resizer(this, &resizeListener)//DocumentWindow("Gem", Colours::black, DocumentWindow::minimiseButton | DocumentWindow::maximiseButton, false) { instance = libpd_this_instance(); @@ -51,12 +56,9 @@ class GemJUCEWindow final : public DocumentWindow setThis(); gemEndExternalResize(); }; - - setConstrainer(&resizeListener); setOpaque (true); openGLContext.setSwapInterval(0); - setResizable(true, false); openGLContext.setMultisamplingEnabled(true); auto pixelFormat = OpenGLPixelFormat(8, 8, 16, 8); @@ -64,6 +66,8 @@ class GemJUCEWindow final : public DocumentWindow openGLContext.setPixelFormat(pixelFormat); openGLContext.attachTo (*this); + + addAndMakeVisible(resizer); } ~GemJUCEWindow() override @@ -72,8 +76,11 @@ class GemJUCEWindow final : public DocumentWindow void resized() override { + auto scale = Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale; + setThis(); - triggerResizeEvent(getWidth(), getHeight()); + triggerResizeEvent(getWidth() * scale, getHeight() * scale); + resizer.setBounds(getLocalBounds()); } void paint (Graphics&) override @@ -120,8 +127,6 @@ class GemJUCEWindow final : public DocumentWindow libpd_set_instance(instance); } - - GemWindowResizeListener resizeListener; OpenGLContext openGLContext; t_pdinstance* instance; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GemJUCEWindow) @@ -152,8 +157,7 @@ int createGemWindow(WindowInfo& info, WindowHints& hints) // As a result, on Linux, we need to make sure that only one thread has the GL context set as active, otherwise things go very wrong auto* window = new GemJUCEWindow(); - window->setUsingNativeTitleBar(true); - window->addToDesktop(); + window->addToDesktop(ComponentPeer::windowHasTitleBar | ComponentPeer::windowHasDropShadow | ComponentPeer::windowIsResizable); window->setVisible(true); gemJUCEWindow[window->instance].reset(window); @@ -199,8 +203,6 @@ void initWin_sharedContext(WindowInfo& info, WindowHints& hints) { // Rendering void gemWinSwapBuffers(WindowInfo& info) { if (auto* context = info.getContext()) { - initGemWindow(); - context->makeActive(); context->swapBuffers(); } From a060d05beb1462731f06671c61269409c338db53 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 16:47:37 +0100 Subject: [PATCH 0042/1030] Thread safety fix --- Source/Objects/Gem.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index e222e27db9..af1750de33 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -79,7 +79,9 @@ class GemJUCEWindow final : public Component auto scale = Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale; setThis(); + sys_lock(); triggerResizeEvent(getWidth() * scale, getHeight() * scale); + sys_unlock(); resizer.setBounds(getLocalBounds()); } @@ -90,30 +92,40 @@ class GemJUCEWindow final : public Component void mouseDown(const MouseEvent& e) override { setThis(); + sys_lock(); triggerButtonEvent(e.mods.isRightButtonDown(), 1, e.x, e.y); + sys_unlock(); } void mouseUp(const MouseEvent& e) override { setThis(); + sys_lock(); triggerButtonEvent(e.mods.isRightButtonDown(), 0, e.x, e.y); + sys_unlock(); } void mouseMove(const MouseEvent& e) override { setThis(); + sys_lock(); triggerMotionEvent(e.x, e.y); + sys_unlock(); } void mouseDrag(const MouseEvent& e) override { setThis(); + sys_lock(); triggerMotionEvent(e.x, e.y); + sys_unlock(); } void mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) override { setThis(); + sys_lock(); triggerWheelEvent(wheel.deltaX, wheel.deltaY); + sys_unlock(); } bool keyPressed(KeyPress const& key) override From 41485806a122b148bfe6243bf49a4cce7cb2d860 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 16:51:49 +0100 Subject: [PATCH 0043/1030] Reverted some unnecessary changes --- Libraries/Gem | 2 +- Source/Objects/Gem.h | 37 +------------------------------------ 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index 4b90b27450..3dd5f0a526 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 4b90b27450b808d6c1c4bfb0cdde25a1823f410b +Subproject commit 3dd5f0a52615b48289898d3f82c0afd5b4f8ebab diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index af1750de33..6e580aef62 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -13,49 +13,17 @@ void triggerWheelEvent(int axis, int value); void triggerKeyboardEvent(const char *string, int value, int state); void triggerResizeEvent(int xSize, int ySize); -void gemBeginExternalResize(); -void gemEndExternalResize(); - void initGemWindow(); class GemJUCEWindow final : public Component { - - // Use a constrainer as a resize listener! - struct GemWindowResizeListener : public ComponentBoundsConstrainer - { - std::function beginResize, endResize; - - void resizeStart() - { - beginResize(); - } - - void resizeEnd() - { - endResize(); - } - }; - - ResizableCornerComponent resizer; - GemWindowResizeListener resizeListener; - public: //============================================================================== - GemJUCEWindow() : resizer(this, &resizeListener)//DocumentWindow("Gem", Colours::black, DocumentWindow::minimiseButton | DocumentWindow::maximiseButton, false) + GemJUCEWindow() { instance = libpd_this_instance(); setSize (800, 600); - - resizeListener.beginResize = [this](){ - setThis(); - gemBeginExternalResize(); - }; - resizeListener.endResize = [this](){ - setThis(); - gemEndExternalResize(); - }; setOpaque (true); openGLContext.setSwapInterval(0); @@ -66,8 +34,6 @@ class GemJUCEWindow final : public Component openGLContext.setPixelFormat(pixelFormat); openGLContext.attachTo (*this); - - addAndMakeVisible(resizer); } ~GemJUCEWindow() override @@ -82,7 +48,6 @@ class GemJUCEWindow final : public Component sys_lock(); triggerResizeEvent(getWidth() * scale, getHeight() * scale); sys_unlock(); - resizer.setBounds(getLocalBounds()); } void paint (Graphics&) override From 0285cde5aec6e477d8ad6b2c7f5ed2d71a9b9b48 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 16:57:07 +0100 Subject: [PATCH 0044/1030] Fixed regression --- Source/Objects/Gem.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 6e580aef62..30bcf3009c 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -180,6 +180,8 @@ void initWin_sharedContext(WindowInfo& info, WindowHints& hints) { // Rendering void gemWinSwapBuffers(WindowInfo& info) { if (auto* context = info.getContext()) { + initGemWindow(); // If we don't put this here, the background doens't get filled, but there must be a better way? + context->makeActive(); context->swapBuffers(); } From c5b1a8e22f95499f5b48d69ba6bb41d06b3b9f54 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 16:58:14 +0100 Subject: [PATCH 0045/1030] Arch build fix --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 65e216bad4..6d6ee17622 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -227,7 +227,7 @@ jobs: - name: Install Dependencies (pacman) if: ${{ matrix.pacman == 'pacman' }} - run: pacman -Sy && pacman -S --noconfirm cmake wget bzip2 git alsa-lib freetype2 libx11 libxcursor libxi libxext libxinerama libxrandr libxrender webkit2gtk cmake make gcc pkgconf python python-pip curl ccache freeglut && pacman --noconfirm -Syu + run: pacman -Sy && pacman -S --noconfirm cmake wget bzip2 git alsa-lib freetype2 libx11 libxcursor libxi libxext libxinerama libxrandr libxrender webkit2gtk cmake make gcc pkgconf python python-pip curl ccache freeglut mesa glfw-x11 glew && pacman --noconfirm -Syu - uses: actions/checkout@v3 with: From 4ee1615e0946c61e121e73dbc8adf7ecd972f0cc Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 17:13:27 +0100 Subject: [PATCH 0046/1030] Make sure helpfiles can find resources --- Resources/Scripts/package_resources.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index b0d18f527c..84778db609 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -129,6 +129,7 @@ def splitFile(file, num_files): copyDir("../../Libraries/Gem/doc", "Documentation/14.gem/examples/Documentation") globCopy("../../Libraries/Gem/abstractions/*.pd", "Abstractions/Gem/") globMove("Abstractions/Gem/*-help.pd", "Documentation/14.gem/") +globCopy("../../Libraries/Gem/examples/data/*", "Extra/Gem/") # if we don't do this, helpfiles can't find resources for some reason makeDir("Extra/Gem") # user can put plugins in here changeWorkingDir("./..") From d499afbb229e344c847ccd8cd8547673cad7e8ff Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 17:18:42 +0100 Subject: [PATCH 0047/1030] Add resources to gem search path --- Resources/Scripts/package_resources.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index 84778db609..79e76beb32 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -129,8 +129,10 @@ def splitFile(file, num_files): copyDir("../../Libraries/Gem/doc", "Documentation/14.gem/examples/Documentation") globCopy("../../Libraries/Gem/abstractions/*.pd", "Abstractions/Gem/") globMove("Abstractions/Gem/*-help.pd", "Documentation/14.gem/") + +makeDir("Extra/Gem") # user can put plugins and resources in here globCopy("../../Libraries/Gem/examples/data/*", "Extra/Gem/") # if we don't do this, helpfiles can't find resources for some reason -makeDir("Extra/Gem") # user can put plugins in here + changeWorkingDir("./..") From 2f976758aff509917c6fd644e687a7f9d123082a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 17:22:20 +0100 Subject: [PATCH 0048/1030] Try to compile on Windows --- Libraries/CMakeLists.txt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 5ae51c14f8..af5bdd20b5 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -253,9 +253,7 @@ source_group(pdlua FILES ${PDLUA_SOURCES}) # ------------------------------------------------------------------------------# set(LIBPD_COMPILE_DEFINITIONS PD=1 USEAPI_DUMMY=1 PD_INTERNAL=1) -if(NOT MSVC) # not on Windows yet... add_subdirectory(Gem) -endif() if(ENABLE_SFIZZ) list(APPEND LIBPD_COMPILE_DEFINITIONS ENABLE_SFIZZ=1) @@ -437,8 +435,8 @@ elseif(MSVC) add_library(pd SHARED $ $) add_library(pd-multi STATIC $ $) - target_link_libraries(pd PUBLIC pthreadVC3 ws2_32 ${externals_libs}) - target_link_libraries(pd-multi PUBLIC pthreadVC3 ws2_32 ${externals_libs}) + target_link_libraries(pd PUBLIC pthreadVC3 ws2_32 ${externals_libs} Gem ftgl glew freetype) + target_link_libraries(pd-multi PUBLIC pthreadVC3 ws2_32 ${externals_libs} Gem-multi ftgl glew freetype) add_custom_command(TARGET pd POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy From 0771af38ce82ae1283c21ddd4c08671ceeb562f2 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 17:46:55 +0100 Subject: [PATCH 0049/1030] Add implementation for Gem keypress objects --- Libraries/Gem | 2 +- Source/Objects/Gem.h | 31 +++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index 3dd5f0a526..da8116c821 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 3dd5f0a52615b48289898d3f82c0afd5b4f8ebab +Subproject commit da8116c821e290f7fb2fd6a4686a0f656dd6d9b0 diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 30bcf3009c..70eb117f1f 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -7,6 +7,7 @@ #include #include + void triggerMotionEvent(int x, int y); void triggerButtonEvent(int which, int state, int x, int y); void triggerWheelEvent(int axis, int value); @@ -15,7 +16,7 @@ void triggerResizeEvent(int xSize, int ySize); void initGemWindow(); -class GemJUCEWindow final : public Component +class GemJUCEWindow final : public Component, public Timer { public: //============================================================================== @@ -34,6 +35,8 @@ class GemJUCEWindow final : public Component openGLContext.setPixelFormat(pixelFormat); openGLContext.attachTo (*this); + + startTimerHz(30); } ~GemJUCEWindow() override @@ -95,10 +98,32 @@ class GemJUCEWindow final : public Component bool keyPressed(KeyPress const& key) override { - //triggerKeyboardEvent(key.getTextDescription().toRawUTF8(), key.getKeyCode(), 1); + sys_lock(); + triggerKeyboardEvent(key.getTextDescription().toRawUTF8(), key.getKeyCode(), 1); + sys_unlock(); + + heldKeys.add(key); + return false; } + void timerCallback() override + { + for(int i = heldKeys.size() - 1; i >= 0; i--) + { + auto key = heldKeys[i]; + if(!KeyPress::isKeyCurrentlyDown(key.getKeyCode())) + { + sys_lock(); + triggerKeyboardEvent(key.getTextDescription().toRawUTF8(), key.getKeyCode(), 0); + sys_unlock(); + + heldKeys.remove(i); + } + } + + } + void setThis() { libpd_set_instance(instance); @@ -106,6 +131,8 @@ class GemJUCEWindow final : public Component OpenGLContext openGLContext; t_pdinstance* instance; + Array heldKeys; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GemJUCEWindow) }; From 48379aa41802c59c97089af554df1bd9d44d65d3 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 20:24:10 +0100 Subject: [PATCH 0050/1030] Statically link glew --- Libraries/CMakeLists.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index af5bdd20b5..e8147669f3 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -429,22 +429,22 @@ if("${CMAKE_SYSTEM}" MATCHES "Linux") add_library(pd INTERFACE) add_library(pd-multi INTERFACE) find_library(MATH_LIB m) - target_link_libraries(externals ${externals_libs} Gem ftgl glew freetype) - target_link_libraries(externals-multi ${externals_libs} Gem-multi ftgl glew freetype) + target_link_libraries(externals ${externals_libs} Gem ftgl glew_s freetype) + target_link_libraries(externals-multi ${externals_libs} Gem-multi ftgl glew_s freetype) elseif(MSVC) add_library(pd SHARED $ $) add_library(pd-multi STATIC $ $) - target_link_libraries(pd PUBLIC pthreadVC3 ws2_32 ${externals_libs} Gem ftgl glew freetype) - target_link_libraries(pd-multi PUBLIC pthreadVC3 ws2_32 ${externals_libs} Gem-multi ftgl glew freetype) + target_link_libraries(pd PUBLIC pthreadVC3 ws2_32 ${externals_libs} Gem ftgl glew_s freetype) + target_link_libraries(pd-multi PUBLIC pthreadVC3 ws2_32 ${externals_libs} Gem-multi ftgl glew_s freetype) add_custom_command(TARGET pd POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_BINARY_DIR}/$/pd.dll" ${CMAKE_SOURCE_DIR}/Plugins/Standalone/pd.dll) elseif(APPLE) - target_link_libraries(externals ${externals_libs} Gem ftgl glew freetype) - target_link_libraries(externals-multi ${externals_libs} Gem-multi ftgl glew freetype) + target_link_libraries(externals ${externals_libs} Gem ftgl glew_s freetype) + target_link_libraries(externals-multi ${externals_libs} Gem-multi ftgl glew_s freetype) endif() # PTHREAD From e6c2c4e454b78fb8c0b1121cf8f2f912e826d492 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 20:24:44 +0100 Subject: [PATCH 0051/1030] Fix linker issue --- Libraries/Gem | 2 +- Source/Objects/Gem.h | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index da8116c821..567b51b677 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit da8116c821e290f7fb2fd6a4686a0f656dd6d9b0 +Subproject commit 567b51b6773d95b57870e83d7cd2ae3955f3ddfd diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 70eb117f1f..234a9ac70c 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -15,9 +15,30 @@ void triggerKeyboardEvent(const char *string, int value, int state); void triggerResizeEvent(int xSize, int ySize); void initGemWindow(); +void gemBeginExternalResize(); +void gemEndExternalResize(); class GemJUCEWindow final : public Component, public Timer { + // Use a constrainer as a resize listener! + struct GemWindowResizeListener : public ComponentBoundsConstrainer + { + std::function beginResize, endResize; + + void resizeStart() + { + beginResize(); + } + + void resizeEnd() + { + endResize(); + } + }; + + //ResizableCornerComponent resizer; + //GemWindowResizeListener resizeListener; + public: //============================================================================== GemJUCEWindow() From 75ee36b6e4bde17636733facd2bdc567a1869167 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 20:42:54 +0100 Subject: [PATCH 0052/1030] Add cmake switch to disable Gem --- CMakeLists.txt | 9 +++++++++ Libraries/CMakeLists.txt | 16 ++++++++++------ Source/Pd/Instance.cpp | 3 +-- Source/Pd/Setup.cpp | 4 ++++ 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 884ab7bbdf..d40a2cc026 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) option(RUN_CLANG_TIDY "" OFF) option(ENABLE_TESTING "" OFF) option(ENABLE_SFIZZ "" ON) +option(ENABLE_GEM "" ON) option(ENABLE_ASAN "" OFF) option(VERBOSE "" OFF) @@ -55,6 +56,10 @@ if(("${CMAKE_SYSTEM_NAME}" MATCHES "Linux" AND ${CMAKE_SYSTEM_PROCESSOR} MATCHES set(ENABLE_SFIZZ OFF) endif() +if(MSVC) # Gem is not supported on Windows yet +set(ENABLE_GEM OFF) +endif() + add_subdirectory(Libraries/ EXCLUDE_FROM_ALL) cmake_policy(SET CMP0091 NEW) @@ -210,6 +215,10 @@ if(ENABLE_SFIZZ) list(APPEND PLUGDATA_COMPILE_DEFINITIONS ENABLE_SFIZZ=1) endif() +if(ENABLE_GEM) + list(APPEND PLUGDATA_COMPILE_DEFINITIONS ENABLE_GEM=1) +endif() + add_library(juce STATIC) target_compile_definitions(juce PUBLIC diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index e8147669f3..d12b6fcab7 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -253,7 +253,11 @@ source_group(pdlua FILES ${PDLUA_SOURCES}) # ------------------------------------------------------------------------------# set(LIBPD_COMPILE_DEFINITIONS PD=1 USEAPI_DUMMY=1 PD_INTERNAL=1) +if(ENABLE_GEM) add_subdirectory(Gem) +set(GEM_LIBS Gem ftgl glew_s freetype) +set(GEM_LIBS_MULTI Gem-multi ftgl glew_s freetype) +endif() if(ENABLE_SFIZZ) list(APPEND LIBPD_COMPILE_DEFINITIONS ENABLE_SFIZZ=1) @@ -429,22 +433,22 @@ if("${CMAKE_SYSTEM}" MATCHES "Linux") add_library(pd INTERFACE) add_library(pd-multi INTERFACE) find_library(MATH_LIB m) - target_link_libraries(externals ${externals_libs} Gem ftgl glew_s freetype) - target_link_libraries(externals-multi ${externals_libs} Gem-multi ftgl glew_s freetype) + target_link_libraries(externals ${externals_libs} ${GEM_LIBS}) + target_link_libraries(externals-multi ${externals_libs} ${GEM_LIBS_MULTI}) elseif(MSVC) add_library(pd SHARED $ $) add_library(pd-multi STATIC $ $) - target_link_libraries(pd PUBLIC pthreadVC3 ws2_32 ${externals_libs} Gem ftgl glew_s freetype) - target_link_libraries(pd-multi PUBLIC pthreadVC3 ws2_32 ${externals_libs} Gem-multi ftgl glew_s freetype) + target_link_libraries(pd PUBLIC pthreadVC3 ws2_32 ${externals_libs}) + target_link_libraries(pd-multi PUBLIC pthreadVC3 ws2_32 ${externals_libs}) add_custom_command(TARGET pd POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_BINARY_DIR}/$/pd.dll" ${CMAKE_SOURCE_DIR}/Plugins/Standalone/pd.dll) elseif(APPLE) - target_link_libraries(externals ${externals_libs} Gem ftgl glew_s freetype) - target_link_libraries(externals-multi ${externals_libs} Gem-multi ftgl glew_s freetype) + target_link_libraries(externals ${externals_libs} ${GEM_LIBS}) + target_link_libraries(externals-multi ${externals_libs} ${GEM_LIBS_MULTI}) endif() # PTHREAD diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 38090e17b1..30543eca1c 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -285,12 +285,11 @@ void Instance::initialisePd(String& pdlua_version) class_set_extern_dir(gensym("10.cyclone")); pd::Setup::initialiseCyclone(); -#if !JUCE_WINDOWS // Windows support will hopefully be added soon! set_class_prefix(gensym("Gem")); class_set_extern_dir(gensym(ProjectInfo::appDataDir.getChildFile("Extra").getChildFile("Gem").getFullPathName().toRawUTF8())); pd::Setup::initialiseGem(); -#endif + class_set_extern_dir(gensym("")); set_class_prefix(nullptr); diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index 6c875bf861..ce9ae6361f 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -159,6 +159,7 @@ extern "C" { void pd_init(); +#if ENABLE_GEM void Gem_setup(); void gemcubeframebuffer_setup(); void gemframebuffer_setup(); @@ -697,6 +698,7 @@ void GEMglViewport_setup(); void GEMgluLookAt_setup(); void GEMgluPerspective_setup(); void GLdefine_setup(); +#endif // pd-extra objects functions declaration void bob_tilde_setup(); @@ -1670,6 +1672,7 @@ void Setup::initialiseELSE() void Setup::initialiseGem() { +#if ENABLE_GEM Gem_setup(); gemcubeframebuffer_setup(); gemframebuffer_setup(); @@ -2221,6 +2224,7 @@ void Setup::initialiseGem() GEMgluLookAt_setup(); GEMgluPerspective_setup(); GLdefine_setup(); +#endif } void Setup::initialiseCyclone() From f323d0a15b56e00a63137addb33198aba88642cd Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 20:52:39 +0100 Subject: [PATCH 0053/1030] Fixed compile option without Gem --- Source/Objects/Gem.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 234a9ac70c..72dc7c37db 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -7,7 +7,7 @@ #include #include - +#if ENABLE_GEM void triggerMotionEvent(int x, int y); void triggerButtonEvent(int which, int state, int x, int y); void triggerWheelEvent(int axis, int value); @@ -272,3 +272,4 @@ int topmostGemWindow(WindowInfo& info, int state) if(info.getWindow() && state) info.getWindow()->toFront(true); return state; } +#endif From e6b19104aff95297f1f994c075ceaba01616f1fc Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 14 Jan 2024 21:07:58 +0100 Subject: [PATCH 0054/1030] Fixed deadlock --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 567b51b677..a1b801ed2c 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 567b51b6773d95b57870e83d7cd2ae3955f3ddfd +Subproject commit a1b801ed2c5d5307496bffc4d4c62fab1378a172 From 7d4094b59a845f81500f71be094d959d6a36b4b2 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 15 Jan 2024 01:40:04 +0100 Subject: [PATCH 0055/1030] Gem: fix crashes when resizing window --- Source/Objects/Gem.h | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 72dc7c37db..4940957771 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -36,8 +36,7 @@ class GemJUCEWindow final : public Component, public Timer } }; - //ResizableCornerComponent resizer; - //GemWindowResizeListener resizeListener; + GemWindowResizeListener resizeListener; public: //============================================================================== @@ -45,6 +44,21 @@ class GemJUCEWindow final : public Component, public Timer { instance = libpd_this_instance(); + resizeListener.beginResize = [this](){ + setThis(); + sys_lock(); + gemBeginExternalResize(); + sys_unlock(); + }; + + resizeListener.endResize = [this](){ + setThis(); + sys_lock(); + gemEndExternalResize(); + initGemWindow(); + sys_unlock(); + }; + setSize (800, 600); setOpaque (true); @@ -58,6 +72,20 @@ class GemJUCEWindow final : public Component, public Timer openGLContext.attachTo (*this); startTimerHz(30); + + addToDesktop(ComponentPeer::windowHasTitleBar | + ComponentPeer::windowHasDropShadow | + ComponentPeer::windowIsResizable | + ComponentPeer::windowHasMinimiseButton | + ComponentPeer::windowHasMaximiseButton + ); + + setVisible(true); + + if(auto* peer = getPeer()) + { + peer->setConstrainer(&resizeListener); + } } ~GemJUCEWindow() override @@ -182,9 +210,6 @@ int createGemWindow(WindowInfo& info, WindowHints& hints) // As a result, on Linux, we need to make sure that only one thread has the GL context set as active, otherwise things go very wrong auto* window = new GemJUCEWindow(); - window->addToDesktop(ComponentPeer::windowHasTitleBar | ComponentPeer::windowHasDropShadow | ComponentPeer::windowIsResizable); - window->setVisible(true); - gemJUCEWindow[window->instance].reset(window); info.window[window->instance] = window; From 74304e28e447ed6930fc427f036a4d55065072d1 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 15 Jan 2024 02:05:57 +0100 Subject: [PATCH 0056/1030] Gem: fixed windows compilation --- CMakeLists.txt | 2 +- Libraries/CMakeLists.txt | 4 ++-- Libraries/Gem | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d40a2cc026..58493a8b64 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,7 +57,7 @@ if(("${CMAKE_SYSTEM_NAME}" MATCHES "Linux" AND ${CMAKE_SYSTEM_PROCESSOR} MATCHES endif() if(MSVC) # Gem is not supported on Windows yet -set(ENABLE_GEM OFF) +#set(ENABLE_GEM OFF) endif() add_subdirectory(Libraries/ EXCLUDE_FROM_ALL) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index d12b6fcab7..f8781a6df1 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -439,8 +439,8 @@ elseif(MSVC) add_library(pd SHARED $ $) add_library(pd-multi STATIC $ $) - target_link_libraries(pd PUBLIC pthreadVC3 ws2_32 ${externals_libs}) - target_link_libraries(pd-multi PUBLIC pthreadVC3 ws2_32 ${externals_libs}) + target_link_libraries(pd PUBLIC pthreadVC3 ws2_32 ${externals_libs} ${GEM_LIBS}) + target_link_libraries(pd-multi PUBLIC pthreadVC3 ws2_32 ${externals_libs} ${GEM_LIBS_MULTI}) add_custom_command(TARGET pd POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy diff --git a/Libraries/Gem b/Libraries/Gem index a1b801ed2c..45af08e790 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit a1b801ed2c5d5307496bffc4d4c62fab1378a172 +Subproject commit 45af08e7906f95df4b6849a41e379a3409210204 From dce3fb7cce88b89bb9ba98cfc099e889c31b44b9 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 15 Jan 2024 02:07:47 +0100 Subject: [PATCH 0057/1030] Gem: Make sure window appears at reasonable position --- Source/Objects/Gem.h | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 4940957771..8353fea50a 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -81,6 +81,7 @@ class GemJUCEWindow final : public Component, public Timer ); setVisible(true); + setTopLeftPosition(100, 100); if(auto* peer = getPeer()) { @@ -108,32 +109,40 @@ class GemJUCEWindow final : public Component, public Timer void mouseDown(const MouseEvent& e) override { + auto pos = getGemMousePosition(e); + setThis(); sys_lock(); - triggerButtonEvent(e.mods.isRightButtonDown(), 1, e.x, e.y); + triggerButtonEvent(e.mods.isRightButtonDown(), 1, pos.x, pos.y); sys_unlock(); } void mouseUp(const MouseEvent& e) override { + auto pos = getGemMousePosition(e); + setThis(); sys_lock(); - triggerButtonEvent(e.mods.isRightButtonDown(), 0, e.x, e.y); + triggerButtonEvent(e.mods.isRightButtonDown(), 0, pos.x, pos.y); sys_unlock(); } void mouseMove(const MouseEvent& e) override { + auto pos = getGemMousePosition(e); + setThis(); sys_lock(); - triggerMotionEvent(e.x, e.y); + triggerMotionEvent(pos.x, pos.y); sys_unlock(); } void mouseDrag(const MouseEvent& e) override { + auto pos = getGemMousePosition(e); + setThis(); sys_lock(); - triggerMotionEvent(e.x, e.y); + triggerMotionEvent(pos.x, pos.y); sys_unlock(); } @@ -178,6 +187,14 @@ class GemJUCEWindow final : public Component, public Timer libpd_set_instance(instance); } + Point getGemMousePosition(const MouseEvent& e) + { + auto w = static_cast(e.x) / getWidth(); + auto h = static_cast(e.y) / getHeight(); + return Point(w * gemWidth, h * gemHeight); + } + + int gemHeight, gemWidth; OpenGLContext openGLContext; t_pdinstance* instance; Array heldKeys; @@ -228,6 +245,15 @@ int createGemWindow(WindowInfo& info, WindowHints& hints) hints.real_w = window->getWidth(); hints.real_h = window->getHeight(); + window->gemWidth = hints.width; + window->gemHeight = hints.height; + + auto* peer = window->getPeer(); + if(peer && hints.title) + { + window->setTitle(String::fromUTF8(hints.title)); + } + // Make sure only audio thread has the context set as active // We call async here, because if this call comes from the message thread already, // we need to keep the context active until GLEW is initialised. Bit of a hack though @@ -267,7 +293,11 @@ void gemWinMakeCurrent(WindowInfo& info) { void gemWinResize(WindowInfo& info, int width, int height) { MessageManager::callAsync([window = info.getWindow(), width, height](){ - if(window) window->setSize(width, height); + if(window) { + window->setSize(width, height); + window->gemHeight = height; + window->gemWidth = width; + } }); } From 7364bf9423c8bfb9eab68255ef0f6f29d3c9c108 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 15 Jan 2024 04:43:15 +0100 Subject: [PATCH 0058/1030] Use new cleaned up Gem repo, updated Gem --- Libraries/Gem | 2 +- Source/Pd/Setup.cpp | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index 45af08e790..e4325a2ef0 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 45af08e7906f95df4b6849a41e379a3409210204 +Subproject commit e4325a2ef0c933a708213a8e007fe5c131d171af diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index ce9ae6361f..717fc2ca34 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -348,7 +348,6 @@ void pix_multitexture_setup(); void pix_noise_setup(); void pix_normalize_setup(); void pix_offset_setup(); -void pix_pix2sig_setup(); void pix_posterize_setup(); void pix_puzzle_setup(); void pix_rds_setup(); @@ -365,7 +364,6 @@ void pix_scanline_setup(); void pix_set_setup(); void pix_share_read_setup(); void pix_share_write_setup(); -void pix_sig2pix_setup(); void pix_snap_setup(); void pix_snap2tex_setup(); void pix_subtract_setup(); @@ -1874,7 +1872,6 @@ void Setup::initialiseGem() pix_noise_setup(); pix_normalize_setup(); pix_offset_setup(); - pix_pix2sig_setup(); pix_posterize_setup(); pix_puzzle_setup(); pix_rds_setup(); @@ -1891,7 +1888,6 @@ void Setup::initialiseGem() pix_set_setup(); pix_share_read_setup(); pix_share_write_setup(); - pix_sig2pix_setup(); pix_snap_setup(); pix_snap2tex_setup(); pix_subtract_setup(); From 415bf0bcfdca5bf65aee6ae5605a75237a6f532e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 15 Jan 2024 04:44:58 +0100 Subject: [PATCH 0059/1030] Windows compilation fix --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index e4325a2ef0..553e595d69 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit e4325a2ef0c933a708213a8e007fe5c131d171af +Subproject commit 553e595d69537a804043e14dfaa0b73ff738de30 From 70e140c1c5c6c94e7fdd233435a762bb05757430 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 15 Jan 2024 04:58:26 +0100 Subject: [PATCH 0060/1030] Fixed Gem startup crash --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 553e595d69..1fc49a7fe1 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 553e595d69537a804043e14dfaa0b73ff738de30 +Subproject commit 1fc49a7fe1e8febe15155561336488cf52f6fb7b From 012c1f0b5c47be1083313d23ab6c95a792bd70c4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 15 Jan 2024 05:22:18 +0100 Subject: [PATCH 0061/1030] MSVC compilation fixes --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 1fc49a7fe1..73dffff22c 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 1fc49a7fe1e8febe15155561336488cf52f6fb7b +Subproject commit 73dffff22cc35951f155553c6a4dc8d098bccb35 From 4e7281eb1b2fb416c6ee91b1a51cadc716cca075 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 15 Jan 2024 05:55:10 +0100 Subject: [PATCH 0062/1030] MSVC fix --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 73dffff22c..bd195f02c8 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 73dffff22cc35951f155553c6a4dc8d098bccb35 +Subproject commit bd195f02c88674c1a213ab5a0267e19e750184b4 From 04292669531921f236d000629af186df19e10367 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 15 Jan 2024 13:11:52 +0100 Subject: [PATCH 0063/1030] Fix Linux bugs --- Libraries/Gem | 2 +- Source/Objects/Gem.h | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index bd195f02c8..6b92ff4de6 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit bd195f02c88674c1a213ab5a0267e19e750184b4 +Subproject commit 6b92ff4de66d2c2a4d71e15e40b002b94267324f diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 8353fea50a..64475902df 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -25,6 +25,10 @@ class GemJUCEWindow final : public Component, public Timer { std::function beginResize, endResize; + GemWindowResizeListener() { + setSizeLimits (50, 50, 30000, 30000); + } + void resizeStart() { beginResize(); @@ -279,10 +283,10 @@ void initWin_sharedContext(WindowInfo& info, WindowHints& hints) { // Rendering void gemWinSwapBuffers(WindowInfo& info) { if (auto* context = info.getContext()) { - initGemWindow(); // If we don't put this here, the background doens't get filled, but there must be a better way? - context->makeActive(); context->swapBuffers(); + initGemWindow(); // If we don't put this here, the background doens't get filled, but there must be a better way? + } } void gemWinMakeCurrent(WindowInfo& info) { From 1b79b9a1430e0da8049c13f2d6f9c656876b0dd0 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 15 Jan 2024 13:22:18 +0100 Subject: [PATCH 0064/1030] Temporarily remove submodules --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 6b92ff4de6..9f38e45ff6 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 6b92ff4de66d2c2a4d71e15e40b002b94267324f +Subproject commit 9f38e45ff632e96a3c442548b616bbdad0263be1 From dbd2ba1ff16847f9d4de2ace6facdb8f493d0267 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 15 Jan 2024 13:29:45 +0100 Subject: [PATCH 0065/1030] Fixed issues with Gem repository --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 9f38e45ff6..e91874d57a 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 9f38e45ff632e96a3c442548b616bbdad0263be1 +Subproject commit e91874d57ad49cfd2028fe7f351c328b543282df From 5d17d240d0b360693c043aad330a48e7878a37e5 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 15 Jan 2024 14:43:41 +0100 Subject: [PATCH 0066/1030] Enabled vertex_model object --- Source/Pd/Setup.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index 717fc2ca34..a59001ce6c 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -1909,7 +1909,7 @@ void Setup::initialiseGem() vertex_draw_setup(); vertex_grid_setup(); vertex_info_setup(); - //vertex_model_setup(); + vertex_model_setup(); vertex_mul_setup(); vertex_offset_setup(); vertex_quad_setup(); From f8e8c92a2bf6ae01feb189a0266d28e0d182ee37 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 15 Jan 2024 15:02:53 +0100 Subject: [PATCH 0067/1030] Revert last commit --- Source/Pd/Setup.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index a59001ce6c..a3c269e996 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -385,7 +385,7 @@ void vertex_combine_setup(); void vertex_draw_setup(); void vertex_grid_setup(); void vertex_info_setup(); -void vertex_model_setup(); +//void vertex_model_setup(); void vertex_mul_setup(); void vertex_offset_setup(); void vertex_quad_setup(); @@ -1909,7 +1909,7 @@ void Setup::initialiseGem() vertex_draw_setup(); vertex_grid_setup(); vertex_info_setup(); - vertex_model_setup(); + //vertex_model_setup(); vertex_mul_setup(); vertex_offset_setup(); vertex_quad_setup(); From 092d5b19b1d2b75267985625af33db73b29bc32c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 15 Jan 2024 16:02:36 +0100 Subject: [PATCH 0068/1030] Simplify windowing implementation, remove some scaling stuff --- Source/Objects/Gem.h | 49 +++++++++++++++----------------------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 64475902df..fd32c7090d 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -4,10 +4,11 @@ // WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ +#if ENABLE_GEM + #include #include -#if ENABLE_GEM void triggerMotionEvent(int x, int y); void triggerButtonEvent(int which, int state, int x, int y); void triggerWheelEvent(int axis, int value); @@ -44,7 +45,7 @@ class GemJUCEWindow final : public Component, public Timer public: //============================================================================== - GemJUCEWindow() + GemJUCEWindow(int width, int height) : gemWidth(width), gemHeight(height) { instance = libpd_this_instance(); @@ -63,8 +64,8 @@ class GemJUCEWindow final : public Component, public Timer sys_unlock(); }; - setSize (800, 600); - + setSize(width, height); + setOpaque (true); openGLContext.setSwapInterval(0); openGLContext.setMultisamplingEnabled(true); @@ -89,6 +90,9 @@ class GemJUCEWindow final : public Component, public Timer if(auto* peer = getPeer()) { + // Attach the constrainer to the peer + // Constrainer doesn't actually contrain, it's just the simplest way to get a callback if the native window is being resized + // The reason for this is that rendering while resizing causes issues, especially on macOS peer->setConstrainer(&resizeListener); } } @@ -99,11 +103,9 @@ class GemJUCEWindow final : public Component, public Timer void resized() override { - auto scale = Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale; - setThis(); sys_lock(); - triggerResizeEvent(getWidth() * scale, getHeight() * scale); + triggerResizeEvent(getWidth(), getHeight()); sys_unlock(); } @@ -113,40 +115,32 @@ class GemJUCEWindow final : public Component, public Timer void mouseDown(const MouseEvent& e) override { - auto pos = getGemMousePosition(e); - setThis(); sys_lock(); - triggerButtonEvent(e.mods.isRightButtonDown(), 1, pos.x, pos.y); + triggerButtonEvent(e.mods.isRightButtonDown(), 1, e.x, e.y); sys_unlock(); } void mouseUp(const MouseEvent& e) override { - auto pos = getGemMousePosition(e); - setThis(); sys_lock(); - triggerButtonEvent(e.mods.isRightButtonDown(), 0, pos.x, pos.y); + triggerButtonEvent(e.mods.isRightButtonDown(), 0, e.x, e.y); sys_unlock(); } void mouseMove(const MouseEvent& e) override { - auto pos = getGemMousePosition(e); - setThis(); sys_lock(); - triggerMotionEvent(pos.x, pos.y); + triggerMotionEvent(e.x, e.y); sys_unlock(); } void mouseDrag(const MouseEvent& e) override { - auto pos = getGemMousePosition(e); - setThis(); sys_lock(); - triggerMotionEvent(pos.x, pos.y); + triggerMotionEvent(e.x, e.y); sys_unlock(); } @@ -191,14 +185,8 @@ class GemJUCEWindow final : public Component, public Timer libpd_set_instance(instance); } - Point getGemMousePosition(const MouseEvent& e) - { - auto w = static_cast(e.x) / getWidth(); - auto h = static_cast(e.y) / getHeight(); - return Point(w * gemWidth, h * gemHeight); - } - int gemHeight, gemWidth; + int gemWidth, gemHeight; OpenGLContext openGLContext; t_pdinstance* instance; Array heldKeys; @@ -230,7 +218,7 @@ int createGemWindow(WindowInfo& info, WindowHints& hints) // Problem: this call can either come from the message thread, or the pd/audio thread // As a result, on Linux, we need to make sure that only one thread has the GL context set as active, otherwise things go very wrong - auto* window = new GemJUCEWindow(); + auto* window = new GemJUCEWindow(hints.width, hints.height); gemJUCEWindow[window->instance].reset(window); info.window[window->instance] = window; @@ -245,13 +233,10 @@ int createGemWindow(WindowInfo& info, WindowHints& hints) window->openGLContext.makeActive(); info.context[window->instance] = &window->openGLContext; - + hints.real_w = window->getWidth(); hints.real_h = window->getHeight(); - - window->gemWidth = hints.width; - window->gemHeight = hints.height; - + auto* peer = window->getPeer(); if(peer && hints.title) { From b4c88c237ab3ce96e82347cc6353338be93374d1 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 17 Jan 2024 18:11:02 +0100 Subject: [PATCH 0069/1030] iOS: Fixed issues with saving/loading patches, fixed confusing save dialog label --- Libraries/JUCE | 2 +- Source/Dialogs/Dialogs.cpp | 8 ++++++++ Source/Pd/Patch.cpp | 8 ++++++++ Source/Sidebar/Palettes.h | 2 +- Source/Utility/OSUtils.h | 18 ++++++++++++++++++ Source/Utility/OSUtils.mm | 30 ++++++++++++++++++++++++++++++ 6 files changed, 66 insertions(+), 2 deletions(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index 3fa91012aa..29a4b04894 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit 3fa91012aa5c1c65933ba8fa90984c0c5df293e0 +Subproject commit 29a4b04894436ef4eceb2fd5bd070c86b307e510 diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index 524f416d97..65f46fa65b 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -688,6 +688,10 @@ void Dialogs::showOpenDialog(std::function callback, bool canSelect fileChooser->launchAsync(openChooserFlags, [callback, lastFileId](FileChooser const& fileChooser) { auto result = fileChooser.getResult(); + +#if JUCE_IOS + OSUtils::iOSScopedResourceAccess scopedSecurityResource(result); +#endif auto lastDir = result.isDirectory() ? result : result.getParentDirectory(); SettingsFile::getInstance()->setLastBrowserPathForId(lastFileId, lastDir); callback(result); @@ -718,11 +722,15 @@ void Dialogs::showSaveDialog(std::function callback, String const& fileChooser->launchAsync(saveChooserFlags, [callback, lastFileId](FileChooser const& fileChooser) { auto result = fileChooser.getResult(); +#if JUCE_IOS + OSUtils::iOSScopedResourceAccess scopedSecurityResource(result); +#endif auto parentDirectory = result.getParentDirectory(); if (parentDirectory.exists()) { SettingsFile::getInstance()->setLastBrowserPathForId(lastFileId, parentDirectory); callback(result); Dialogs::fileChooser = nullptr; } + }); } diff --git a/Source/Pd/Patch.cpp b/Source/Pd/Patch.cpp index fc0e3b309e..020d0ac261 100644 --- a/Source/Pd/Patch.cpp +++ b/Source/Pd/Patch.cpp @@ -99,6 +99,10 @@ void Patch::savePatch(File const& location) setTitle(filename); untitledPatchNum = 0; canvas_dirty(patch.get(), 0); + +#if JUCE_IOS + OSUtils::iOSScopedResourceAccess scopedSecurityResource(location); +#endif pd::Interface::saveToFile(patch.get(), file, dir); @@ -164,6 +168,10 @@ void Patch::savePatch() untitledPatchNum = 0; canvas_dirty(patch.get(), 0); +#if JUCE_IOS + OSUtils::iOSScopedResourceAccess scopedSecurityResource(currentFile); +#endif + pd::Interface::saveToFile(patch.get(), file, dir); } diff --git a/Source/Sidebar/Palettes.h b/Source/Sidebar/Palettes.h index d6c4d7cd20..6843c0c1fe 100644 --- a/Source/Sidebar/Palettes.h +++ b/Source/Sidebar/Palettes.h @@ -719,7 +719,7 @@ class Palettes : public Component } void savePalettes() - { + { palettesFile.replaceWithText(palettesTree.toXmlString()); } diff --git a/Source/Utility/OSUtils.h b/Source/Utility/OSUtils.h index d74df25aa6..a61197e82f 100644 --- a/Source/Utility/OSUtils.h +++ b/Source/Utility/OSUtils.h @@ -87,5 +87,23 @@ struct OSUtils { static void showMobileMainMenu(juce::ComponentPeer* peer, std::function callback); static void showMobileCanvasMenu(juce::ComponentPeer* peer, std::function callback); + static void startAccessingSecurityScopedResource(juce::File file); + static void stopAccessingSecurityScopedResource(juce::File file); + + struct iOSScopedResourceAccess + { + iOSScopedResourceAccess(const juce::File& file) : scopedAccessFile(file) + { + startAccessingSecurityScopedResource(scopedAccessFile); + } + + ~iOSScopedResourceAccess() + { + stopAccessingSecurityScopedResource(scopedAccessFile); + } + + juce::File scopedAccessFile; + }; + #endif }; diff --git a/Source/Utility/OSUtils.mm b/Source/Utility/OSUtils.mm index 770bd653cc..f235ddba98 100644 --- a/Source/Utility/OSUtils.mm +++ b/Source/Utility/OSUtils.mm @@ -279,6 +279,36 @@ - (void)scrollEventOccurred:(UIPanGestureRecognizer*)gesture { return [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad; } +void OSUtils::startAccessingSecurityScopedResource(juce::File file) +{ + // Convert JUCE File to NSString + NSString* filePath = [NSString stringWithUTF8String:file.getFullPathName().toRawUTF8()]; + + // Create NSURL from file path + NSURL* fileURL = [NSURL fileURLWithPath:filePath]; + + // Start accessing security-scoped resource + NSError* error = nil; + BOOL success = [fileURL startAccessingSecurityScopedResource]; + + if (!success) { + // Handle error if needed + NSLog(@"Error starting access to security-scoped resource: %@", error); + } +} + +void OSUtils::stopAccessingSecurityScopedResource(juce::File file) +{ + // Convert JUCE File to NSString + NSString* filePath = [NSString stringWithUTF8String:file.getFullPathName().toRawUTF8()]; + + // Create NSURL from file path + NSURL* fileURL = [NSURL fileURLWithPath:filePath]; + + // Stop accessing security-scoped resource + [fileURL stopAccessingSecurityScopedResource]; +} + void OSUtils::showMobileMainMenu(juce::ComponentPeer* peer, std::function callback) { auto* view = (UIView*)peer->getNativeHandle(); From 54266ebda54ed2ac3b790a80253b8b72c0ea486a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 18 Jan 2024 11:22:58 +0100 Subject: [PATCH 0070/1030] Enabled sfz again, whoops --- Source/Pd/Setup.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index ae95b64665..a0a815d731 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -671,7 +671,7 @@ void conv_tilde_setup(); void fm_tilde_setup(); #ifdef ENABLE_SFIZZ -// void sfz_tilde_setup(); + void sfz_tilde_setup(); #endif void knob_setup(); @@ -1113,7 +1113,7 @@ void Setup::initialiseELSE() setup_xgate0x2emc_tilde(); setup_xfade0x2emc_tilde(); #ifdef ENABLE_SFIZZ - // sfz_tilde_setup(); + sfz_tilde_setup(); #endif sender_setup(); setup_ptouch0x2ein(); From b4a8022e3e60a91389697cf790315ceab299ec6d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 19 Jan 2024 02:27:14 +0100 Subject: [PATCH 0071/1030] Try using faster github actions runner --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 173b624aef..594e2bbf22 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -9,7 +9,7 @@ env: jobs: macos-universal-build: - runs-on: macos-latest + runs-on: macos-latest-xlarge steps: - uses: actions/checkout@v3 From a36114fd926e7bf283cc786cb7fa68c5174cc995 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 19 Jan 2024 02:28:15 +0100 Subject: [PATCH 0072/1030] Try different macOS runner --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 594e2bbf22..869cdb4c14 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -9,7 +9,7 @@ env: jobs: macos-universal-build: - runs-on: macos-latest-xlarge + runs-on: macos-latest-large steps: - uses: actions/checkout@v3 From 7d61239a5e6af38f6e59171a04833ff89e16c315 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 19 Jan 2024 02:28:45 +0100 Subject: [PATCH 0073/1030] Revert last 2 commits --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 869cdb4c14..173b624aef 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -9,7 +9,7 @@ env: jobs: macos-universal-build: - runs-on: macos-latest-large + runs-on: macos-latest steps: - uses: actions/checkout@v3 From 29ba6c53ac1f3e9402466c6c849dbce0c62f0c4a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 19 Jan 2024 02:30:33 +0100 Subject: [PATCH 0074/1030] macOS build fix --- .github/workflows/cmake.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 173b624aef..10bcdb8d9a 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -23,8 +23,8 @@ jobs: key: macos - name: Install Gem plugin dependencies - run: ${{github.workspace}}/Libraries/Gem/.git-ci/travis-ci/depinstall-osx.sh - working-directory: ${{github.workspace}}/Libraries/Gem + run: ./depinstall-osx.sh + working-directory: ${{github.workspace}}/Libraries/Gem/.git-ci/travis-ci - name: Create Build Environment run: cmake -E make_directory ${{github.workspace}}/build From 9e7a6c80183d5bd650133a6d1202c2627efb6c64 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 19 Jan 2024 02:36:14 +0100 Subject: [PATCH 0075/1030] CI build fix --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 5fa1feb5d2..2739e8a059 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 5fa1feb5d2ceb7256f358cd3d6040942b3ad7189 +Subproject commit 2739e8a0594efdf3034b0a97452dbdaea03f3077 From 918102beaa2a3b2b30bd015786138a24cb3b745c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 19 Jan 2024 03:01:05 +0100 Subject: [PATCH 0076/1030] Don't try to build gem plugins, compilation fix --- CMakeLists.txt | 6 ------ Libraries/Gem | 2 +- Source/PluginProcessor.cpp | 2 -- Source/Standalone/PlugDataApp.cpp | 3 ++- 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d11d39beb..baf9fdf630 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,8 +11,6 @@ option(ENABLE_GEM "" ON) option(ENABLE_ASAN "" OFF) option(VERBOSE "" OFF) -option(BUILD_GEM_PLUGINS "" OFF) - set (CMAKE_CXX_STANDARD 20) # Visiblity needs to be hidden for all plugin targets, otherwise loading both plugdata and plugdata-fx will cause problems. We later undo this for the standalone build, so that externals can load @@ -222,10 +220,6 @@ if(ENABLE_GEM) list(APPEND PLUGDATA_COMPILE_DEFINITIONS ENABLE_GEM=1) endif() -if(BUILD_GEM_PLUGINS) - list(APPEND PLUGDATA_COMPILE_DEFINITIONS EXTRACT_GEM_PLUGINS=1) -endif() - add_library(juce STATIC) target_compile_definitions(juce PUBLIC diff --git a/Libraries/Gem b/Libraries/Gem index 2739e8a059..4021230a7e 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 2739e8a0594efdf3034b0a97452dbdaea03f3077 +Subproject commit 4021230a7e7246e3cbc7ca86ea207f6f62a4874d diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index b59e6eb400..9481e81e69 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -22,8 +22,6 @@ #include "Utility/MidiDeviceManager.h" #include "Dialogs/ConnectionMessageDisplay.h" -#include "Objects/Gem.h" - #include "Utility/Presets.h" #include "Canvas.h" #include "PluginMode.h" diff --git a/Source/Standalone/PlugDataApp.cpp b/Source/Standalone/PlugDataApp.cpp index bd25c1b64a..230aed475e 100644 --- a/Source/Standalone/PlugDataApp.cpp +++ b/Source/Standalone/PlugDataApp.cpp @@ -97,10 +97,11 @@ class PlugDataApp : public JUCEApplication { } // Open file callback on iOS + /* bool urlOpened(URL& url) override { anotherInstanceStarted(url.toString(false)); return true; - } + } */ void initialise(String const& arguments) override { From ad6974e80b002a747ea9defcea6b35d65ba3aaf1 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 19 Jan 2024 03:02:05 +0100 Subject: [PATCH 0077/1030] Compilation fixes --- Source/Objects/Gem.h | 11 ----------- Source/PluginProcessor.cpp | 1 - 2 files changed, 12 deletions(-) diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 97b4df7748..d105f13d3d 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -9,10 +9,6 @@ #include #include -#if EXTRACT_GEM_PLUGINS -#include -#endif - void triggerMotionEvent(int x, int y); void triggerButtonEvent(int which, int state, int x, int y); void triggerWheelEvent(int axis, int value); @@ -210,13 +206,6 @@ void GemCallOnMessageThread(std::function callback) } -void extractGemPlugins(File path) -{ -#if EXTRACT_GEM_PLUGINS - GemPlugins::extractZipFile(path.getFullPathName().toStdString()); -#endif -} - // window/context creation&destruction bool initGemWin(void) { diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 9481e81e69..fcb5e639ad 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -255,7 +255,6 @@ void PluginProcessor::initialiseFilesystem() versionDataDir.getChildFile("Extra").createSymbolicLink(homeDir.getChildFile("Extra"), true); #endif - extractGemPlugins(versionDataDir.getChildFile("Extra").getChildFile("Gem")); internalSynth->extractSoundfont(); } From 93bf9a71d7b0cfb032f25a56108868c1d509e880 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sat, 20 Jan 2024 18:05:13 +1030 Subject: [PATCH 0078/1030] Use same spelling for "centre_resized_canvas" so the setting works (UK vs USA English) --- Source/CanvasViewport.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/CanvasViewport.h b/Source/CanvasViewport.h index a30c10508e..52a52c6db4 100644 --- a/Source/CanvasViewport.h +++ b/Source/CanvasViewport.h @@ -395,7 +395,7 @@ class CanvasViewport : public Viewport { adjustScrollbarBounds(); - if (!SettingsFile::getInstance()->getProperty("center_resized_canvas")) { + if (!SettingsFile::getInstance()->getProperty("centre_resized_canvas")) { Viewport::resized(); return; } From 714278d1aef3122f30838accd4140d2b64795d8a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Jan 2024 15:05:46 +0100 Subject: [PATCH 0079/1030] Unpack Gem plugins --- Libraries/Gem | 2 +- Resources/Scripts/package_resources.py | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index 4021230a7e..e04532f583 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 4021230a7e7246e3cbc7ca86ea207f6f62a4874d +Subproject commit e04532f583f5938c314abde858855dbb13eaba73 diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index 79e76beb32..fab35b62da 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -2,6 +2,8 @@ import os import glob import sys +import platform +import zipfile # Utility filesystem functions def removeFile(path): @@ -133,6 +135,28 @@ def splitFile(file, num_files): makeDir("Extra/Gem") # user can put plugins and resources in here globCopy("../../Libraries/Gem/examples/data/*", "Extra/Gem/") # if we don't do this, helpfiles can't find resources for some reason +# extract precompiled Gem plugins for our architecture +system = platform.system().lower() +architecture = platform.architecture()[0] +gem_plugin_path = "../../Libraries/Gem/" +gem_plugins_file = "" + +if system == 'linux': + if 'arm' in architecture: + gem_plugins_file = 'plugins_linux_arm64' + elif '64' in architecture: + gem_plugins_file = 'plugins_linux_x64' +elif system == 'darwin': + gem_plugins_file = 'plugins_macos' +elif system == 'windows' and '64' in architecture: + gem_plugins_file = 'plugins_win64' + +# unpack if architecture is supported +if len(gem_plugins_file) != 0: + with zipfile.ZipFile(gem_plugin_path + gem_plugins_file + ".zip", 'r') as zip_ref: + zip_ref.extractall("Extra/Gem/") + globCopy("Extra/Gem/" + gem_plugins_file + "/*", "Extra/Gem/") + removeDir("Extra/Gem/" + gem_plugins_file) changeWorkingDir("./..") @@ -142,4 +166,4 @@ def splitFile(file, num_files): splitFile("./Fonts/InterUnicode.ttf", 12) splitFile("./Filesystem.zip", 12) -removeFile("./Filesystem.zip") +#removeFile("./Filesystem.zip") From cf1fda9b8c2ac09553a186a6cad9f7d6bbfce6d8 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Jan 2024 15:06:00 +0100 Subject: [PATCH 0080/1030] Fixed Gem bugs --- Source/Objects/Gem.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index d105f13d3d..71dc185c6c 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -269,7 +269,6 @@ void gemWinSwapBuffers(WindowInfo& info) { context->makeActive(); context->swapBuffers(); initGemWindow(); // If we don't put this here, the background doens't get filled, but there must be a better way? - } } void gemWinMakeCurrent(WindowInfo& info) { @@ -279,11 +278,11 @@ void gemWinMakeCurrent(WindowInfo& info) { } void gemWinResize(WindowInfo& info, int width, int height) { - MessageManager::callAsync([window = info.getWindow(), width, height](){ - if(window) { - window->setSize(width, height); - window->gemHeight = height; - window->gemWidth = width; + MessageManager::callAsync([window = Component::SafePointer(info.getWindow()), width, height](){ + if(auto* w = window.getComponent()) { + w->setSize(width, height); + w->gemHeight = height; + w->gemWidth = width; } }); } From dd611e67141e4590e76f48a18d94b1e861b6651f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Jan 2024 15:06:09 +0100 Subject: [PATCH 0081/1030] Version bump --- .github/scripts/package-Windows.sh | 2 +- CMakeLists.txt | 4 ++-- Source/Utility/Config.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/scripts/package-Windows.sh b/.github/scripts/package-Windows.sh index e4a89d3384..16d64d4468 100644 --- a/.github/scripts/package-Windows.sh +++ b/.github/scripts/package-Windows.sh @@ -4,7 +4,7 @@ if [[ $1 == "x64" ]]; then X64BitMode="x64" fi -VERSION=0.8.3 +VERSION=0.8.4 rm -f ./plugdata.wxs cat > ./plugdata.wxs <<-EOL diff --git a/CMakeLists.txt b/CMakeLists.txt index baf9fdf630..0b1d1ff8a8 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,7 @@ set(HARDENED_RUNTIME_OPTIONS "com.apple.security.device.audio-input") set(JUCE_ENABLE_MODULE_SOURCE_GROUPS ON CACHE BOOL "" FORCE) set_property(GLOBAL PROPERTY USE_FOLDERS YES) -project(plugdata VERSION 0.8.3 LANGUAGES C CXX) +project(plugdata VERSION 0.8.4 LANGUAGES C CXX) if(("${CMAKE_SYSTEM_NAME}" MATCHES "Linux" AND ${CMAKE_SYSTEM_PROCESSOR} MATCHES "aarch64") OR ("${CMAKE_SYSTEM_NAME}" MATCHES "iOS")) @@ -90,7 +90,7 @@ message(STATUS "Packaging resources") execute_process(COMMAND python3 package_resources.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Scripts) -set(PLUGDATA_VERSION "0.8.3") +set(PLUGDATA_VERSION "0.8.4") set(PLUGDATA_COMPANY_NAME "plugdata") set(PLUGDATA_COMPANY_COPYRIGHT "plugdata by Timothy Schoen") set(PLUGDATA_COMPANY_WEBSITE "github.com/plugdata-team/plugdata") diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index 89c8036c81..90b9134e81 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -38,7 +38,7 @@ struct ProjectInfo { static inline File const appDataDir = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory).getChildFile("plugdata"); - static inline String const versionSuffix = "-gem"; + static inline String const versionSuffix = "-1"; static inline File const versionDataDir = appDataDir.getChildFile("Versions").getChildFile(ProjectInfo::versionString + versionSuffix); }; From a7a5be29ef6c99f12fd280549f9530c8c51e2606 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Jan 2024 15:48:26 +0100 Subject: [PATCH 0082/1030] Fixed resource unpacking problem on arm --- Resources/Scripts/package_resources.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index fab35b62da..cedc2a06c8 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -137,12 +137,14 @@ def splitFile(file, num_files): # extract precompiled Gem plugins for our architecture system = platform.system().lower() -architecture = platform.architecture()[0] +architecture = platform.architecture() +machine = platform.machine() + gem_plugin_path = "../../Libraries/Gem/" gem_plugins_file = "" if system == 'linux': - if 'arm' in architecture: + if 'arm' in machine: gem_plugins_file = 'plugins_linux_arm64' elif '64' in architecture: gem_plugins_file = 'plugins_linux_x64' @@ -166,4 +168,4 @@ def splitFile(file, num_files): splitFile("./Fonts/InterUnicode.ttf", 12) splitFile("./Filesystem.zip", 12) -#removeFile("./Filesystem.zip") +removeFile("./Filesystem.zip") From 247e6182cdd9cab3f338e42991b213b7ab5d9d43 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Jan 2024 16:03:07 +0100 Subject: [PATCH 0083/1030] Update pre-packaged Gem plugins --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index e04532f583..98e1443697 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit e04532f583f5938c314abde858855dbb13eaba73 +Subproject commit 98e14436978d310ca7813e83459ce21b58c8859b From bc1dac5cf1f5378b4a73f3635b4ce89bc421bd27 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Jan 2024 16:14:04 +0100 Subject: [PATCH 0084/1030] Gem plugins for linux fix --- Resources/Scripts/package_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index cedc2a06c8..835956a256 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -144,7 +144,7 @@ def splitFile(file, num_files): gem_plugins_file = "" if system == 'linux': - if 'arm' in machine: + if 'aarch64' in machine or 'arm' in machine: gem_plugins_file = 'plugins_linux_arm64' elif '64' in architecture: gem_plugins_file = 'plugins_linux_x64' From d7533433be2e20f1a3d80d178f0a9b9a3d65d659 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Jan 2024 16:41:08 +0100 Subject: [PATCH 0085/1030] Updated gem plugins for Linux --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 98e1443697..8dc1cf2bbb 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 98e14436978d310ca7813e83459ce21b58c8859b +Subproject commit 8dc1cf2bbba06511f2dddf7583bded66cddf4ade From 613384f2db525b814544610eba4aed5310b92570 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Jan 2024 16:56:31 +0100 Subject: [PATCH 0086/1030] Fix Gem plugin extraction on Windows --- Resources/Scripts/package_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index 835956a256..5193d35be5 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -150,7 +150,7 @@ def splitFile(file, num_files): gem_plugins_file = 'plugins_linux_x64' elif system == 'darwin': gem_plugins_file = 'plugins_macos' -elif system == 'windows' and '64' in architecture: +elif system == 'windows' and '64' in architecture[0]: gem_plugins_file = 'plugins_win64' # unpack if architecture is supported From ac56523e7c9b588c275d1bf56a46db8bad476a40 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Jan 2024 16:58:26 +0100 Subject: [PATCH 0087/1030] Update Windows plugins --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 8dc1cf2bbb..a7a605ff97 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 8dc1cf2bbba06511f2dddf7583bded66cddf4ade +Subproject commit a7a605ff97c1bf24deabab9400205ee19e209da7 From b37634346783ef5cb620597685f38740632cd490 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Jan 2024 18:12:48 +0100 Subject: [PATCH 0088/1030] Gem fixes for macOS --- Libraries/Gem | 2 +- Source/Objects/Gem.h | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index a7a605ff97..592f88011c 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit a7a605ff97c1bf24deabab9400205ee19e209da7 +Subproject commit 592f88011cd283079aaeae8751643fca8f91809d diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 71dc185c6c..467674be81 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -104,9 +104,9 @@ class GemJUCEWindow final : public Component, public Timer void resized() override { setThis(); - sys_lock(); - triggerResizeEvent(getWidth(), getHeight()); - sys_unlock(); + //sys_lock(); + //triggerResizeEvent(getWidth(), getHeight()); + //sys_unlock(); } void paint (Graphics&) override @@ -196,7 +196,6 @@ class GemJUCEWindow final : public Component, public Timer void GemCallOnMessageThread(std::function callback) { - //MessageManager::callAsync(callback); MessageManager::getInstance()->callFunctionOnMessageThread([](void* callback) -> void* { auto& fn = *reinterpret_cast*>(callback); fn(); From f9bb3a23f50db45b583e0ad46ac7f6814de80756 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Jan 2024 13:47:19 +0100 Subject: [PATCH 0089/1030] Gem fix for macOS --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 592f88011c..6231861082 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 592f88011cd283079aaeae8751643fca8f91809d +Subproject commit 62318610826b2a4add7a9c53c972a9e3aca6f230 From 0cd0fa2a7ac4bc2755e333873b132a321c1091b1 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Jan 2024 15:09:21 +0100 Subject: [PATCH 0090/1030] Gem dependencies fix --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 6231861082..3902c1f9f4 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 62318610826b2a4add7a9c53c972a9e3aca6f230 +Subproject commit 3902c1f9f49fbe549f7ca7d6895510f11949bca9 From f2dc4e554e760ea5b512c4e95b3cc79b6b0bb38b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Jan 2024 15:44:51 +0100 Subject: [PATCH 0091/1030] Freetype build fixes --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 3902c1f9f4..967f7fadbf 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 3902c1f9f49fbe549f7ca7d6895510f11949bca9 +Subproject commit 967f7fadbf197deab743a34ba1fa1def8d0c0a04 From bb377a98d003def1c65b09acb62c26d37aa03cd4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Jan 2024 18:17:49 +0100 Subject: [PATCH 0092/1030] Gem macOS plugin fixes --- Libraries/Gem | 2 +- Source/Objects/Gem.h | 31 ++++++++++++++++++------------- Source/PluginProcessor.cpp | 5 +++-- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index 967f7fadbf..ccbc93d850 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 967f7fadbf197deab743a34ba1fa1def8d0c0a04 +Subproject commit ccbc93d850c4496c310d07cf4e2f437b694e0bec diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 467674be81..d40dd77850 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -104,6 +104,7 @@ class GemJUCEWindow final : public Component, public Timer void resized() override { setThis(); + // This does nothing at the moment, but does cause issues //sys_lock(); //triggerResizeEvent(getWidth(), getHeight()); //sys_unlock(); @@ -204,15 +205,29 @@ void GemCallOnMessageThread(std::function callback) }, (void*)&callback); } +std::map> gemJUCEWindow; + +bool gemWinSetCurrent() { + if(!gemJUCEWindow.contains(libpd_this_instance())) return false; + + if(auto& window = gemJUCEWindow.at(libpd_this_instance())) { + window->openGLContext.makeActive(); + return true; + } + + return false; +} + +void gemWinUnsetCurrent() { + OpenGLContext::deactivateCurrentContext(); +} // window/context creation&destruction bool initGemWin(void) { - return true; + return true; } -std::map> gemJUCEWindow; - int createGemWindow(WindowInfo& info, WindowHints& hints) { auto* window = new GemJUCEWindow(hints.width, hints.height); @@ -286,16 +301,6 @@ void gemWinResize(WindowInfo& info, int width, int height) { }); } -bool gemWinMakeCurrent() { - if(!gemJUCEWindow.contains(libpd_this_instance())) return false; - - if(auto& window = gemJUCEWindow.at(libpd_this_instance())) { - window->openGLContext.makeActive(); - return true; - } - - return false; -} // Window behaviour int cursorGemWindow(WindowInfo& info, int state) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index fcb5e639ad..587ff21ae4 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -39,6 +39,9 @@ extern "C" { EXTERN char* pd_version; } +bool gemWinSetCurrent(); +bool gemWinUnsetCurrent(); + AudioProcessor::BusesProperties PluginProcessor::buildBusesProperties() { #if JUCE_IOS @@ -1220,7 +1223,6 @@ pd::Patch::Ptr PluginProcessor::loadPatch(File const& patchFile, PluginEditor* e // TODO: why though? lockAudioThread(); - #if JUCE_IOS auto tempFile = File::createTempFile(".pd"); tempFile.replaceWithText(patchFile.loadFileAsString()); @@ -1241,7 +1243,6 @@ pd::Patch::Ptr PluginProcessor::loadPatch(File const& patchFile, PluginEditor* e #else auto newPatch = openPatch(patchFile); #endif - unlockAudioThread(); if (!newPatch->getPointer()) { From 4abd6657d8917428834ba91a92005d2ec2629754 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Jan 2024 21:08:39 +0100 Subject: [PATCH 0093/1030] Gem stability improvements --- Libraries/Gem | 2 +- Source/Objects/Gem.h | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index ccbc93d850..e23134ce26 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit ccbc93d850c4496c310d07cf4e2f437b694e0bec +Subproject commit e23134ce26e837e3d78c6d7871e8399b3db12627 diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index d40dd77850..01b21c4028 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -267,6 +267,8 @@ int createGemWindow(WindowInfo& info, WindowHints& hints) void destroyGemWindow(WindowInfo& info) { if(auto* window = info.getWindow()) { window->removeFromDesktop(); + info.window.erase(window->instance); + info.context.erase(window->instance); gemJUCEWindow[window->instance].reset(nullptr); } } @@ -292,13 +294,15 @@ void gemWinMakeCurrent(WindowInfo& info) { } void gemWinResize(WindowInfo& info, int width, int height) { - MessageManager::callAsync([window = Component::SafePointer(info.getWindow()), width, height](){ - if(auto* w = window.getComponent()) { - w->setSize(width, height); - w->gemHeight = height; - w->gemWidth = width; - } - }); + if(auto* windowPtr = info.getWindow()) { + MessageManager::callAsync([window = Component::SafePointer(info.getWindow()), width, height](){ + if(auto* w = window.getComponent()) { + w->setSize(width, height); + w->gemHeight = height; + w->gemWidth = width; + } + }); + } } From 6661e91d963cc9bbb222f2c51dfe262d9dbe0d83 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Jan 2024 21:11:23 +0100 Subject: [PATCH 0094/1030] Fixed resize issue --- Source/Objects/Gem.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 01b21c4028..88ca237754 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -104,10 +104,11 @@ class GemJUCEWindow final : public Component, public Timer void resized() override { setThis(); - // This does nothing at the moment, but does cause issues - //sys_lock(); - //triggerResizeEvent(getWidth(), getHeight()); - //sys_unlock(); + + // TODO: this sometimes causes a deadlock + sys_lock(); + triggerResizeEvent(getWidth(), getHeight()); + sys_unlock(); } void paint (Graphics&) override From 5171b0ce4283f64d987b2c8097e8914a54824af4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Jan 2024 21:27:05 +0100 Subject: [PATCH 0095/1030] Build system fix, log gem version number --- Resources/Scripts/package_resources.py | 2 +- Source/PluginProcessor.cpp | 2 ++ Source/PluginProcessor.h | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index 5193d35be5..0a2851f539 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -157,7 +157,7 @@ def splitFile(file, num_files): if len(gem_plugins_file) != 0: with zipfile.ZipFile(gem_plugin_path + gem_plugins_file + ".zip", 'r') as zip_ref: zip_ref.extractall("Extra/Gem/") - globCopy("Extra/Gem/" + gem_plugins_file + "/*", "Extra/Gem/") + globMove("Extra/Gem/" + gem_plugins_file + "/*", "Extra/Gem/") removeDir("Extra/Gem/" + gem_plugins_file) changeWorkingDir("./..") diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 587ff21ae4..85bea35400 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -123,7 +123,9 @@ PluginProcessor::PluginProcessor() logMessage("Libraries:"); logMessage(else_version); logMessage(cyclone_version); + logMessage(gem_version); logMessage(heavylib_version); + // Set up midi buffers midiBufferIn.ensureSize(2048); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 49b76509c2..e93cdb4d11 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -208,6 +208,7 @@ class PluginProcessor : public AudioProcessor static inline String const else_version = "ELSE v1.0-rc10"; static inline String const cyclone_version = "cyclone v0.8-0"; static inline String const heavylib_version = "heavylib v0.3.1"; + static inline String const gem_version = "Gem v0.94"; // this gets updated with live version data later static String pdlua_version; From c8f06deca3c1b546df0857392e4673c69c93f7a0 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Jan 2024 21:29:26 +0100 Subject: [PATCH 0096/1030] Small bugfix --- Source/Objects/Gem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 88ca237754..7c09c9aeda 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -296,7 +296,7 @@ void gemWinMakeCurrent(WindowInfo& info) { void gemWinResize(WindowInfo& info, int width, int height) { if(auto* windowPtr = info.getWindow()) { - MessageManager::callAsync([window = Component::SafePointer(info.getWindow()), width, height](){ + MessageManager::callAsync([window = Component::SafePointer(windowPtr), width, height](){ if(auto* w = window.getComponent()) { w->setSize(width, height); w->gemHeight = height; From 666da88ad93105171453bb411766e6b451ffa68a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Jan 2024 22:14:35 +0100 Subject: [PATCH 0097/1030] Gem thread-safety fixes --- Libraries/Gem | 2 +- Source/Objects/Gem.h | 26 ++++++-------------------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index e23134ce26..163b8d37f8 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit e23134ce26e837e3d78c6d7871e8399b3db12627 +Subproject commit 163b8d37f8ec0844ae2246d71f77ab1ef1d474f3 diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 7c09c9aeda..f1cb73e56b 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -104,11 +104,7 @@ class GemJUCEWindow final : public Component, public Timer void resized() override { setThis(); - - // TODO: this sometimes causes a deadlock - sys_lock(); triggerResizeEvent(getWidth(), getHeight()); - sys_unlock(); } void paint (Graphics&) override @@ -118,47 +114,35 @@ class GemJUCEWindow final : public Component, public Timer void mouseDown(const MouseEvent& e) override { setThis(); - sys_lock(); triggerButtonEvent(e.mods.isRightButtonDown(), 1, e.x, e.y); - sys_unlock(); } void mouseUp(const MouseEvent& e) override { setThis(); - sys_lock(); triggerButtonEvent(e.mods.isRightButtonDown(), 0, e.x, e.y); - sys_unlock(); } void mouseMove(const MouseEvent& e) override { setThis(); - sys_lock(); triggerMotionEvent(e.x, e.y); - sys_unlock(); } void mouseDrag(const MouseEvent& e) override { setThis(); - sys_lock(); triggerMotionEvent(e.x, e.y); - sys_unlock(); } void mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) override { setThis(); - sys_lock(); triggerWheelEvent(wheel.deltaX, wheel.deltaY); - sys_unlock(); } bool keyPressed(KeyPress const& key) override { - sys_lock(); triggerKeyboardEvent(key.getTextDescription().toRawUTF8(), key.getKeyCode(), 1); - sys_unlock(); heldKeys.add(key); @@ -267,10 +251,12 @@ int createGemWindow(WindowInfo& info, WindowHints& hints) } void destroyGemWindow(WindowInfo& info) { if(auto* window = info.getWindow()) { - window->removeFromDesktop(); - info.window.erase(window->instance); - info.context.erase(window->instance); - gemJUCEWindow[window->instance].reset(nullptr); + GemCallOnMessageThread([window, &info](){ + window->removeFromDesktop(); + info.window.erase(window->instance); + info.context.erase(window->instance); + gemJUCEWindow[window->instance].reset(nullptr); + }); } } From 0562b29a83f8ad15285718ede8c8f7c8615dd4e4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Jan 2024 22:19:38 +0100 Subject: [PATCH 0098/1030] Gem: fix scaling on Windows and Linux --- Source/Objects/Gem.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index f1cb73e56b..5e3a4e4d83 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -103,8 +103,17 @@ class GemJUCEWindow final : public Component, public Timer void resized() override { + auto w = getWidth(); + auto h = getHeight(); + + if(auto* peer = getPeer()) + { + w *= peer->getPlatformScaleFactor(); + h *= peer->getPlatformScaleFactor(); + } + setThis(); - triggerResizeEvent(getWidth(), getHeight()); + triggerResizeEvent(w, h); } void paint (Graphics&) override From 045282bbe7c62cb7985a55d7922218833d26287c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Jan 2024 22:39:30 +0100 Subject: [PATCH 0099/1030] Resize fix on macOS --- Libraries/Gem | 2 +- Source/Objects/Gem.h | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index 163b8d37f8..7178779645 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 163b8d37f8ec0844ae2246d71f77ab1ef1d474f3 +Subproject commit 71787796450fcfdbfc01ca9b7ebcf9bde52e9894 diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 5e3a4e4d83..fa4f93b59a 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -108,8 +108,9 @@ class GemJUCEWindow final : public Component, public Timer if(auto* peer = getPeer()) { - w *= peer->getPlatformScaleFactor(); - h *= peer->getPlatformScaleFactor(); + auto scale = peer->getPlatformScaleFactor(); + w *= scale; + h *= scale; } setThis(); From 970d594c5a5eadccd3da8d858c9660936dd711af Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Jan 2024 22:45:18 +0100 Subject: [PATCH 0100/1030] Gem fix for Linux and Windows --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 7178779645..4dda43702e 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 71787796450fcfdbfc01ca9b7ebcf9bde52e9894 +Subproject commit 4dda43702ed72b7015c36772d74f6c55ad9c421d From 09945035629f054658273db7b43d1f9b6ceaf75f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Jan 2024 23:12:48 +0100 Subject: [PATCH 0101/1030] Gem: implement more windowing features like fullscreen, title, borderless --- Source/Objects/Gem.h | 50 +++++++++++++++++++++----------- Source/Utility/ValueTreeViewer.h | 2 +- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index fa4f93b59a..56ced6f47b 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -45,7 +45,7 @@ class GemJUCEWindow final : public Component, public Timer public: //============================================================================== - GemJUCEWindow(int width, int height) : gemWidth(width), gemHeight(height) + GemJUCEWindow(Rectangle bounds, bool border) { instance = libpd_this_instance(); @@ -64,7 +64,7 @@ class GemJUCEWindow final : public Component, public Timer sys_unlock(); }; - setSize(width, height); + setBounds(bounds); setOpaque (true); openGLContext.setSwapInterval(0); @@ -78,15 +78,19 @@ class GemJUCEWindow final : public Component, public Timer startTimerHz(30); - addToDesktop(ComponentPeer::windowHasTitleBar | - ComponentPeer::windowHasDropShadow | - ComponentPeer::windowIsResizable | - ComponentPeer::windowHasMinimiseButton | - ComponentPeer::windowHasMaximiseButton - ); + if(border) + { + addToDesktop(ComponentPeer::windowHasTitleBar | + ComponentPeer::windowHasDropShadow | + ComponentPeer::windowIsResizable | + ComponentPeer::windowHasMinimiseButton | + ComponentPeer::windowHasMaximiseButton); + } + else { + addToDesktop(0); + } setVisible(true); - setTopLeftPosition(100, 100); if(auto* peer = getPeer()) { @@ -182,7 +186,6 @@ class GemJUCEWindow final : public Component, public Timer } - int gemWidth, gemHeight; OpenGLContext openGLContext; t_pdinstance* instance; Array heldKeys; @@ -225,7 +228,7 @@ bool initGemWin(void) int createGemWindow(WindowInfo& info, WindowHints& hints) { - auto* window = new GemJUCEWindow(hints.width, hints.height); + auto* window = new GemJUCEWindow({hints.x_offset, hints.y_offset, hints.width, hints.height}, hints.border); gemJUCEWindow[window->instance].reset(window); info.window[window->instance] = window; @@ -243,13 +246,28 @@ int createGemWindow(WindowInfo& info, WindowHints& hints) hints.real_w = window->getWidth(); hints.real_h = window->getHeight(); - - auto* peer = window->getPeer(); - if(peer && hints.title) + + if(auto* peer = window->getPeer()) { - window->setTitle(String::fromUTF8(hints.title)); + if(hints.title) + { + peer->setTitle(String::fromUTF8(hints.title)); + } + if(hints.fullscreen) + { + peer->setFullScreen(hints.fullscreen); + } + + auto const& displays = Desktop::getInstance().getDisplays().displays; + if(hints.secondscreen && displays.size() >= 2) + { + // Move window to second screen + window->setTopLeftPosition(displays[1].userArea.getPosition() + window->getPosition()); + } } + // TODO: hints.secondscreen + // Make sure only audio thread has the context set as active // We call async here, because if this call comes from the message thread already, // we need to keep the context active until GLEW is initialised. Bit of a hack though @@ -295,8 +313,6 @@ void gemWinResize(WindowInfo& info, int width, int height) { MessageManager::callAsync([window = Component::SafePointer(windowPtr), width, height](){ if(auto* w = window.getComponent()) { w->setSize(width, height); - w->gemHeight = height; - w->gemWidth = width; } }); } diff --git a/Source/Utility/ValueTreeViewer.h b/Source/Utility/ValueTreeViewer.h index fa15e7866a..1994db87d9 100644 --- a/Source/Utility/ValueTreeViewer.h +++ b/Source/Utility/ValueTreeViewer.h @@ -17,7 +17,7 @@ struct ValueTreeOwnerView : public Component class ValueTreeNodeComponent : public Component { public: - ValueTreeNodeComponent(const ValueTree& node, ValueTreeNodeComponent* parentNode, String prepend = String()) : parent(parentNode), valueTreeNode(node) + ValueTreeNodeComponent(const ValueTree& node, ValueTreeNodeComponent* parentNode, String prepend = String()) : valueTreeNode(node), parent(parentNode) { nodeBranchLine = std::make_unique(this); addAndMakeVisible(nodeBranchLine.get()); From 534d2e82ab3a473963cd0db17181b79101f79ba2 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 22 Jan 2024 00:14:07 +0100 Subject: [PATCH 0102/1030] Fixed bad function definition --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 4dda43702e..1a440810d2 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 4dda43702ed72b7015c36772d74f6c55ad9c421d +Subproject commit 1a440810d2ba272244ee1d1cedba01a78cc773e0 From d7adf6faa4231a74b2f6b456fd53dff72bcb63c3 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 22 Jan 2024 00:21:12 +0100 Subject: [PATCH 0103/1030] No longer install gem dependencies --- .github/workflows/cmake.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 10bcdb8d9a..cd224fb164 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -22,10 +22,6 @@ jobs: with: key: macos - - name: Install Gem plugin dependencies - run: ./depinstall-osx.sh - working-directory: ${{github.workspace}}/Libraries/Gem/.git-ci/travis-ci - - name: Create Build Environment run: cmake -E make_directory ${{github.workspace}}/build From 7cb79bbad39a343b34f08b17f623ad72374aea7e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 22 Jan 2024 00:23:28 +0100 Subject: [PATCH 0104/1030] Fixed Gem dependency issue --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 1a440810d2..d61c3dcd36 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 1a440810d2ba272244ee1d1cedba01a78cc773e0 +Subproject commit d61c3dcd36410258a126eccf1de9e626fb30523d From a5cae6a769dc1d0cb5553a154168cc6c2e4a90fa Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 22 Jan 2024 01:42:01 +0100 Subject: [PATCH 0105/1030] Fix Gem helpfile issues --- Libraries/Gem | 2 +- Source/Pd/Instance.cpp | 6 +++--- Source/Pd/Setup.cpp | 6 +++--- Source/Pd/Setup.h | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index d61c3dcd36..c915e0f91b 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit d61c3dcd36410258a126eccf1de9e626fb30523d +Subproject commit c915e0f91b3daa02d32bc13ba940032c64f3d4ce diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 5e895e5388..3e53086f4e 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -284,9 +284,9 @@ void Instance::initialisePd(String& pdlua_version) pd::Setup::initialiseCyclone(); set_class_prefix(gensym("Gem")); - - class_set_extern_dir(gensym(ProjectInfo::appDataDir.getChildFile("Extra").getChildFile("Gem").getFullPathName().toRawUTF8())); - pd::Setup::initialiseGem(); + + class_set_extern_dir(gensym("14.gem")); + pd::Setup::initialiseGem(ProjectInfo::appDataDir.getChildFile("Extra").getChildFile("Gem").getFullPathName().toStdString()); class_set_extern_dir(gensym("")); set_class_prefix(nullptr); diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index 150ce48488..20e6500b2d 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -161,7 +161,7 @@ extern "C" { void pd_init(); #if ENABLE_GEM -void Gem_setup(); +void Gem_setup(t_symbol* plugin_path); void gemcubeframebuffer_setup(); void gemframebuffer_setup(); void gemhead_setup(); @@ -1672,10 +1672,10 @@ void Setup::initialiseELSE() fm_tilde_setup(); } -void Setup::initialiseGem() +void Setup::initialiseGem(std::string gemPluginPath) { #if ENABLE_GEM - Gem_setup(); + Gem_setup(gensym(gemPluginPath.c_str())); gemcubeframebuffer_setup(); gemframebuffer_setup(); gemhead_setup(); diff --git a/Source/Pd/Setup.h b/Source/Pd/Setup.h index c7395628ea..d33a5c15d8 100644 --- a/Source/Pd/Setup.h +++ b/Source/Pd/Setup.h @@ -35,7 +35,7 @@ struct Setup { static void initialisePdLua(char const* datadir, char* vers, int vers_len, void(*register_class_callback)(const char*)); static void initialiseELSE(); static void initialiseCyclone(); - static void initialiseGem(); + static void initialiseGem(std::string gemPluginPath); static void* createMIDIHook(void* ptr, t_plugdata_noteonhook hook_noteon, From 64cc0e67dfe96715a3edd62f0980f89726ceb68e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 22 Jan 2024 02:28:31 +0100 Subject: [PATCH 0106/1030] Gem: fixed plugin event handling --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index c915e0f91b..b40b129f27 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit c915e0f91b3daa02d32bc13ba940032c64f3d4ce +Subproject commit b40b129f27524afbc3eff11fec7a411ea24f51ac From 42f2ec77b9f46ab17cfa883e64705fe634679d0e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 22 Jan 2024 13:33:03 +0100 Subject: [PATCH 0107/1030] Fixed naming conflict between [Gem/scale] and [cyclone/scale] --- Libraries/cyclone/cyclone_objects/binaries/control/scale.c | 2 +- Source/Pd/Setup.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Libraries/cyclone/cyclone_objects/binaries/control/scale.c b/Libraries/cyclone/cyclone_objects/binaries/control/scale.c index fe23c3d2bd..7891191d75 100644 --- a/Libraries/cyclone/cyclone_objects/binaries/control/scale.c +++ b/Libraries/cyclone/cyclone_objects/binaries/control/scale.c @@ -146,7 +146,7 @@ static void *scale_new(t_symbol *s, int argc, t_atom *argv) return (x); } -CYCLONE_OBJ_API void scale_setup(void) +CYCLONE_OBJ_API void cyclone_scale_setup(void) { t_class *c; diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index 20e6500b2d..ecc098288c 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -776,7 +776,7 @@ void pv_setup(); void rdiv_setup(); void rminus_setup(); void round_setup(); -void scale_setup(); +void cyclone_scale_setup(); void seq_setup(); void sinh_setup(); void speedlim_setup(); @@ -2293,7 +2293,7 @@ void Setup::initialiseCyclone() rdiv_setup(); rminus_setup(); round_setup(); - scale_setup(); + cyclone_scale_setup(); seq_setup(); sinh_setup(); speedlim_setup(); From 8313b256201bf00ae380d20fd3dc883ef60615eb Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 22 Jan 2024 14:03:42 +0100 Subject: [PATCH 0108/1030] Gem: removed zexy dependency from examples --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index b40b129f27..1fd2670875 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit b40b129f27524afbc3eff11fec7a411ea24f51ac +Subproject commit 1fd267087517eaf2d2bdc709cba876e080398467 From d3484521638833f60a42b0ab4f8c049cfbcc6d6b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 22 Jan 2024 15:06:05 +0100 Subject: [PATCH 0109/1030] Gem: more changes so we don't need zexy for examples --- Libraries/CMakeLists.txt | 2 +- Libraries/Gem | 2 +- Resources/Scripts/parse_documentation.py | 15 +++++++++++---- Source/CanvasViewport.h | 2 +- Source/Pd/Setup.cpp | 2 ++ Source/Utility/Config.h | 2 +- 6 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index d025ac535a..99ba29c4e7 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -409,7 +409,7 @@ if(MSVC) target_compile_definitions(pd-src-multi PRIVATE PTW32_STATIC_LIB=1 "EXTERN= ") endif() -set_target_properties(pd-src-multi PROPERTIES CXX_VISIBILITY_PRESET hidden) +#set_target_properties(pd-src-multi PROPERTIES CXX_VISIBILITY_PRESET hidden) set_target_properties(pd-src PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(pd-src-multi PROPERTIES POSITION_INDEPENDENT_CODE ON) diff --git a/Libraries/Gem b/Libraries/Gem index 1fd2670875..1820974718 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 1fd267087517eaf2d2bdc709cba876e080398467 +Subproject commit 1820974718e5d99b5ffda4509fd559724925224a diff --git a/Resources/Scripts/parse_documentation.py b/Resources/Scripts/parse_documentation.py index 88988488a3..5abedd8b3d 100644 --- a/Resources/Scripts/parse_documentation.py +++ b/Resources/Scripts/parse_documentation.py @@ -231,7 +231,8 @@ def markdownToXml(root, md): desc = sectionMap["description"] if "description" in sectionMap else "" ET.SubElement(flags, "flag", name=sectionMap["name"], description=desc) - numbers = { "1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "nth" }; + numbers = { "1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", "nth" }; + #print(description) if "inlets" in sections: inletSections = getSections(sections["inlets"], numbers) @@ -242,7 +243,9 @@ def markdownToXml(root, md): inlet = ET.Element("inlet", variable=isVariable) for argument in sectionsFromHyphens(inletSections[section]): typeAndDescription = getSections(argument, { "type", "description" }) - tip += "(" + typeAndDescription["type"] + ") " + typeAndDescription["description"] + "\n" + tip += "(" + typeAndDescription["type"] + ") " + if "description" in typeAndDescription: + tip += typeAndDescription["description"] + "\n" ET.SubElement(inlet, "message", type=typeAndDescription["type"], description=typeAndDescription["description"]) inlet.set("tooltip", tip.strip()) iolets.append(inlet) @@ -256,8 +259,12 @@ def markdownToXml(root, md): outlet = ET.Element("outlet", variable=isVariable) for argument in sectionsFromHyphens(outletSections[section]): typeAndDescription = getSections(argument, { "type", "description" }) - tip += "(" + typeAndDescription["type"] + ") " + typeAndDescription["description"] + "\n" - ET.SubElement(outlet, "message", type=typeAndDescription["type"], description=typeAndDescription["description"]) + tip += "(" + typeAndDescription["type"] + ") " + description = "" + if "description" in typeAndDescription: + tip += typeAndDescription["description"] + "\n" + description = typeAndDescription["description"] + ET.SubElement(outlet, "message", type=typeAndDescription["type"], description=description) outlet.set("tooltip", tip.strip()) iolets.append(outlet) diff --git a/Source/CanvasViewport.h b/Source/CanvasViewport.h index 52a52c6db4..2201daf2d7 100644 --- a/Source/CanvasViewport.h +++ b/Source/CanvasViewport.h @@ -372,7 +372,7 @@ class CanvasViewport : public Viewport { void componentMovedOrResized(Component& c, bool moved, bool resized) override { - if (editor->pd->isInPluginMode()) + if (cnv->pd->isInPluginMode()) return; Viewport::componentMovedOrResized(c, moved, resized); diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index ecc098288c..82acff1256 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -239,6 +239,7 @@ void polygon_smooth_setup(); void rotate_setup(); void rotateXYZ_setup(); void scale_setup(); +void gemrepeat_setup(); void scaleXYZ_setup(); void separator_setup(); void shearXY_setup(); @@ -1753,6 +1754,7 @@ void Setup::initialiseGem(std::string gemPluginPath) rotate_setup(); rotateXYZ_setup(); scale_setup(); + gemrepeat_setup(); scaleXYZ_setup(); separator_setup(); shearXY_setup(); diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index 90b9134e81..486f86e2cd 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -38,7 +38,7 @@ struct ProjectInfo { static inline File const appDataDir = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory).getChildFile("plugdata"); - static inline String const versionSuffix = "-1"; + static inline String const versionSuffix = "-2"; static inline File const versionDataDir = appDataDir.getChildFile("Versions").getChildFile(ProjectInfo::versionString + versionSuffix); }; From 49503ad34c7ea5df6bddf46a0dd1add7747c865f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 22 Jan 2024 15:25:47 +0100 Subject: [PATCH 0110/1030] Revert accidental change --- Libraries/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 99ba29c4e7..d025ac535a 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -409,7 +409,7 @@ if(MSVC) target_compile_definitions(pd-src-multi PRIVATE PTW32_STATIC_LIB=1 "EXTERN= ") endif() -#set_target_properties(pd-src-multi PROPERTIES CXX_VISIBILITY_PRESET hidden) +set_target_properties(pd-src-multi PROPERTIES CXX_VISIBILITY_PRESET hidden) set_target_properties(pd-src PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(pd-src-multi PROPERTIES POSITION_INDEPENDENT_CODE ON) From bcc4b0fe2e4970cbaf178d8484c77a8e6005d408 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Jan 2024 16:20:21 +0100 Subject: [PATCH 0111/1030] Added markdown documentation for Gem --- Resources/Documentation/Gem/GEMglAccum.md | 30 ++++++++ .../Documentation/Gem/GEMglActiveTexture.md | 24 +++++++ .../Gem/GEMglActiveTextureARB.md | 24 +++++++ Resources/Documentation/Gem/GEMglAlphaFunc.md | 30 ++++++++ .../Gem/GEMglAreTexturesResident.md | 33 +++++++++ .../Documentation/Gem/GEMglArrayElement.md | 24 +++++++ Resources/Documentation/Gem/GEMglBegin.md | 24 +++++++ .../Documentation/Gem/GEMglBindProgramARB.md | 30 ++++++++ .../Documentation/Gem/GEMglBindTexture.md | 30 ++++++++ Resources/Documentation/Gem/GEMglBitmap.md | 59 +++++++++++++++ .../Documentation/Gem/GEMglBlendEquation.md | 24 +++++++ Resources/Documentation/Gem/GEMglBlendFunc.md | 30 ++++++++ Resources/Documentation/Gem/GEMglCallList.md | 24 +++++++ Resources/Documentation/Gem/GEMglClear.md | 24 +++++++ .../Documentation/Gem/GEMglClearAccum.md | 42 +++++++++++ .../Documentation/Gem/GEMglClearColor.md | 42 +++++++++++ .../Documentation/Gem/GEMglClearDepth.md | 24 +++++++ .../Documentation/Gem/GEMglClearIndex.md | 24 +++++++ .../Documentation/Gem/GEMglClearStencil.md | 24 +++++++ Resources/Documentation/Gem/GEMglClipPlane.md | 30 ++++++++ Resources/Documentation/Gem/GEMglColor3b.md | 36 ++++++++++ Resources/Documentation/Gem/GEMglColor3bv.md | 24 +++++++ Resources/Documentation/Gem/GEMglColor3d.md | 36 ++++++++++ Resources/Documentation/Gem/GEMglColor3dv.md | 24 +++++++ Resources/Documentation/Gem/GEMglColor3f.md | 35 +++++++++ Resources/Documentation/Gem/GEMglColor3fv.md | 24 +++++++ Resources/Documentation/Gem/GEMglColor3i.md | 36 ++++++++++ Resources/Documentation/Gem/GEMglColor3iv.md | 24 +++++++ Resources/Documentation/Gem/GEMglColor3s.md | 36 ++++++++++ Resources/Documentation/Gem/GEMglColor3sv.md | 23 ++++++ Resources/Documentation/Gem/GEMglColor3ub.md | 36 ++++++++++ Resources/Documentation/Gem/GEMglColor3ubv.md | 24 +++++++ Resources/Documentation/Gem/GEMglColor3ui.md | 36 ++++++++++ Resources/Documentation/Gem/GEMglColor3uiv.md | 24 +++++++ Resources/Documentation/Gem/GEMglColor3us.md | 35 +++++++++ Resources/Documentation/Gem/GEMglColor3usv.md | 24 +++++++ Resources/Documentation/Gem/GEMglColor4b.md | 42 +++++++++++ Resources/Documentation/Gem/GEMglColor4bv.md | 24 +++++++ Resources/Documentation/Gem/GEMglColor4d.md | 42 +++++++++++ Resources/Documentation/Gem/GEMglColor4dv.md | 23 ++++++ Resources/Documentation/Gem/GEMglColor4f.md | 42 +++++++++++ Resources/Documentation/Gem/GEMglColor4fv.md | 24 +++++++ Resources/Documentation/Gem/GEMglColor4i.md | 42 +++++++++++ Resources/Documentation/Gem/GEMglColor4iv.md | 24 +++++++ Resources/Documentation/Gem/GEMglColor4s.md | 41 +++++++++++ Resources/Documentation/Gem/GEMglColor4sv.md | 24 +++++++ Resources/Documentation/Gem/GEMglColor4ub.md | 42 +++++++++++ Resources/Documentation/Gem/GEMglColor4ubv.md | 24 +++++++ Resources/Documentation/Gem/GEMglColor4ui.md | 42 +++++++++++ Resources/Documentation/Gem/GEMglColor4uiv.md | 23 ++++++ Resources/Documentation/Gem/GEMglColor4us.md | 42 +++++++++++ Resources/Documentation/Gem/GEMglColor4usv.md | 24 +++++++ Resources/Documentation/Gem/GEMglColorMask.md | 24 +++++++ .../Documentation/Gem/GEMglColorMaterial.md | 36 ++++++++++ .../Documentation/Gem/GEMglCopyPixels.md | 48 +++++++++++++ .../Documentation/Gem/GEMglCopyTexImage1D.md | 54 ++++++++++++++ .../Documentation/Gem/GEMglCopyTexImage2D.md | 54 ++++++++++++++ .../Gem/GEMglCopyTexSubImage1D.md | 42 +++++++++++ .../Gem/GEMglCopyTexSubImage2D.md | 42 +++++++++++ Resources/Documentation/Gem/GEMglCullFace.md | 23 ++++++ .../Documentation/Gem/GEMglDeleteTextures.md | 30 ++++++++ Resources/Documentation/Gem/GEMglDepthFunc.md | 24 +++++++ Resources/Documentation/Gem/GEMglDepthMask.md | 24 +++++++ .../Documentation/Gem/GEMglDepthRange.md | 30 ++++++++ Resources/Documentation/Gem/GEMglDisable.md | 23 ++++++ .../Gem/GEMglDisableClientState.md | 24 +++++++ .../Documentation/Gem/GEMglDrawArrays.md | 36 ++++++++++ .../Documentation/Gem/GEMglDrawBuffer.md | 24 +++++++ .../Documentation/Gem/GEMglDrawElements.md | 42 +++++++++++ Resources/Documentation/Gem/GEMglEdgeFlag.md | 23 ++++++ Resources/Documentation/Gem/GEMglEnable.md | 24 +++++++ .../Gem/GEMglEnableClientState.md | 24 +++++++ Resources/Documentation/Gem/GEMglEnd.md | 17 +++++ Resources/Documentation/Gem/GEMglEndList.md | 17 +++++ .../Documentation/Gem/GEMglEvalCoord1d.md | 23 ++++++ .../Documentation/Gem/GEMglEvalCoord1dv.md | 24 +++++++ .../Documentation/Gem/GEMglEvalCoord1f.md | 24 +++++++ .../Documentation/Gem/GEMglEvalCoord1fv.md | 24 +++++++ .../Documentation/Gem/GEMglEvalCoord2d.md | 28 ++++++++ .../Documentation/Gem/GEMglEvalCoord2dv.md | 27 +++++++ .../Documentation/Gem/GEMglEvalCoord2f.md | 28 ++++++++ .../Documentation/Gem/GEMglEvalCoord2fv.md | 28 ++++++++ Resources/Documentation/Gem/GEMglEvalMesh1.md | 36 ++++++++++ Resources/Documentation/Gem/GEMglEvalMesh2.md | 41 +++++++++++ .../Documentation/Gem/GEMglEvalPoint1.md | 29 ++++++++ .../Documentation/Gem/GEMglEvalPoint2.md | 35 +++++++++ .../Documentation/Gem/GEMglFeedbackBuffer.md | 30 ++++++++ Resources/Documentation/Gem/GEMglFinish.md | 17 +++++ Resources/Documentation/Gem/GEMglFlush.md | 17 +++++ Resources/Documentation/Gem/GEMglFogf.md | 29 ++++++++ Resources/Documentation/Gem/GEMglFogfv.md | 30 ++++++++ Resources/Documentation/Gem/GEMglFogi.md | 30 ++++++++ Resources/Documentation/Gem/GEMglFogiv.md | 30 ++++++++ Resources/Documentation/Gem/GEMglFrontFace.md | 24 +++++++ Resources/Documentation/Gem/GEMglFrustum.md | 38 ++++++++++ Resources/Documentation/Gem/GEMglGenLists.md | 24 +++++++ .../Documentation/Gem/GEMglGenProgramsARB.md | 24 +++++++ .../Documentation/Gem/GEMglGenTextures.md | 24 +++++++ .../Documentation/Gem/GEMglGenerateMipmap.md | 24 +++++++ Resources/Documentation/Gem/GEMglGetError.md | 17 +++++ Resources/Documentation/Gem/GEMglGetFloatv.md | 27 +++++++ .../Documentation/Gem/GEMglGetIntegerv.md | 27 +++++++ Resources/Documentation/Gem/GEMglGetMapdv.md | 33 +++++++++ Resources/Documentation/Gem/GEMglGetMapfv.md | 33 +++++++++ Resources/Documentation/Gem/GEMglGetMapiv.md | 32 +++++++++ .../Documentation/Gem/GEMglGetPointerv.md | 27 +++++++ Resources/Documentation/Gem/GEMglGetString.md | 27 +++++++ Resources/Documentation/Gem/GEMglHint.md | 30 ++++++++ Resources/Documentation/Gem/GEMglIndexMask.md | 24 +++++++ Resources/Documentation/Gem/GEMglIndexd.md | 24 +++++++ Resources/Documentation/Gem/GEMglIndexdv.md | 24 +++++++ Resources/Documentation/Gem/GEMglIndexf.md | 24 +++++++ Resources/Documentation/Gem/GEMglIndexfv.md | 24 +++++++ Resources/Documentation/Gem/GEMglIndexi.md | 24 +++++++ Resources/Documentation/Gem/GEMglIndexiv.md | 24 +++++++ Resources/Documentation/Gem/GEMglIndexs.md | 24 +++++++ Resources/Documentation/Gem/GEMglIndexsv.md | 24 +++++++ Resources/Documentation/Gem/GEMglIndexub.md | 24 +++++++ Resources/Documentation/Gem/GEMglIndexubv.md | 24 +++++++ Resources/Documentation/Gem/GEMglInitNames.md | 17 +++++ Resources/Documentation/Gem/GEMglIsEnabled.md | 25 +++++++ Resources/Documentation/Gem/GEMglIsList.md | 24 +++++++ Resources/Documentation/Gem/GEMglIsTexture.md | 24 +++++++ .../Documentation/Gem/GEMglLightModelf.md | 30 ++++++++ .../Documentation/Gem/GEMglLightModeli.md | 29 ++++++++ Resources/Documentation/Gem/GEMglLightf.md | 36 ++++++++++ Resources/Documentation/Gem/GEMglLighti.md | 36 ++++++++++ .../Documentation/Gem/GEMglLineStipple.md | 30 ++++++++ Resources/Documentation/Gem/GEMglLineWidth.md | 24 +++++++ .../Documentation/Gem/GEMglLoadIdentity.md | 17 +++++ .../Documentation/Gem/GEMglLoadMatrixd.md | 23 ++++++ .../Documentation/Gem/GEMglLoadMatrixf.md | 23 ++++++ Resources/Documentation/Gem/GEMglLoadName.md | 23 ++++++ .../Gem/GEMglLoadTransposeMatrixd.md | 23 ++++++ .../Gem/GEMglLoadTransposeMatrixf.md | 23 ++++++ Resources/Documentation/Gem/GEMglLogicOp.md | 24 +++++++ Resources/Documentation/Gem/GEMglMap1d.md | 29 ++++++++ Resources/Documentation/Gem/GEMglMap1f.md | 29 ++++++++ Resources/Documentation/Gem/GEMglMap2d.md | 29 ++++++++ Resources/Documentation/Gem/GEMglMap2f.md | 28 ++++++++ Resources/Documentation/Gem/GEMglMapGrid1d.md | 29 ++++++++ Resources/Documentation/Gem/GEMglMapGrid1f.md | 29 ++++++++ Resources/Documentation/Gem/GEMglMapGrid2d.md | 40 +++++++++++ Resources/Documentation/Gem/GEMglMapGrid2f.md | 40 +++++++++++ Resources/Documentation/Gem/GEMglMaterialf.md | 36 ++++++++++ .../Documentation/Gem/GEMglMaterialfv.md | 36 ++++++++++ Resources/Documentation/Gem/GEMglMateriali.md | 36 ++++++++++ .../Documentation/Gem/GEMglMatrixMode.md | 24 +++++++ .../Documentation/Gem/GEMglMultMatrixd.md | 23 ++++++ .../Documentation/Gem/GEMglMultMatrixf.md | 23 ++++++ .../Gem/GEMglMultTransposeMatrixf.md | 23 ++++++ .../Documentation/Gem/GEMglMultiTexCoord2f.md | 32 +++++++++ .../Gem/GEMglMultiTexCoord2fARB.md | 32 +++++++++ Resources/Documentation/Gem/GEMglNewList.md | 31 ++++++++ Resources/Documentation/Gem/GEMglNormal3b.md | 24 +++++++ Resources/Documentation/Gem/GEMglNormal3bv.md | 23 ++++++ Resources/Documentation/Gem/GEMglNormal3d.md | 24 +++++++ Resources/Documentation/Gem/GEMglNormal3dv.md | 23 ++++++ Resources/Documentation/Gem/GEMglNormal3f.md | 24 +++++++ Resources/Documentation/Gem/GEMglNormal3fv.md | 23 ++++++ Resources/Documentation/Gem/GEMglNormal3i.md | 24 +++++++ Resources/Documentation/Gem/GEMglNormal3iv.md | 23 ++++++ Resources/Documentation/Gem/GEMglNormal3s.md | 24 +++++++ Resources/Documentation/Gem/GEMglNormal3sv.md | 23 ++++++ Resources/Documentation/Gem/GEMglOrtho.md | 36 ++++++++++ .../Documentation/Gem/GEMglPassThrough.md | 23 ++++++ .../Documentation/Gem/GEMglPixelStoref.md | 28 ++++++++ .../Documentation/Gem/GEMglPixelStorei.md | 28 ++++++++ .../Documentation/Gem/GEMglPixelTransferf.md | 28 ++++++++ .../Documentation/Gem/GEMglPixelTransferi.md | 28 ++++++++ Resources/Documentation/Gem/GEMglPixelZoom.md | 30 ++++++++ Resources/Documentation/Gem/GEMglPointSize.md | 24 +++++++ .../Documentation/Gem/GEMglPolygonMode.md | 28 ++++++++ .../Documentation/Gem/GEMglPolygonOffset.md | 30 ++++++++ Resources/Documentation/Gem/GEMglPopAttrib.md | 18 +++++ .../Documentation/Gem/GEMglPopClientAttrib.md | 18 +++++ Resources/Documentation/Gem/GEMglPopMatrix.md | 18 +++++ Resources/Documentation/Gem/GEMglPopName.md | 18 +++++ .../Gem/GEMglPrioritizeTextures.md | 33 +++++++++ .../Gem/GEMglProgramEnvParameter4dARB.md | 48 +++++++++++++ .../Gem/GEMglProgramEnvParameter4fvARB.md | 48 +++++++++++++ .../Gem/GEMglProgramLocalParameter4fvARB.md | 48 +++++++++++++ .../Gem/GEMglProgramStringARB.md | 43 +++++++++++ .../Documentation/Gem/GEMglPushAttrib.md | 23 ++++++ .../Gem/GEMglPushClientAttrib.md | 23 ++++++ .../Documentation/Gem/GEMglPushMatrix.md | 18 +++++ Resources/Documentation/Gem/GEMglPushName.md | 23 ++++++ .../Documentation/Gem/GEMglRasterPos2d.md | 28 ++++++++ .../Documentation/Gem/GEMglRasterPos2dv.md | 23 ++++++ .../Documentation/Gem/GEMglRasterPos2f.md | 28 ++++++++ .../Documentation/Gem/GEMglRasterPos2fv.md | 23 ++++++ .../Documentation/Gem/GEMglRasterPos2i.md | 28 ++++++++ .../Documentation/Gem/GEMglRasterPos2iv.md | 23 ++++++ .../Documentation/Gem/GEMglRasterPos2s.md | 28 ++++++++ .../Documentation/Gem/GEMglRasterPos2sv.md | 23 ++++++ .../Documentation/Gem/GEMglRasterPos3d.md | 33 +++++++++ .../Documentation/Gem/GEMglRasterPos3dv.md | 26 +++++++ .../Documentation/Gem/GEMglRasterPos3f.md | 33 +++++++++ .../Documentation/Gem/GEMglRasterPos3fv.md | 26 +++++++ .../Documentation/Gem/GEMglRasterPos3i.md | 33 +++++++++ .../Documentation/Gem/GEMglRasterPos3iv.md | 26 +++++++ .../Documentation/Gem/GEMglRasterPos3s.md | 33 +++++++++ .../Documentation/Gem/GEMglRasterPos3sv.md | 26 +++++++ .../Documentation/Gem/GEMglRasterPos4d.md | 38 ++++++++++ .../Documentation/Gem/GEMglRasterPos4dv.md | 32 +++++++++ .../Documentation/Gem/GEMglRasterPos4f.md | 38 ++++++++++ .../Documentation/Gem/GEMglRasterPos4fv.md | 32 +++++++++ .../Documentation/Gem/GEMglRasterPos4i.md | 38 ++++++++++ .../Documentation/Gem/GEMglRasterPos4iv.md | 32 +++++++++ .../Documentation/Gem/GEMglRasterPos4s.md | 38 ++++++++++ .../Documentation/Gem/GEMglRasterPos4sv.md | 32 +++++++++ Resources/Documentation/Gem/GEMglRectd.md | 34 +++++++++ Resources/Documentation/Gem/GEMglRectf.md | 34 +++++++++ Resources/Documentation/Gem/GEMglRecti.md | 34 +++++++++ Resources/Documentation/Gem/GEMglRects.md | 34 +++++++++ .../Documentation/Gem/GEMglRenderMode.md | 16 +++++ .../Documentation/Gem/GEMglReportError.md | 13 ++++ Resources/Documentation/Gem/GEMglRotated.md | 20 ++++++ Resources/Documentation/Gem/GEMglRotatef.md | 19 +++++ Resources/Documentation/Gem/GEMglScaled.md | 20 ++++++ Resources/Documentation/Gem/GEMglScalef.md | 20 ++++++ Resources/Documentation/Gem/GEMglScissor.md | 38 ++++++++++ .../Documentation/Gem/GEMglSelectBuffer.md | 23 ++++++ .../Documentation/Gem/GEMglShadeModel.md | 23 ++++++ .../Documentation/Gem/GEMglStencilFunc.md | 33 +++++++++ .../Documentation/Gem/GEMglStencilMask.md | 23 ++++++ Resources/Documentation/Gem/GEMglStencilOp.md | 33 +++++++++ .../Documentation/Gem/GEMglTexCoord1d.md | 23 ++++++ .../Documentation/Gem/GEMglTexCoord1dv.md | 15 ++++ .../Documentation/Gem/GEMglTexCoord1f.md | 23 ++++++ .../Documentation/Gem/GEMglTexCoord1fv.md | 16 +++++ .../Documentation/Gem/GEMglTexCoord1i.md | 23 ++++++ .../Documentation/Gem/GEMglTexCoord1iv.md | 16 +++++ .../Documentation/Gem/GEMglTexCoord1s.md | 22 ++++++ .../Documentation/Gem/GEMglTexCoord1sv.md | 16 +++++ .../Documentation/Gem/GEMglTexCoord2d.md | 23 ++++++ .../Documentation/Gem/GEMglTexCoord2dv.md | 16 +++++ .../Documentation/Gem/GEMglTexCoord2f.md | 23 ++++++ .../Documentation/Gem/GEMglTexCoord2fv.md | 16 +++++ .../Documentation/Gem/GEMglTexCoord2i.md | 23 ++++++ .../Documentation/Gem/GEMglTexCoord2iv.md | 16 +++++ .../Documentation/Gem/GEMglTexCoord2s.md | 23 ++++++ .../Documentation/Gem/GEMglTexCoord2sv.md | 16 +++++ .../Documentation/Gem/GEMglTexCoord3d.md | 23 ++++++ .../Documentation/Gem/GEMglTexCoord3dv.md | 16 +++++ .../Documentation/Gem/GEMglTexCoord3f.md | 23 ++++++ .../Documentation/Gem/GEMglTexCoord3fv.md | 16 +++++ .../Documentation/Gem/GEMglTexCoord3i.md | 23 ++++++ .../Documentation/Gem/GEMglTexCoord3iv.md | 16 +++++ .../Documentation/Gem/GEMglTexCoord3s.md | 23 ++++++ .../Documentation/Gem/GEMglTexCoord3sv.md | 16 +++++ .../Documentation/Gem/GEMglTexCoord4d.md | 23 ++++++ .../Documentation/Gem/GEMglTexCoord4dv.md | 16 +++++ .../Documentation/Gem/GEMglTexCoord4f.md | 23 ++++++ .../Documentation/Gem/GEMglTexCoord4fv.md | 16 +++++ .../Documentation/Gem/GEMglTexCoord4i.md | 23 ++++++ .../Documentation/Gem/GEMglTexCoord4iv.md | 16 +++++ .../Documentation/Gem/GEMglTexCoord4s.md | 23 ++++++ .../Documentation/Gem/GEMglTexCoord4sv.md | 16 +++++ Resources/Documentation/Gem/GEMglTexEnvf.md | 33 +++++++++ Resources/Documentation/Gem/GEMglTexEnvi.md | 33 +++++++++ Resources/Documentation/Gem/GEMglTexGend.md | 33 +++++++++ Resources/Documentation/Gem/GEMglTexGenf.md | 33 +++++++++ Resources/Documentation/Gem/GEMglTexGenfv.md | 33 +++++++++ Resources/Documentation/Gem/GEMglTexGeni.md | 33 +++++++++ .../Documentation/Gem/GEMglTexImage2D.md | 63 ++++++++++++++++ .../Documentation/Gem/GEMglTexParameterf.md | 33 +++++++++ .../Documentation/Gem/GEMglTexParameteri.md | 33 +++++++++ .../Documentation/Gem/GEMglTexSubImage1D.md | 53 ++++++++++++++ .../Documentation/Gem/GEMglTexSubImage2D.md | 63 ++++++++++++++++ .../Documentation/Gem/GEMglTranslated.md | 23 ++++++ .../Documentation/Gem/GEMglTranslatef.md | 23 ++++++ Resources/Documentation/Gem/GEMglUniform1f.md | 28 ++++++++ .../Documentation/Gem/GEMglUniform1fARB.md | 28 ++++++++ .../Gem/GEMglUseProgramObjectARB.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex2d.md | 30 ++++++++ Resources/Documentation/Gem/GEMglVertex2dv.md | 24 +++++++ Resources/Documentation/Gem/GEMglVertex2f.md | 30 ++++++++ Resources/Documentation/Gem/GEMglVertex2fv.md | 24 +++++++ Resources/Documentation/Gem/GEMglVertex2i.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex2iv.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex2s.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex2sv.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex3d.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex3dv.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex3f.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex3fv.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex3i.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex3iv.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex3s.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex3sv.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex4d.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex4dv.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex4f.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex4fv.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex4i.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex4iv.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex4s.md | 23 ++++++ Resources/Documentation/Gem/GEMglVertex4sv.md | 23 ++++++ Resources/Documentation/Gem/GEMglViewport.md | 30 ++++++++ Resources/Documentation/Gem/GEMgluLookAt.md | 43 +++++++++++ .../Documentation/Gem/GEMgluPerspective.md | 42 +++++++++++ Resources/Documentation/Gem/GLdefine.md | 19 +++++ Resources/Documentation/Gem/accumrotate.md | 36 ++++++++++ Resources/Documentation/Gem/alpha.md | 28 ++++++++ Resources/Documentation/Gem/ambient.md | 22 ++++++ Resources/Documentation/Gem/ambientRGB.md | 40 +++++++++++ Resources/Documentation/Gem/circle.md | 22 ++++++ Resources/Documentation/Gem/color.md | 22 ++++++ Resources/Documentation/Gem/colorRGB.md | 37 ++++++++++ Resources/Documentation/Gem/colorSquare.md | 33 +++++++++ Resources/Documentation/Gem/cone.md | 26 +++++++ Resources/Documentation/Gem/cube.md | 21 ++++++ Resources/Documentation/Gem/cuboid.md | 31 ++++++++ Resources/Documentation/Gem/curve.md | 21 ++++++ Resources/Documentation/Gem/curve3d.md | 26 +++++++ Resources/Documentation/Gem/cylinder.md | 26 +++++++ Resources/Documentation/Gem/depth.md | 20 ++++++ Resources/Documentation/Gem/diffuse.md | 22 ++++++ Resources/Documentation/Gem/diffuseRGB.md | 40 +++++++++++ Resources/Documentation/Gem/disk.md | 34 +++++++++ Resources/Documentation/Gem/emission.md | 22 ++++++ Resources/Documentation/Gem/emissionRGB.md | 31 ++++++++ .../Documentation/Gem/fragment_program.md | 18 +++++ Resources/Documentation/Gem/gemargs.md | 19 +++++ .../Documentation/Gem/gemcubeframebuffer.md | 26 +++++++ Resources/Documentation/Gem/gemframebuffer.md | 32 +++++++++ Resources/Documentation/Gem/gemhead.md | 27 +++++++ Resources/Documentation/Gem/gemkeyboard.md | 14 ++++ Resources/Documentation/Gem/gemkeyname.md | 19 +++++ Resources/Documentation/Gem/gemlist.md | 22 ++++++ Resources/Documentation/Gem/gemlist_info.md | 28 ++++++++ Resources/Documentation/Gem/gemlist_matrix.md | 18 +++++ Resources/Documentation/Gem/gemmanager.md | 11 +++ Resources/Documentation/Gem/gemmouse.md | 32 +++++++++ Resources/Documentation/Gem/gemreceive.md | 19 +++++ Resources/Documentation/Gem/gemrepeat.md | 24 +++++++ .../Documentation/Gem/gemvertexbuffer.md | 40 +++++++++++ Resources/Documentation/Gem/gemwin.md | 47 ++++++++++++ Resources/Documentation/Gem/glsl_fragment.md | 25 +++++++ Resources/Documentation/Gem/glsl_geometry.md | 25 +++++++ Resources/Documentation/Gem/glsl_program.md | 41 +++++++++++ Resources/Documentation/Gem/glsl_vertex.md | 25 +++++++ Resources/Documentation/Gem/hsv2rgb.md | 16 +++++ Resources/Documentation/Gem/imageVert.md | 14 ++++ Resources/Documentation/Gem/light.md | 26 +++++++ Resources/Documentation/Gem/linear_path.md | 21 ++++++ Resources/Documentation/Gem/mesh_line.md | 28 ++++++++ Resources/Documentation/Gem/mesh_square.md | 29 ++++++++ Resources/Documentation/Gem/model.md | 35 +++++++++ Resources/Documentation/Gem/multimodel.md | 18 +++++ Resources/Documentation/Gem/newWave.md | 58 +++++++++++++++ Resources/Documentation/Gem/ortho.md | 20 ++++++ Resources/Documentation/Gem/part_color.md | 21 ++++++ Resources/Documentation/Gem/part_damp.md | 29 ++++++++ Resources/Documentation/Gem/part_draw.md | 15 ++++ Resources/Documentation/Gem/part_follow.md | 19 +++++ Resources/Documentation/Gem/part_gravity.md | 23 ++++++ Resources/Documentation/Gem/part_head.md | 20 ++++++ Resources/Documentation/Gem/part_info.md | 16 +++++ Resources/Documentation/Gem/part_killold.md | 18 +++++ Resources/Documentation/Gem/part_killslow.md | 18 +++++ .../Documentation/Gem/part_orbitpoint.md | 27 +++++++ Resources/Documentation/Gem/part_render.md | 14 ++++ Resources/Documentation/Gem/part_sink.md | 25 +++++++ Resources/Documentation/Gem/part_size.md | 17 +++++ Resources/Documentation/Gem/part_source.md | 23 ++++++ .../Documentation/Gem/part_targetcolor.md | 19 +++++ .../Documentation/Gem/part_targetsize.md | 21 ++++++ Resources/Documentation/Gem/part_velcone.md | 23 ++++++ Resources/Documentation/Gem/part_velocity.md | 25 +++++++ Resources/Documentation/Gem/part_velsphere.md | 23 ++++++ Resources/Documentation/Gem/part_vertex.md | 17 +++++ Resources/Documentation/Gem/pix_2grey.md | 15 ++++ Resources/Documentation/Gem/pix_a_2grey.md | 18 +++++ Resources/Documentation/Gem/pix_add.md | 16 +++++ Resources/Documentation/Gem/pix_aging.md | 31 ++++++++ Resources/Documentation/Gem/pix_alpha.md | 26 +++++++ Resources/Documentation/Gem/pix_background.md | 25 +++++++ Resources/Documentation/Gem/pix_backlight.md | 33 +++++++++ Resources/Documentation/Gem/pix_biquad.md | 19 +++++ Resources/Documentation/Gem/pix_bitmask.md | 20 ++++++ Resources/Documentation/Gem/pix_blob.md | 20 ++++++ .../Documentation/Gem/pix_blobtracker.md | 22 ++++++ Resources/Documentation/Gem/pix_blur.md | 18 +++++ Resources/Documentation/Gem/pix_buf.md | 22 ++++++ Resources/Documentation/Gem/pix_buffer.md | 27 +++++++ .../Documentation/Gem/pix_buffer_filmopen.md | 24 +++++++ .../Documentation/Gem/pix_buffer_read.md | 24 +++++++ .../Documentation/Gem/pix_buffer_write.md | 24 +++++++ Resources/Documentation/Gem/pix_chroma_key.md | 26 +++++++ Resources/Documentation/Gem/pix_clearblock.md | 18 +++++ Resources/Documentation/Gem/pix_color.md | 19 +++++ Resources/Documentation/Gem/pix_coloralpha.md | 19 +++++ .../Documentation/Gem/pix_colorclassify.md | 16 +++++ .../Documentation/Gem/pix_colormatrix.md | 19 +++++ .../Documentation/Gem/pix_colorreduce.md | 28 ++++++++ Resources/Documentation/Gem/pix_compare.md | 28 ++++++++ Resources/Documentation/Gem/pix_composite.md | 19 +++++ Resources/Documentation/Gem/pix_contrast.md | 29 ++++++++ Resources/Documentation/Gem/pix_convert.md | 22 ++++++ Resources/Documentation/Gem/pix_convolve.md | 23 ++++++ Resources/Documentation/Gem/pix_coordinate.md | 18 +++++ Resources/Documentation/Gem/pix_crop.md | 39 ++++++++++ Resources/Documentation/Gem/pix_curve.md | 25 +++++++ Resources/Documentation/Gem/pix_data.md | 43 +++++++++++ .../Documentation/Gem/pix_deinterlace.md | 19 +++++ Resources/Documentation/Gem/pix_delay.md | 20 ++++++ Resources/Documentation/Gem/pix_diff.md | 20 ++++++ Resources/Documentation/Gem/pix_dot.md | 21 ++++++ Resources/Documentation/Gem/pix_draw.md | 16 +++++ Resources/Documentation/Gem/pix_dump.md | 24 +++++++ Resources/Documentation/Gem/pix_duotone.md | 24 +++++++ Resources/Documentation/Gem/pix_equal.md | 22 ++++++ Resources/Documentation/Gem/pix_film.md | 39 ++++++++++ Resources/Documentation/Gem/pix_flip.md | 28 ++++++++ Resources/Documentation/Gem/pix_freeframe.md | 27 +++++++ Resources/Documentation/Gem/pix_frei0r.md | 27 +++++++ Resources/Documentation/Gem/pix_gain.md | 24 +++++++ Resources/Documentation/Gem/pix_grey.md | 20 ++++++ Resources/Documentation/Gem/pix_halftone.md | 27 +++++++ Resources/Documentation/Gem/pix_histo.md | 25 +++++++ Resources/Documentation/Gem/pix_hsv2rgb.md | 17 +++++ Resources/Documentation/Gem/pix_image.md | 24 +++++++ .../Documentation/Gem/pix_imageInPlace.md | 29 ++++++++ Resources/Documentation/Gem/pix_info.md | 43 +++++++++++ Resources/Documentation/Gem/pix_invert.md | 18 +++++ .../Documentation/Gem/pix_kaleidoscope.md | 37 ++++++++++ Resources/Documentation/Gem/pix_levels.md | 41 +++++++++++ Resources/Documentation/Gem/pix_lumaoffset.md | 27 +++++++ Resources/Documentation/Gem/pix_mask.md | 18 +++++ Resources/Documentation/Gem/pix_mean_color.md | 19 +++++ Resources/Documentation/Gem/pix_metaimage.md | 30 ++++++++ Resources/Documentation/Gem/pix_mix.md | 32 +++++++++ Resources/Documentation/Gem/pix_motionblur.md | 23 ++++++ Resources/Documentation/Gem/pix_movement.md | 22 ++++++ Resources/Documentation/Gem/pix_movement2.md | 30 ++++++++ Resources/Documentation/Gem/pix_movie.md | 29 ++++++++ Resources/Documentation/Gem/pix_multiblob.md | 27 +++++++ Resources/Documentation/Gem/pix_multiimage.md | 25 +++++++ Resources/Documentation/Gem/pix_multiply.md | 19 +++++ .../Documentation/Gem/pix_multitexture.md | 23 ++++++ Resources/Documentation/Gem/pix_noise.md | 36 ++++++++++ Resources/Documentation/Gem/pix_normalize.md | 19 +++++ Resources/Documentation/Gem/pix_offset.md | 24 +++++++ Resources/Documentation/Gem/pix_posterize.md | 25 +++++++ Resources/Documentation/Gem/pix_puzzle.md | 25 +++++++ Resources/Documentation/Gem/pix_rds.md | 25 +++++++ Resources/Documentation/Gem/pix_record.md | 40 +++++++++++ Resources/Documentation/Gem/pix_rectangle.md | 22 ++++++ Resources/Documentation/Gem/pix_refraction.md | 28 ++++++++ Resources/Documentation/Gem/pix_resize.md | 24 +++++++ Resources/Documentation/Gem/pix_rgb2hsv.md | 18 +++++ Resources/Documentation/Gem/pix_rgba.md | 18 +++++ Resources/Documentation/Gem/pix_roi.md | 28 ++++++++ Resources/Documentation/Gem/pix_roll.md | 22 ++++++ Resources/Documentation/Gem/pix_rtx.md | 22 ++++++ Resources/Documentation/Gem/pix_scanline.md | 23 ++++++ Resources/Documentation/Gem/pix_set.md | 37 ++++++++++ Resources/Documentation/Gem/pix_share_read.md | 32 +++++++++ .../Documentation/Gem/pix_share_write.md | 31 ++++++++ Resources/Documentation/Gem/pix_snap.md | 36 ++++++++++ Resources/Documentation/Gem/pix_snap2tex.md | 38 ++++++++++ Resources/Documentation/Gem/pix_subtract.md | 19 +++++ Resources/Documentation/Gem/pix_tIIR.md | 24 +++++++ Resources/Documentation/Gem/pix_takealpha.md | 21 ++++++ Resources/Documentation/Gem/pix_texture.md | 42 +++++++++++ Resources/Documentation/Gem/pix_threshold.md | 24 +++++++ .../Gem/pix_threshold_bernsen.md | 33 +++++++++ Resources/Documentation/Gem/pix_video.md | 39 ++++++++++ Resources/Documentation/Gem/pix_write.md | 29 ++++++++ Resources/Documentation/Gem/pix_writer.md | 24 +++++++ Resources/Documentation/Gem/pix_yuv.md | 18 +++++ Resources/Documentation/Gem/pix_zoom.md | 20 ++++++ Resources/Documentation/Gem/polygon.md | 19 +++++ Resources/Documentation/Gem/polygon_smooth.md | 17 +++++ Resources/Documentation/Gem/pqtorusknots.md | 33 +++++++++ Resources/Documentation/Gem/primTri.md | 40 +++++++++++ Resources/Documentation/Gem/rectangle.md | 34 +++++++++ Resources/Documentation/Gem/render_trigger.md | 21 ++++++ Resources/Documentation/Gem/rgb2hsv.md | 16 +++++ Resources/Documentation/Gem/rgb2yuv.md | 16 +++++ Resources/Documentation/Gem/ripple.md | 34 +++++++++ Resources/Documentation/Gem/rotate.md | 26 +++++++ Resources/Documentation/Gem/rotateXYZ.md | 31 ++++++++ Resources/Documentation/Gem/rubber.md | 41 +++++++++++ Resources/Documentation/Gem/scale.md | 26 +++++++ Resources/Documentation/Gem/scaleXYZ.md | 31 ++++++++ Resources/Documentation/Gem/scopeXYZ~.md | 35 +++++++++ Resources/Documentation/Gem/separator.md | 19 +++++ Resources/Documentation/Gem/shearXY.md | 22 ++++++ Resources/Documentation/Gem/shearXZ.md | 22 ++++++ Resources/Documentation/Gem/shearYX.md | 22 ++++++ Resources/Documentation/Gem/shearYZ.md | 22 ++++++ Resources/Documentation/Gem/shearZX.md | 22 ++++++ Resources/Documentation/Gem/shearZY.md | 22 ++++++ Resources/Documentation/Gem/shininess.md | 23 ++++++ Resources/Documentation/Gem/slideSquares.md | 31 ++++++++ Resources/Documentation/Gem/specular.md | 22 ++++++ Resources/Documentation/Gem/specularRGB.md | 31 ++++++++ Resources/Documentation/Gem/sphere.md | 27 +++++++ Resources/Documentation/Gem/sphere3d.md | 34 +++++++++ Resources/Documentation/Gem/spline_path.md | 24 +++++++ Resources/Documentation/Gem/spot_light.md | 32 +++++++++ Resources/Documentation/Gem/square.md | 23 ++++++ Resources/Documentation/Gem/surface3d.md | 35 +++++++++ Resources/Documentation/Gem/teapot.md | 40 +++++++++++ Resources/Documentation/Gem/text2d.md | 30 ++++++++ Resources/Documentation/Gem/text3d.md | 36 ++++++++++ Resources/Documentation/Gem/textoutline.md | 33 +++++++++ Resources/Documentation/Gem/todo/Gem.md | 0 Resources/Documentation/Gem/todo/camera.md | 0 Resources/Documentation/Gem/todo/pix_depot.md | 0 .../Documentation/Gem/todo/pix_emboss.md | 0 Resources/Documentation/Gem/todo/pix_get.md | 0 Resources/Documentation/Gem/todo/pix_put.md | 0 .../Documentation/Gem/todo/pix_separator.md | 0 Resources/Documentation/Gem/todo/pix_test.md | 0 .../Documentation/Gem/todo/pix_vpaint.md | 0 .../Documentation/Gem/todo/vertex_info.md | 0 Resources/Documentation/Gem/torus.md | 46 ++++++++++++ Resources/Documentation/Gem/translate.md | 27 +++++++ Resources/Documentation/Gem/translateXYZ.md | 33 +++++++++ Resources/Documentation/Gem/trapezoid.md | 26 +++++++ Resources/Documentation/Gem/triangle.md | 22 ++++++ Resources/Documentation/Gem/tube.md | 71 +++++++++++++++++++ Resources/Documentation/Gem/vertex_add.md | 25 +++++++ Resources/Documentation/Gem/vertex_combine.md | 21 ++++++ Resources/Documentation/Gem/vertex_draw.md | 18 +++++ Resources/Documentation/Gem/vertex_grid.md | 21 ++++++ Resources/Documentation/Gem/vertex_mul.md | 25 +++++++ Resources/Documentation/Gem/vertex_offset.md | 24 +++++++ Resources/Documentation/Gem/vertex_program.md | 18 +++++ Resources/Documentation/Gem/vertex_quad.md | 18 +++++ Resources/Documentation/Gem/vertex_scale.md | 24 +++++++ Resources/Documentation/Gem/vertex_set.md | 24 +++++++ Resources/Documentation/Gem/vertex_tabread.md | 24 +++++++ Resources/Documentation/Gem/world_light.md | 22 ++++++ Resources/Documentation/Gem/yuv2rgb.md | 16 +++++ 539 files changed, 14198 insertions(+) create mode 100644 Resources/Documentation/Gem/GEMglAccum.md create mode 100644 Resources/Documentation/Gem/GEMglActiveTexture.md create mode 100644 Resources/Documentation/Gem/GEMglActiveTextureARB.md create mode 100644 Resources/Documentation/Gem/GEMglAlphaFunc.md create mode 100644 Resources/Documentation/Gem/GEMglAreTexturesResident.md create mode 100644 Resources/Documentation/Gem/GEMglArrayElement.md create mode 100644 Resources/Documentation/Gem/GEMglBegin.md create mode 100644 Resources/Documentation/Gem/GEMglBindProgramARB.md create mode 100644 Resources/Documentation/Gem/GEMglBindTexture.md create mode 100644 Resources/Documentation/Gem/GEMglBitmap.md create mode 100644 Resources/Documentation/Gem/GEMglBlendEquation.md create mode 100644 Resources/Documentation/Gem/GEMglBlendFunc.md create mode 100644 Resources/Documentation/Gem/GEMglCallList.md create mode 100644 Resources/Documentation/Gem/GEMglClear.md create mode 100644 Resources/Documentation/Gem/GEMglClearAccum.md create mode 100644 Resources/Documentation/Gem/GEMglClearColor.md create mode 100644 Resources/Documentation/Gem/GEMglClearDepth.md create mode 100644 Resources/Documentation/Gem/GEMglClearIndex.md create mode 100644 Resources/Documentation/Gem/GEMglClearStencil.md create mode 100644 Resources/Documentation/Gem/GEMglClipPlane.md create mode 100644 Resources/Documentation/Gem/GEMglColor3b.md create mode 100644 Resources/Documentation/Gem/GEMglColor3bv.md create mode 100644 Resources/Documentation/Gem/GEMglColor3d.md create mode 100644 Resources/Documentation/Gem/GEMglColor3dv.md create mode 100644 Resources/Documentation/Gem/GEMglColor3f.md create mode 100644 Resources/Documentation/Gem/GEMglColor3fv.md create mode 100644 Resources/Documentation/Gem/GEMglColor3i.md create mode 100644 Resources/Documentation/Gem/GEMglColor3iv.md create mode 100644 Resources/Documentation/Gem/GEMglColor3s.md create mode 100644 Resources/Documentation/Gem/GEMglColor3sv.md create mode 100644 Resources/Documentation/Gem/GEMglColor3ub.md create mode 100644 Resources/Documentation/Gem/GEMglColor3ubv.md create mode 100644 Resources/Documentation/Gem/GEMglColor3ui.md create mode 100644 Resources/Documentation/Gem/GEMglColor3uiv.md create mode 100644 Resources/Documentation/Gem/GEMglColor3us.md create mode 100644 Resources/Documentation/Gem/GEMglColor3usv.md create mode 100644 Resources/Documentation/Gem/GEMglColor4b.md create mode 100644 Resources/Documentation/Gem/GEMglColor4bv.md create mode 100644 Resources/Documentation/Gem/GEMglColor4d.md create mode 100644 Resources/Documentation/Gem/GEMglColor4dv.md create mode 100644 Resources/Documentation/Gem/GEMglColor4f.md create mode 100644 Resources/Documentation/Gem/GEMglColor4fv.md create mode 100644 Resources/Documentation/Gem/GEMglColor4i.md create mode 100644 Resources/Documentation/Gem/GEMglColor4iv.md create mode 100644 Resources/Documentation/Gem/GEMglColor4s.md create mode 100644 Resources/Documentation/Gem/GEMglColor4sv.md create mode 100644 Resources/Documentation/Gem/GEMglColor4ub.md create mode 100644 Resources/Documentation/Gem/GEMglColor4ubv.md create mode 100644 Resources/Documentation/Gem/GEMglColor4ui.md create mode 100644 Resources/Documentation/Gem/GEMglColor4uiv.md create mode 100644 Resources/Documentation/Gem/GEMglColor4us.md create mode 100644 Resources/Documentation/Gem/GEMglColor4usv.md create mode 100644 Resources/Documentation/Gem/GEMglColorMask.md create mode 100644 Resources/Documentation/Gem/GEMglColorMaterial.md create mode 100644 Resources/Documentation/Gem/GEMglCopyPixels.md create mode 100644 Resources/Documentation/Gem/GEMglCopyTexImage1D.md create mode 100644 Resources/Documentation/Gem/GEMglCopyTexImage2D.md create mode 100644 Resources/Documentation/Gem/GEMglCopyTexSubImage1D.md create mode 100644 Resources/Documentation/Gem/GEMglCopyTexSubImage2D.md create mode 100644 Resources/Documentation/Gem/GEMglCullFace.md create mode 100644 Resources/Documentation/Gem/GEMglDeleteTextures.md create mode 100644 Resources/Documentation/Gem/GEMglDepthFunc.md create mode 100644 Resources/Documentation/Gem/GEMglDepthMask.md create mode 100644 Resources/Documentation/Gem/GEMglDepthRange.md create mode 100644 Resources/Documentation/Gem/GEMglDisable.md create mode 100644 Resources/Documentation/Gem/GEMglDisableClientState.md create mode 100644 Resources/Documentation/Gem/GEMglDrawArrays.md create mode 100644 Resources/Documentation/Gem/GEMglDrawBuffer.md create mode 100644 Resources/Documentation/Gem/GEMglDrawElements.md create mode 100644 Resources/Documentation/Gem/GEMglEdgeFlag.md create mode 100644 Resources/Documentation/Gem/GEMglEnable.md create mode 100644 Resources/Documentation/Gem/GEMglEnableClientState.md create mode 100644 Resources/Documentation/Gem/GEMglEnd.md create mode 100644 Resources/Documentation/Gem/GEMglEndList.md create mode 100644 Resources/Documentation/Gem/GEMglEvalCoord1d.md create mode 100644 Resources/Documentation/Gem/GEMglEvalCoord1dv.md create mode 100644 Resources/Documentation/Gem/GEMglEvalCoord1f.md create mode 100644 Resources/Documentation/Gem/GEMglEvalCoord1fv.md create mode 100644 Resources/Documentation/Gem/GEMglEvalCoord2d.md create mode 100644 Resources/Documentation/Gem/GEMglEvalCoord2dv.md create mode 100644 Resources/Documentation/Gem/GEMglEvalCoord2f.md create mode 100644 Resources/Documentation/Gem/GEMglEvalCoord2fv.md create mode 100644 Resources/Documentation/Gem/GEMglEvalMesh1.md create mode 100644 Resources/Documentation/Gem/GEMglEvalMesh2.md create mode 100644 Resources/Documentation/Gem/GEMglEvalPoint1.md create mode 100644 Resources/Documentation/Gem/GEMglEvalPoint2.md create mode 100644 Resources/Documentation/Gem/GEMglFeedbackBuffer.md create mode 100644 Resources/Documentation/Gem/GEMglFinish.md create mode 100644 Resources/Documentation/Gem/GEMglFlush.md create mode 100644 Resources/Documentation/Gem/GEMglFogf.md create mode 100644 Resources/Documentation/Gem/GEMglFogfv.md create mode 100644 Resources/Documentation/Gem/GEMglFogi.md create mode 100644 Resources/Documentation/Gem/GEMglFogiv.md create mode 100644 Resources/Documentation/Gem/GEMglFrontFace.md create mode 100644 Resources/Documentation/Gem/GEMglFrustum.md create mode 100644 Resources/Documentation/Gem/GEMglGenLists.md create mode 100644 Resources/Documentation/Gem/GEMglGenProgramsARB.md create mode 100644 Resources/Documentation/Gem/GEMglGenTextures.md create mode 100644 Resources/Documentation/Gem/GEMglGenerateMipmap.md create mode 100644 Resources/Documentation/Gem/GEMglGetError.md create mode 100644 Resources/Documentation/Gem/GEMglGetFloatv.md create mode 100644 Resources/Documentation/Gem/GEMglGetIntegerv.md create mode 100644 Resources/Documentation/Gem/GEMglGetMapdv.md create mode 100644 Resources/Documentation/Gem/GEMglGetMapfv.md create mode 100644 Resources/Documentation/Gem/GEMglGetMapiv.md create mode 100644 Resources/Documentation/Gem/GEMglGetPointerv.md create mode 100644 Resources/Documentation/Gem/GEMglGetString.md create mode 100644 Resources/Documentation/Gem/GEMglHint.md create mode 100644 Resources/Documentation/Gem/GEMglIndexMask.md create mode 100644 Resources/Documentation/Gem/GEMglIndexd.md create mode 100644 Resources/Documentation/Gem/GEMglIndexdv.md create mode 100644 Resources/Documentation/Gem/GEMglIndexf.md create mode 100644 Resources/Documentation/Gem/GEMglIndexfv.md create mode 100644 Resources/Documentation/Gem/GEMglIndexi.md create mode 100644 Resources/Documentation/Gem/GEMglIndexiv.md create mode 100644 Resources/Documentation/Gem/GEMglIndexs.md create mode 100644 Resources/Documentation/Gem/GEMglIndexsv.md create mode 100644 Resources/Documentation/Gem/GEMglIndexub.md create mode 100644 Resources/Documentation/Gem/GEMglIndexubv.md create mode 100644 Resources/Documentation/Gem/GEMglInitNames.md create mode 100644 Resources/Documentation/Gem/GEMglIsEnabled.md create mode 100644 Resources/Documentation/Gem/GEMglIsList.md create mode 100644 Resources/Documentation/Gem/GEMglIsTexture.md create mode 100644 Resources/Documentation/Gem/GEMglLightModelf.md create mode 100644 Resources/Documentation/Gem/GEMglLightModeli.md create mode 100644 Resources/Documentation/Gem/GEMglLightf.md create mode 100644 Resources/Documentation/Gem/GEMglLighti.md create mode 100644 Resources/Documentation/Gem/GEMglLineStipple.md create mode 100644 Resources/Documentation/Gem/GEMglLineWidth.md create mode 100644 Resources/Documentation/Gem/GEMglLoadIdentity.md create mode 100644 Resources/Documentation/Gem/GEMglLoadMatrixd.md create mode 100644 Resources/Documentation/Gem/GEMglLoadMatrixf.md create mode 100644 Resources/Documentation/Gem/GEMglLoadName.md create mode 100644 Resources/Documentation/Gem/GEMglLoadTransposeMatrixd.md create mode 100644 Resources/Documentation/Gem/GEMglLoadTransposeMatrixf.md create mode 100644 Resources/Documentation/Gem/GEMglLogicOp.md create mode 100644 Resources/Documentation/Gem/GEMglMap1d.md create mode 100644 Resources/Documentation/Gem/GEMglMap1f.md create mode 100644 Resources/Documentation/Gem/GEMglMap2d.md create mode 100644 Resources/Documentation/Gem/GEMglMap2f.md create mode 100644 Resources/Documentation/Gem/GEMglMapGrid1d.md create mode 100644 Resources/Documentation/Gem/GEMglMapGrid1f.md create mode 100644 Resources/Documentation/Gem/GEMglMapGrid2d.md create mode 100644 Resources/Documentation/Gem/GEMglMapGrid2f.md create mode 100644 Resources/Documentation/Gem/GEMglMaterialf.md create mode 100644 Resources/Documentation/Gem/GEMglMaterialfv.md create mode 100644 Resources/Documentation/Gem/GEMglMateriali.md create mode 100644 Resources/Documentation/Gem/GEMglMatrixMode.md create mode 100644 Resources/Documentation/Gem/GEMglMultMatrixd.md create mode 100644 Resources/Documentation/Gem/GEMglMultMatrixf.md create mode 100644 Resources/Documentation/Gem/GEMglMultTransposeMatrixf.md create mode 100644 Resources/Documentation/Gem/GEMglMultiTexCoord2f.md create mode 100644 Resources/Documentation/Gem/GEMglMultiTexCoord2fARB.md create mode 100644 Resources/Documentation/Gem/GEMglNewList.md create mode 100644 Resources/Documentation/Gem/GEMglNormal3b.md create mode 100644 Resources/Documentation/Gem/GEMglNormal3bv.md create mode 100644 Resources/Documentation/Gem/GEMglNormal3d.md create mode 100644 Resources/Documentation/Gem/GEMglNormal3dv.md create mode 100644 Resources/Documentation/Gem/GEMglNormal3f.md create mode 100644 Resources/Documentation/Gem/GEMglNormal3fv.md create mode 100644 Resources/Documentation/Gem/GEMglNormal3i.md create mode 100644 Resources/Documentation/Gem/GEMglNormal3iv.md create mode 100644 Resources/Documentation/Gem/GEMglNormal3s.md create mode 100644 Resources/Documentation/Gem/GEMglNormal3sv.md create mode 100644 Resources/Documentation/Gem/GEMglOrtho.md create mode 100644 Resources/Documentation/Gem/GEMglPassThrough.md create mode 100644 Resources/Documentation/Gem/GEMglPixelStoref.md create mode 100644 Resources/Documentation/Gem/GEMglPixelStorei.md create mode 100644 Resources/Documentation/Gem/GEMglPixelTransferf.md create mode 100644 Resources/Documentation/Gem/GEMglPixelTransferi.md create mode 100644 Resources/Documentation/Gem/GEMglPixelZoom.md create mode 100644 Resources/Documentation/Gem/GEMglPointSize.md create mode 100644 Resources/Documentation/Gem/GEMglPolygonMode.md create mode 100644 Resources/Documentation/Gem/GEMglPolygonOffset.md create mode 100644 Resources/Documentation/Gem/GEMglPopAttrib.md create mode 100644 Resources/Documentation/Gem/GEMglPopClientAttrib.md create mode 100644 Resources/Documentation/Gem/GEMglPopMatrix.md create mode 100644 Resources/Documentation/Gem/GEMglPopName.md create mode 100644 Resources/Documentation/Gem/GEMglPrioritizeTextures.md create mode 100644 Resources/Documentation/Gem/GEMglProgramEnvParameter4dARB.md create mode 100644 Resources/Documentation/Gem/GEMglProgramEnvParameter4fvARB.md create mode 100644 Resources/Documentation/Gem/GEMglProgramLocalParameter4fvARB.md create mode 100644 Resources/Documentation/Gem/GEMglProgramStringARB.md create mode 100644 Resources/Documentation/Gem/GEMglPushAttrib.md create mode 100644 Resources/Documentation/Gem/GEMglPushClientAttrib.md create mode 100644 Resources/Documentation/Gem/GEMglPushMatrix.md create mode 100644 Resources/Documentation/Gem/GEMglPushName.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos2d.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos2dv.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos2f.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos2fv.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos2i.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos2iv.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos2s.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos2sv.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos3d.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos3dv.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos3f.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos3fv.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos3i.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos3iv.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos3s.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos3sv.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos4d.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos4dv.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos4f.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos4fv.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos4i.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos4iv.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos4s.md create mode 100644 Resources/Documentation/Gem/GEMglRasterPos4sv.md create mode 100644 Resources/Documentation/Gem/GEMglRectd.md create mode 100644 Resources/Documentation/Gem/GEMglRectf.md create mode 100644 Resources/Documentation/Gem/GEMglRecti.md create mode 100644 Resources/Documentation/Gem/GEMglRects.md create mode 100644 Resources/Documentation/Gem/GEMglRenderMode.md create mode 100644 Resources/Documentation/Gem/GEMglReportError.md create mode 100644 Resources/Documentation/Gem/GEMglRotated.md create mode 100644 Resources/Documentation/Gem/GEMglRotatef.md create mode 100644 Resources/Documentation/Gem/GEMglScaled.md create mode 100644 Resources/Documentation/Gem/GEMglScalef.md create mode 100644 Resources/Documentation/Gem/GEMglScissor.md create mode 100644 Resources/Documentation/Gem/GEMglSelectBuffer.md create mode 100644 Resources/Documentation/Gem/GEMglShadeModel.md create mode 100644 Resources/Documentation/Gem/GEMglStencilFunc.md create mode 100644 Resources/Documentation/Gem/GEMglStencilMask.md create mode 100644 Resources/Documentation/Gem/GEMglStencilOp.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord1d.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord1dv.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord1f.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord1fv.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord1i.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord1iv.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord1s.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord1sv.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord2d.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord2dv.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord2f.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord2fv.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord2i.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord2iv.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord2s.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord2sv.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord3d.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord3dv.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord3f.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord3fv.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord3i.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord3iv.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord3s.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord3sv.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord4d.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord4dv.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord4f.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord4fv.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord4i.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord4iv.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord4s.md create mode 100644 Resources/Documentation/Gem/GEMglTexCoord4sv.md create mode 100644 Resources/Documentation/Gem/GEMglTexEnvf.md create mode 100644 Resources/Documentation/Gem/GEMglTexEnvi.md create mode 100644 Resources/Documentation/Gem/GEMglTexGend.md create mode 100644 Resources/Documentation/Gem/GEMglTexGenf.md create mode 100644 Resources/Documentation/Gem/GEMglTexGenfv.md create mode 100644 Resources/Documentation/Gem/GEMglTexGeni.md create mode 100644 Resources/Documentation/Gem/GEMglTexImage2D.md create mode 100644 Resources/Documentation/Gem/GEMglTexParameterf.md create mode 100644 Resources/Documentation/Gem/GEMglTexParameteri.md create mode 100644 Resources/Documentation/Gem/GEMglTexSubImage1D.md create mode 100644 Resources/Documentation/Gem/GEMglTexSubImage2D.md create mode 100644 Resources/Documentation/Gem/GEMglTranslated.md create mode 100644 Resources/Documentation/Gem/GEMglTranslatef.md create mode 100644 Resources/Documentation/Gem/GEMglUniform1f.md create mode 100644 Resources/Documentation/Gem/GEMglUniform1fARB.md create mode 100644 Resources/Documentation/Gem/GEMglUseProgramObjectARB.md create mode 100644 Resources/Documentation/Gem/GEMglVertex2d.md create mode 100644 Resources/Documentation/Gem/GEMglVertex2dv.md create mode 100644 Resources/Documentation/Gem/GEMglVertex2f.md create mode 100644 Resources/Documentation/Gem/GEMglVertex2fv.md create mode 100644 Resources/Documentation/Gem/GEMglVertex2i.md create mode 100644 Resources/Documentation/Gem/GEMglVertex2iv.md create mode 100644 Resources/Documentation/Gem/GEMglVertex2s.md create mode 100644 Resources/Documentation/Gem/GEMglVertex2sv.md create mode 100644 Resources/Documentation/Gem/GEMglVertex3d.md create mode 100644 Resources/Documentation/Gem/GEMglVertex3dv.md create mode 100644 Resources/Documentation/Gem/GEMglVertex3f.md create mode 100644 Resources/Documentation/Gem/GEMglVertex3fv.md create mode 100644 Resources/Documentation/Gem/GEMglVertex3i.md create mode 100644 Resources/Documentation/Gem/GEMglVertex3iv.md create mode 100644 Resources/Documentation/Gem/GEMglVertex3s.md create mode 100644 Resources/Documentation/Gem/GEMglVertex3sv.md create mode 100644 Resources/Documentation/Gem/GEMglVertex4d.md create mode 100644 Resources/Documentation/Gem/GEMglVertex4dv.md create mode 100644 Resources/Documentation/Gem/GEMglVertex4f.md create mode 100644 Resources/Documentation/Gem/GEMglVertex4fv.md create mode 100644 Resources/Documentation/Gem/GEMglVertex4i.md create mode 100644 Resources/Documentation/Gem/GEMglVertex4iv.md create mode 100644 Resources/Documentation/Gem/GEMglVertex4s.md create mode 100644 Resources/Documentation/Gem/GEMglVertex4sv.md create mode 100644 Resources/Documentation/Gem/GEMglViewport.md create mode 100644 Resources/Documentation/Gem/GEMgluLookAt.md create mode 100644 Resources/Documentation/Gem/GEMgluPerspective.md create mode 100644 Resources/Documentation/Gem/GLdefine.md create mode 100644 Resources/Documentation/Gem/accumrotate.md create mode 100644 Resources/Documentation/Gem/alpha.md create mode 100644 Resources/Documentation/Gem/ambient.md create mode 100644 Resources/Documentation/Gem/ambientRGB.md create mode 100644 Resources/Documentation/Gem/circle.md create mode 100644 Resources/Documentation/Gem/color.md create mode 100644 Resources/Documentation/Gem/colorRGB.md create mode 100644 Resources/Documentation/Gem/colorSquare.md create mode 100644 Resources/Documentation/Gem/cone.md create mode 100644 Resources/Documentation/Gem/cube.md create mode 100644 Resources/Documentation/Gem/cuboid.md create mode 100644 Resources/Documentation/Gem/curve.md create mode 100644 Resources/Documentation/Gem/curve3d.md create mode 100644 Resources/Documentation/Gem/cylinder.md create mode 100644 Resources/Documentation/Gem/depth.md create mode 100644 Resources/Documentation/Gem/diffuse.md create mode 100644 Resources/Documentation/Gem/diffuseRGB.md create mode 100644 Resources/Documentation/Gem/disk.md create mode 100644 Resources/Documentation/Gem/emission.md create mode 100644 Resources/Documentation/Gem/emissionRGB.md create mode 100644 Resources/Documentation/Gem/fragment_program.md create mode 100644 Resources/Documentation/Gem/gemargs.md create mode 100644 Resources/Documentation/Gem/gemcubeframebuffer.md create mode 100644 Resources/Documentation/Gem/gemframebuffer.md create mode 100644 Resources/Documentation/Gem/gemhead.md create mode 100644 Resources/Documentation/Gem/gemkeyboard.md create mode 100644 Resources/Documentation/Gem/gemkeyname.md create mode 100644 Resources/Documentation/Gem/gemlist.md create mode 100644 Resources/Documentation/Gem/gemlist_info.md create mode 100644 Resources/Documentation/Gem/gemlist_matrix.md create mode 100644 Resources/Documentation/Gem/gemmanager.md create mode 100644 Resources/Documentation/Gem/gemmouse.md create mode 100644 Resources/Documentation/Gem/gemreceive.md create mode 100644 Resources/Documentation/Gem/gemrepeat.md create mode 100644 Resources/Documentation/Gem/gemvertexbuffer.md create mode 100644 Resources/Documentation/Gem/gemwin.md create mode 100644 Resources/Documentation/Gem/glsl_fragment.md create mode 100644 Resources/Documentation/Gem/glsl_geometry.md create mode 100644 Resources/Documentation/Gem/glsl_program.md create mode 100644 Resources/Documentation/Gem/glsl_vertex.md create mode 100644 Resources/Documentation/Gem/hsv2rgb.md create mode 100644 Resources/Documentation/Gem/imageVert.md create mode 100644 Resources/Documentation/Gem/light.md create mode 100644 Resources/Documentation/Gem/linear_path.md create mode 100644 Resources/Documentation/Gem/mesh_line.md create mode 100644 Resources/Documentation/Gem/mesh_square.md create mode 100644 Resources/Documentation/Gem/model.md create mode 100644 Resources/Documentation/Gem/multimodel.md create mode 100644 Resources/Documentation/Gem/newWave.md create mode 100644 Resources/Documentation/Gem/ortho.md create mode 100644 Resources/Documentation/Gem/part_color.md create mode 100644 Resources/Documentation/Gem/part_damp.md create mode 100644 Resources/Documentation/Gem/part_draw.md create mode 100644 Resources/Documentation/Gem/part_follow.md create mode 100644 Resources/Documentation/Gem/part_gravity.md create mode 100644 Resources/Documentation/Gem/part_head.md create mode 100644 Resources/Documentation/Gem/part_info.md create mode 100644 Resources/Documentation/Gem/part_killold.md create mode 100644 Resources/Documentation/Gem/part_killslow.md create mode 100644 Resources/Documentation/Gem/part_orbitpoint.md create mode 100644 Resources/Documentation/Gem/part_render.md create mode 100644 Resources/Documentation/Gem/part_sink.md create mode 100644 Resources/Documentation/Gem/part_size.md create mode 100644 Resources/Documentation/Gem/part_source.md create mode 100644 Resources/Documentation/Gem/part_targetcolor.md create mode 100644 Resources/Documentation/Gem/part_targetsize.md create mode 100644 Resources/Documentation/Gem/part_velcone.md create mode 100644 Resources/Documentation/Gem/part_velocity.md create mode 100644 Resources/Documentation/Gem/part_velsphere.md create mode 100644 Resources/Documentation/Gem/part_vertex.md create mode 100644 Resources/Documentation/Gem/pix_2grey.md create mode 100644 Resources/Documentation/Gem/pix_a_2grey.md create mode 100644 Resources/Documentation/Gem/pix_add.md create mode 100644 Resources/Documentation/Gem/pix_aging.md create mode 100644 Resources/Documentation/Gem/pix_alpha.md create mode 100644 Resources/Documentation/Gem/pix_background.md create mode 100644 Resources/Documentation/Gem/pix_backlight.md create mode 100644 Resources/Documentation/Gem/pix_biquad.md create mode 100644 Resources/Documentation/Gem/pix_bitmask.md create mode 100644 Resources/Documentation/Gem/pix_blob.md create mode 100644 Resources/Documentation/Gem/pix_blobtracker.md create mode 100644 Resources/Documentation/Gem/pix_blur.md create mode 100644 Resources/Documentation/Gem/pix_buf.md create mode 100644 Resources/Documentation/Gem/pix_buffer.md create mode 100644 Resources/Documentation/Gem/pix_buffer_filmopen.md create mode 100644 Resources/Documentation/Gem/pix_buffer_read.md create mode 100644 Resources/Documentation/Gem/pix_buffer_write.md create mode 100644 Resources/Documentation/Gem/pix_chroma_key.md create mode 100644 Resources/Documentation/Gem/pix_clearblock.md create mode 100644 Resources/Documentation/Gem/pix_color.md create mode 100644 Resources/Documentation/Gem/pix_coloralpha.md create mode 100644 Resources/Documentation/Gem/pix_colorclassify.md create mode 100644 Resources/Documentation/Gem/pix_colormatrix.md create mode 100644 Resources/Documentation/Gem/pix_colorreduce.md create mode 100644 Resources/Documentation/Gem/pix_compare.md create mode 100644 Resources/Documentation/Gem/pix_composite.md create mode 100644 Resources/Documentation/Gem/pix_contrast.md create mode 100644 Resources/Documentation/Gem/pix_convert.md create mode 100644 Resources/Documentation/Gem/pix_convolve.md create mode 100644 Resources/Documentation/Gem/pix_coordinate.md create mode 100644 Resources/Documentation/Gem/pix_crop.md create mode 100644 Resources/Documentation/Gem/pix_curve.md create mode 100644 Resources/Documentation/Gem/pix_data.md create mode 100644 Resources/Documentation/Gem/pix_deinterlace.md create mode 100644 Resources/Documentation/Gem/pix_delay.md create mode 100644 Resources/Documentation/Gem/pix_diff.md create mode 100644 Resources/Documentation/Gem/pix_dot.md create mode 100644 Resources/Documentation/Gem/pix_draw.md create mode 100644 Resources/Documentation/Gem/pix_dump.md create mode 100644 Resources/Documentation/Gem/pix_duotone.md create mode 100644 Resources/Documentation/Gem/pix_equal.md create mode 100644 Resources/Documentation/Gem/pix_film.md create mode 100644 Resources/Documentation/Gem/pix_flip.md create mode 100644 Resources/Documentation/Gem/pix_freeframe.md create mode 100644 Resources/Documentation/Gem/pix_frei0r.md create mode 100644 Resources/Documentation/Gem/pix_gain.md create mode 100644 Resources/Documentation/Gem/pix_grey.md create mode 100644 Resources/Documentation/Gem/pix_halftone.md create mode 100644 Resources/Documentation/Gem/pix_histo.md create mode 100644 Resources/Documentation/Gem/pix_hsv2rgb.md create mode 100644 Resources/Documentation/Gem/pix_image.md create mode 100644 Resources/Documentation/Gem/pix_imageInPlace.md create mode 100644 Resources/Documentation/Gem/pix_info.md create mode 100644 Resources/Documentation/Gem/pix_invert.md create mode 100644 Resources/Documentation/Gem/pix_kaleidoscope.md create mode 100644 Resources/Documentation/Gem/pix_levels.md create mode 100644 Resources/Documentation/Gem/pix_lumaoffset.md create mode 100644 Resources/Documentation/Gem/pix_mask.md create mode 100644 Resources/Documentation/Gem/pix_mean_color.md create mode 100644 Resources/Documentation/Gem/pix_metaimage.md create mode 100644 Resources/Documentation/Gem/pix_mix.md create mode 100644 Resources/Documentation/Gem/pix_motionblur.md create mode 100644 Resources/Documentation/Gem/pix_movement.md create mode 100644 Resources/Documentation/Gem/pix_movement2.md create mode 100644 Resources/Documentation/Gem/pix_movie.md create mode 100644 Resources/Documentation/Gem/pix_multiblob.md create mode 100644 Resources/Documentation/Gem/pix_multiimage.md create mode 100644 Resources/Documentation/Gem/pix_multiply.md create mode 100644 Resources/Documentation/Gem/pix_multitexture.md create mode 100644 Resources/Documentation/Gem/pix_noise.md create mode 100644 Resources/Documentation/Gem/pix_normalize.md create mode 100644 Resources/Documentation/Gem/pix_offset.md create mode 100644 Resources/Documentation/Gem/pix_posterize.md create mode 100644 Resources/Documentation/Gem/pix_puzzle.md create mode 100644 Resources/Documentation/Gem/pix_rds.md create mode 100644 Resources/Documentation/Gem/pix_record.md create mode 100644 Resources/Documentation/Gem/pix_rectangle.md create mode 100644 Resources/Documentation/Gem/pix_refraction.md create mode 100644 Resources/Documentation/Gem/pix_resize.md create mode 100644 Resources/Documentation/Gem/pix_rgb2hsv.md create mode 100644 Resources/Documentation/Gem/pix_rgba.md create mode 100644 Resources/Documentation/Gem/pix_roi.md create mode 100644 Resources/Documentation/Gem/pix_roll.md create mode 100644 Resources/Documentation/Gem/pix_rtx.md create mode 100644 Resources/Documentation/Gem/pix_scanline.md create mode 100644 Resources/Documentation/Gem/pix_set.md create mode 100644 Resources/Documentation/Gem/pix_share_read.md create mode 100644 Resources/Documentation/Gem/pix_share_write.md create mode 100644 Resources/Documentation/Gem/pix_snap.md create mode 100644 Resources/Documentation/Gem/pix_snap2tex.md create mode 100644 Resources/Documentation/Gem/pix_subtract.md create mode 100644 Resources/Documentation/Gem/pix_tIIR.md create mode 100644 Resources/Documentation/Gem/pix_takealpha.md create mode 100644 Resources/Documentation/Gem/pix_texture.md create mode 100644 Resources/Documentation/Gem/pix_threshold.md create mode 100644 Resources/Documentation/Gem/pix_threshold_bernsen.md create mode 100644 Resources/Documentation/Gem/pix_video.md create mode 100644 Resources/Documentation/Gem/pix_write.md create mode 100644 Resources/Documentation/Gem/pix_writer.md create mode 100644 Resources/Documentation/Gem/pix_yuv.md create mode 100644 Resources/Documentation/Gem/pix_zoom.md create mode 100644 Resources/Documentation/Gem/polygon.md create mode 100644 Resources/Documentation/Gem/polygon_smooth.md create mode 100644 Resources/Documentation/Gem/pqtorusknots.md create mode 100644 Resources/Documentation/Gem/primTri.md create mode 100644 Resources/Documentation/Gem/rectangle.md create mode 100644 Resources/Documentation/Gem/render_trigger.md create mode 100644 Resources/Documentation/Gem/rgb2hsv.md create mode 100644 Resources/Documentation/Gem/rgb2yuv.md create mode 100644 Resources/Documentation/Gem/ripple.md create mode 100644 Resources/Documentation/Gem/rotate.md create mode 100644 Resources/Documentation/Gem/rotateXYZ.md create mode 100644 Resources/Documentation/Gem/rubber.md create mode 100644 Resources/Documentation/Gem/scale.md create mode 100644 Resources/Documentation/Gem/scaleXYZ.md create mode 100644 Resources/Documentation/Gem/scopeXYZ~.md create mode 100644 Resources/Documentation/Gem/separator.md create mode 100644 Resources/Documentation/Gem/shearXY.md create mode 100644 Resources/Documentation/Gem/shearXZ.md create mode 100644 Resources/Documentation/Gem/shearYX.md create mode 100644 Resources/Documentation/Gem/shearYZ.md create mode 100644 Resources/Documentation/Gem/shearZX.md create mode 100644 Resources/Documentation/Gem/shearZY.md create mode 100644 Resources/Documentation/Gem/shininess.md create mode 100644 Resources/Documentation/Gem/slideSquares.md create mode 100644 Resources/Documentation/Gem/specular.md create mode 100644 Resources/Documentation/Gem/specularRGB.md create mode 100644 Resources/Documentation/Gem/sphere.md create mode 100644 Resources/Documentation/Gem/sphere3d.md create mode 100644 Resources/Documentation/Gem/spline_path.md create mode 100644 Resources/Documentation/Gem/spot_light.md create mode 100644 Resources/Documentation/Gem/square.md create mode 100644 Resources/Documentation/Gem/surface3d.md create mode 100644 Resources/Documentation/Gem/teapot.md create mode 100644 Resources/Documentation/Gem/text2d.md create mode 100644 Resources/Documentation/Gem/text3d.md create mode 100644 Resources/Documentation/Gem/textoutline.md create mode 100644 Resources/Documentation/Gem/todo/Gem.md create mode 100644 Resources/Documentation/Gem/todo/camera.md create mode 100644 Resources/Documentation/Gem/todo/pix_depot.md create mode 100644 Resources/Documentation/Gem/todo/pix_emboss.md create mode 100644 Resources/Documentation/Gem/todo/pix_get.md create mode 100644 Resources/Documentation/Gem/todo/pix_put.md create mode 100644 Resources/Documentation/Gem/todo/pix_separator.md create mode 100644 Resources/Documentation/Gem/todo/pix_test.md create mode 100644 Resources/Documentation/Gem/todo/pix_vpaint.md create mode 100644 Resources/Documentation/Gem/todo/vertex_info.md create mode 100644 Resources/Documentation/Gem/torus.md create mode 100644 Resources/Documentation/Gem/translate.md create mode 100644 Resources/Documentation/Gem/translateXYZ.md create mode 100644 Resources/Documentation/Gem/trapezoid.md create mode 100644 Resources/Documentation/Gem/triangle.md create mode 100644 Resources/Documentation/Gem/tube.md create mode 100644 Resources/Documentation/Gem/vertex_add.md create mode 100644 Resources/Documentation/Gem/vertex_combine.md create mode 100644 Resources/Documentation/Gem/vertex_draw.md create mode 100644 Resources/Documentation/Gem/vertex_grid.md create mode 100644 Resources/Documentation/Gem/vertex_mul.md create mode 100644 Resources/Documentation/Gem/vertex_offset.md create mode 100644 Resources/Documentation/Gem/vertex_program.md create mode 100644 Resources/Documentation/Gem/vertex_quad.md create mode 100644 Resources/Documentation/Gem/vertex_scale.md create mode 100644 Resources/Documentation/Gem/vertex_set.md create mode 100644 Resources/Documentation/Gem/vertex_tabread.md create mode 100644 Resources/Documentation/Gem/world_light.md create mode 100644 Resources/Documentation/Gem/yuv2rgb.md diff --git a/Resources/Documentation/Gem/GEMglAccum.md b/Resources/Documentation/Gem/GEMglAccum.md new file mode 100644 index 0000000000..fec49e958f --- /dev/null +++ b/Resources/Documentation/Gem/GEMglAccum.md @@ -0,0 +1,30 @@ + +--- +title: GEMglAccum +description: perform pixel arithmetic on the accumulation buffer +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the operation to be performed. + default: 0 + - type: float + description: Specifies the value to be used in the operation. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the operation to be performed. + 3rd: + - type: float + description: Specifies the value to be used in the operation. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglActiveTexture.md b/Resources/Documentation/Gem/GEMglActiveTexture.md new file mode 100644 index 0000000000..3c408b2e98 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglActiveTexture.md @@ -0,0 +1,24 @@ + +--- +title: GEMglActiveTexture +description: select active texture unit +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies which texture unit to make active. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies which texture unit to make active. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglActiveTextureARB.md b/Resources/Documentation/Gem/GEMglActiveTextureARB.md new file mode 100644 index 0000000000..247d937bbb --- /dev/null +++ b/Resources/Documentation/Gem/GEMglActiveTextureARB.md @@ -0,0 +1,24 @@ + +--- +title: GEMglActiveTextureARB +description: select active texture unit (ARB extension) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies which texture unit to make active. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies which texture unit to make active. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglAlphaFunc.md b/Resources/Documentation/Gem/GEMglAlphaFunc.md new file mode 100644 index 0000000000..b31baa7d12 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglAlphaFunc.md @@ -0,0 +1,30 @@ + +--- +title: GEMglAlphaFunc +description: set the alpha test function +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the alpha comparison function. + default: 0 + - type: float + description: Specifies the reference value for the alpha test. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the alpha comparison function. + 3rd: + - type: float + description: Specifies the reference value for the alpha test. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglAreTexturesResident.md b/Resources/Documentation/Gem/GEMglAreTexturesResident.md new file mode 100644 index 0000000000..ba2fb623eb --- /dev/null +++ b/Resources/Documentation/Gem/GEMglAreTexturesResident.md @@ -0,0 +1,33 @@ + +--- +title: GEMglAreTexturesResident +description: determine if textures are loaded in texture memory +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the number of texture names to be queried. + default: 0 + - type: float + description: Specifies an array containing the texture names to be queried. + default: [] + - type: float + description: Returns an array indicating whether each corresponding texture is resident. + default: [] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the number of texture names to be queried. + 3rd: + - type: float + description: Specifies an array containing the texture names to be queried. +outlets: + 1st: + - type: gemlist + description: Returns an array indicating whether each corresponding texture is resident. +draft: false + diff --git a/Resources/Documentation/Gem/GEMglArrayElement.md b/Resources/Documentation/Gem/GEMglArrayElement.md new file mode 100644 index 0000000000..58d1f5d4f3 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglArrayElement.md @@ -0,0 +1,24 @@ + +--- +title: GEMglArrayElement +description: render a vertex using vertex arrays +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the index of the vertex in the enabled arrays. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the index of the vertex in the enabled arrays. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglBegin.md b/Resources/Documentation/Gem/GEMglBegin.md new file mode 100644 index 0000000000..7a0c74d378 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglBegin.md @@ -0,0 +1,24 @@ + +--- +title: GEMglBegin +description: delimit the vertices of a primitive or a group of like primitives +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the primitive or group of primitives. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the primitive or group of primitives. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglBindProgramARB.md b/Resources/Documentation/Gem/GEMglBindProgramARB.md new file mode 100644 index 0000000000..082897baec --- /dev/null +++ b/Resources/Documentation/Gem/GEMglBindProgramARB.md @@ -0,0 +1,30 @@ + +--- +title: GEMglBindProgramARB +description: bind a program object (ARB extension) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the program target (e.g., GL_VERTEX_PROGRAM_ARB). + default: 0 + - type: float + description: Specifies the name of the program object to bind. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the program target (e.g., GL_VERTEX_PROGRAM_ARB). + 3rd: + - type: float + description: Specifies the name of the program object to bind. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglBindTexture.md b/Resources/Documentation/Gem/GEMglBindTexture.md new file mode 100644 index 0000000000..edf2903a6b --- /dev/null +++ b/Resources/Documentation/Gem/GEMglBindTexture.md @@ -0,0 +1,30 @@ + +--- +title: GEMglBindTexture +description: bind a named texture to a texturing target +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target to which the texture is bound. + default: 0 + - type: float + description: Specifies the name of a texture. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target to which the texture is bound. + 3rd: + - type: float + description: Specifies the name of a texture. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglBitmap.md b/Resources/Documentation/Gem/GEMglBitmap.md new file mode 100644 index 0000000000..e3d7b19b50 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglBitmap.md @@ -0,0 +1,59 @@ + +--- +title: GEMglBitmap +description: draw a bitmap +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the width of the bitmap image. + default: 0 + - type: float + description: Specifies the height of the bitmap image. + default: 0 + - type: float + description: Specifies the x and y offsets from the current raster position. + default: [0, 0] + - type: float + description: Specifies the x and y scales applied to the bitmap. + default: [1, 1] + - type: float + description: Specifies the left and right boundary values for the bitmap. + default: [0, 0] + - type: float + description: Specifies the minimum and maximum values of the map function. + default: [0, 0] + - type: float* + description: Specifies a pointer to the bitmap image. + default: null +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the width of the bitmap image. + 3rd: + - type: float + description: Specifies the height of the bitmap image. + 4th: + - type: float + description: Specifies the x and y offsets from the current raster position. + 5th: + - type: float + description: Specifies the x and y scales applied to the bitmap. + 6th: + - type: float + description: Specifies the left and right boundary values for the bitmap. + 7th: + - type: float + description: Specifies the minimum and maximum values of the map function. + 8th: + - type: float* + description: Specifies a pointer to the bitmap image. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglBlendEquation.md b/Resources/Documentation/Gem/GEMglBlendEquation.md new file mode 100644 index 0000000000..d72ad2502c --- /dev/null +++ b/Resources/Documentation/Gem/GEMglBlendEquation.md @@ -0,0 +1,24 @@ + +--- +title: GEMglBlendEquation +description: specify the equation used for both the RGB and alpha blending +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the blending equation. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the blending equation. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglBlendFunc.md b/Resources/Documentation/Gem/GEMglBlendFunc.md new file mode 100644 index 0000000000..634b86861f --- /dev/null +++ b/Resources/Documentation/Gem/GEMglBlendFunc.md @@ -0,0 +1,30 @@ + +--- +title: GEMglBlendFunc +description: specify pixel arithmetic for RGB and alpha components +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies how the source blending factor is computed. + default: 0 + - type: float + description: Specifies how the destination blending factor is computed. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies how the source blending factor is computed. + 3rd: + - type: float + description: Specifies how the destination blending factor is computed. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglCallList.md b/Resources/Documentation/Gem/GEMglCallList.md new file mode 100644 index 0000000000..cd89612c84 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglCallList.md @@ -0,0 +1,24 @@ + +--- +title: GEMglCallList +description: execute a display list +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the name of the display list to be executed. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the name of the display list to be executed. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglClear.md b/Resources/Documentation/Gem/GEMglClear.md new file mode 100644 index 0000000000..0d0c93f2ba --- /dev/null +++ b/Resources/Documentation/Gem/GEMglClear.md @@ -0,0 +1,24 @@ + +--- +title: GEMglClear +description: clear buffers to preset values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies which buffers are to be cleared. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies which buffers are to be cleared. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglClearAccum.md b/Resources/Documentation/Gem/GEMglClearAccum.md new file mode 100644 index 0000000000..685b5bd75e --- /dev/null +++ b/Resources/Documentation/Gem/GEMglClearAccum.md @@ -0,0 +1,42 @@ + +--- +title: GEMglClearAccum +description: clear the accumulation buffer +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red value used to clear the accumulation buffer. + default: 0 + - type: float + description: Specifies the green value used to clear the accumulation buffer. + default: 0 + - type: float + description: Specifies the blue value used to clear the accumulation buffer. + default: 0 + - type: float + description: Specifies the alpha value used to clear the accumulation buffer. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red value used to clear the accumulation buffer. + 3rd: + - type: float + description: Specifies the green value used to clear the accumulation buffer. + 4th: + - type: float + description: Specifies the blue value used to clear the accumulation buffer. + 5th: + - type: float + description: Specifies the alpha value used to clear the accumulation buffer. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglClearColor.md b/Resources/Documentation/Gem/GEMglClearColor.md new file mode 100644 index 0000000000..9a2599b7a5 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglClearColor.md @@ -0,0 +1,42 @@ + +--- +title: GEMglClearColor +description: specify the clear values for the color buffers +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the clear color. + default: 0 + - type: float + description: Specifies the green component of the clear color. + default: 0 + - type: float + description: Specifies the blue component of the clear color. + default: 0 + - type: float + description: Specifies the alpha component of the clear color. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the clear color. + 3rd: + - type: float + description: Specifies the green component of the clear color. + 4th: + - type: float + description: Specifies the blue component of the clear color. + 5th: + - type: float + description: Specifies the alpha component of the clear color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglClearDepth.md b/Resources/Documentation/Gem/GEMglClearDepth.md new file mode 100644 index 0000000000..1f1c2babc7 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglClearDepth.md @@ -0,0 +1,24 @@ + +--- +title: GEMglClearDepth +description: specify the clear value for the depth buffer +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the depth value used when the depth buffer is cleared. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the depth value used when the depth buffer is cleared. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglClearIndex.md b/Resources/Documentation/Gem/GEMglClearIndex.md new file mode 100644 index 0000000000..b56eb66323 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglClearIndex.md @@ -0,0 +1,24 @@ + +--- +title: GEMglClearIndex +description: specify the clear value for the color index buffers +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the index used when the color index buffers are cleared. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the index used when the color index buffers are cleared. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglClearStencil.md b/Resources/Documentation/Gem/GEMglClearStencil.md new file mode 100644 index 0000000000..3aabf35456 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglClearStencil.md @@ -0,0 +1,24 @@ + +--- +title: GEMglClearStencil +description: specify the clear value for the stencil buffer +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the index used when the stencil buffer is cleared. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the index used when the stencil buffer is cleared. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglClipPlane.md b/Resources/Documentation/Gem/GEMglClipPlane.md new file mode 100644 index 0000000000..188489c731 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglClipPlane.md @@ -0,0 +1,30 @@ + +--- +title: GEMglClipPlane +description: specify a plane against which all geometry is clipped +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies which clipping plane is being specified. + default: 0 + - type: float + description: Specifies the equation coefficients of the clipping plane. + default: [0, 0, 0, 0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies which clipping plane is being specified. + 3rd: + - type: float + description: Specifies the equation coefficients of the clipping plane. +outlets: + 1st: + - type: gemlist +draft: false + + diff --git a/Resources/Documentation/Gem/GEMglColor3b.md b/Resources/Documentation/Gem/GEMglColor3b.md new file mode 100644 index 0000000000..09beb622ad --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3b.md @@ -0,0 +1,36 @@ + +--- +title: GEMglColor3b +description: set the current RGB color using 8-bit integers +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor3bv.md b/Resources/Documentation/Gem/GEMglColor3bv.md new file mode 100644 index 0000000000..fdfdc3b19f --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3bv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglColor3bv +description: set the current RGB color using 8-bit integers from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. + default: [0, 0, 0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor3d.md b/Resources/Documentation/Gem/GEMglColor3d.md new file mode 100644 index 0000000000..6e420ddc8e --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3d.md @@ -0,0 +1,36 @@ + +--- +title: GEMglColor3d +description: set the current RGB color using double-precision floating-point values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor3dv.md b/Resources/Documentation/Gem/GEMglColor3dv.md new file mode 100644 index 0000000000..f72434426a --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3dv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglColor3dv +description: set the current RGB color using double-precision floating-point values from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. + default: [0, 0, 0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor3f.md b/Resources/Documentation/Gem/GEMglColor3f.md new file mode 100644 index 0000000000..5380ddb14b --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3f.md @@ -0,0 +1,35 @@ + +--- +title: GEMglColor3f +description: set the current RGB color using single-precision floating-point values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglColor3fv.md b/Resources/Documentation/Gem/GEMglColor3fv.md new file mode 100644 index 0000000000..f89de53ead --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3fv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglColor3fv +description: set the current RGB color using single-precision floating-point values from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. + default: [0, 0, 0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor3i.md b/Resources/Documentation/Gem/GEMglColor3i.md new file mode 100644 index 0000000000..297e6d40c9 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3i.md @@ -0,0 +1,36 @@ + +--- +title: GEMglColor3i +description: set the current RGB color using 32-bit integers +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor3iv.md b/Resources/Documentation/Gem/GEMglColor3iv.md new file mode 100644 index 0000000000..8c1cd76106 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3iv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglColor3iv +description: set the current RGB color using 32-bit integers from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. + default: [0, 0, 0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor3s.md b/Resources/Documentation/Gem/GEMglColor3s.md new file mode 100644 index 0000000000..5572288b09 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3s.md @@ -0,0 +1,36 @@ + +--- +title: GEMglColor3s +description: set the current RGB color using 16-bit integers +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor3sv.md b/Resources/Documentation/Gem/GEMglColor3sv.md new file mode 100644 index 0000000000..14fd10ceb8 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3sv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglColor3sv +description: set the current RGB color using 16-bit integers from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. + default: [0, 0, 0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglColor3ub.md b/Resources/Documentation/Gem/GEMglColor3ub.md new file mode 100644 index 0000000000..f69de4d3b4 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3ub.md @@ -0,0 +1,36 @@ + +--- +title: GEMglColor3ub +description: set the current RGB color using 8-bit unsigned integers +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor3ubv.md b/Resources/Documentation/Gem/GEMglColor3ubv.md new file mode 100644 index 0000000000..560b0a92fe --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3ubv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglColor3ubv +description: set the current RGB color using 8-bit unsigned integers from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. + default: [0, 0, 0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor3ui.md b/Resources/Documentation/Gem/GEMglColor3ui.md new file mode 100644 index 0000000000..eb8de01b4c --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3ui.md @@ -0,0 +1,36 @@ + +--- +title: GEMglColor3ui +description: set the current RGB color using 32-bit unsigned integers +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor3uiv.md b/Resources/Documentation/Gem/GEMglColor3uiv.md new file mode 100644 index 0000000000..a7793fa813 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3uiv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglColor3uiv +description: set the current RGB color using 32-bit unsigned integers from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. + default: [0, 0, 0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor3us.md b/Resources/Documentation/Gem/GEMglColor3us.md new file mode 100644 index 0000000000..a6a12b03f6 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3us.md @@ -0,0 +1,35 @@ + +--- +title: GEMglColor3us +description: set the current RGB color using 16-bit unsigned integers +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglColor3usv.md b/Resources/Documentation/Gem/GEMglColor3usv.md new file mode 100644 index 0000000000..c485799309 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor3usv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglColor3usv +description: set the current RGB color using 16-bit unsigned integers from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. + default: [0, 0, 0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, and blue components of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor4b.md b/Resources/Documentation/Gem/GEMglColor4b.md new file mode 100644 index 0000000000..7132e51a27 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4b.md @@ -0,0 +1,42 @@ + +--- +title: GEMglColor4b +description: set the current RGBA color using 8-bit integers +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 + - type: float + description: Specifies the alpha component of the current color. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. + 5th: + - type: float + description: Specifies the alpha component of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor4bv.md b/Resources/Documentation/Gem/GEMglColor4bv.md new file mode 100644 index 0000000000..5f9ff5565c --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4bv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglColor4bv +description: set the current RGBA color using 8-bit integers from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. + default: [0, 0, 0, 0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor4d.md b/Resources/Documentation/Gem/GEMglColor4d.md new file mode 100644 index 0000000000..fddf3a32cf --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4d.md @@ -0,0 +1,42 @@ + +--- +title: GEMglColor4d +description: set the current RGBA color using double-precision floating-point values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 + - type: float + description: Specifies the alpha component of the current color. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. + 5th: + - type: float + description: Specifies the alpha component of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor4dv.md b/Resources/Documentation/Gem/GEMglColor4dv.md new file mode 100644 index 0000000000..7c73c353b4 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4dv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglColor4dv +description: set the current RGBA color using double-precision floating-point values from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. + default: [0, 0, 0, 0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglColor4f.md b/Resources/Documentation/Gem/GEMglColor4f.md new file mode 100644 index 0000000000..d9184e29fd --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4f.md @@ -0,0 +1,42 @@ + +--- +title: GEMglColor4f +description: set the current RGBA color using single-precision floating-point values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 + - type: float + description: Specifies the alpha component of the current color. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. + 5th: + - type: float + description: Specifies the alpha component of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor4fv.md b/Resources/Documentation/Gem/GEMglColor4fv.md new file mode 100644 index 0000000000..700bb43273 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4fv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglColor4fv +description: set the current RGBA color using single-precision floating-point values from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. + default: [0, 0, 0, 1] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor4i.md b/Resources/Documentation/Gem/GEMglColor4i.md new file mode 100644 index 0000000000..aa39f43d1d --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4i.md @@ -0,0 +1,42 @@ + +--- +title: GEMglColor4i +description: set the current RGBA color using 32-bit integers +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 + - type: float + description: Specifies the alpha component of the current color. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. + 5th: + - type: float + description: Specifies the alpha component of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor4iv.md b/Resources/Documentation/Gem/GEMglColor4iv.md new file mode 100644 index 0000000000..7d36d22f98 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4iv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglColor4iv +description: set the current RGBA color using 32-bit integers from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. + default: [0, 0, 0, 1] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor4s.md b/Resources/Documentation/Gem/GEMglColor4s.md new file mode 100644 index 0000000000..2899725ee9 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4s.md @@ -0,0 +1,41 @@ + +--- +title: GEMglColor4s +description: set the current RGBA color using 16-bit integers +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 + - type: float + description: Specifies the alpha component of the current color. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. + 5th: + - type: float + description: Specifies the alpha component of the current color. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglColor4sv.md b/Resources/Documentation/Gem/GEMglColor4sv.md new file mode 100644 index 0000000000..f67f432e5f --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4sv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglColor4sv +description: set the current RGBA color using 16-bit integers from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. + default: [0, 0, 0, 1] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor4ub.md b/Resources/Documentation/Gem/GEMglColor4ub.md new file mode 100644 index 0000000000..0f74bfadb6 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4ub.md @@ -0,0 +1,42 @@ + +--- +title: GEMglColor4ub +description: set the current RGBA color using 8-bit unsigned integers +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 + - type: float + description: Specifies the alpha component of the current color. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. + 5th: + - type: float + description: Specifies the alpha component of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor4ubv.md b/Resources/Documentation/Gem/GEMglColor4ubv.md new file mode 100644 index 0000000000..0e92dd9fa8 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4ubv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglColor4ubv +description: set the current RGBA color using 8-bit unsigned integers from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. + default: [0, 0, 0, 1] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor4ui.md b/Resources/Documentation/Gem/GEMglColor4ui.md new file mode 100644 index 0000000000..317926036c --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4ui.md @@ -0,0 +1,42 @@ + +--- +title: GEMglColor4ui +description: set the current RGBA color using 32-bit unsigned integers +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 + - type: float + description: Specifies the alpha component of the current color. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. + 5th: + - type: float + description: Specifies the alpha component of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor4uiv.md b/Resources/Documentation/Gem/GEMglColor4uiv.md new file mode 100644 index 0000000000..383c7f1368 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4uiv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglColor4uiv +description: set the current RGBA color using 32-bit unsigned integers from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. + default: [0, 0, 0, 1] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglColor4us.md b/Resources/Documentation/Gem/GEMglColor4us.md new file mode 100644 index 0000000000..ca83a1ab1f --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4us.md @@ -0,0 +1,42 @@ + +--- +title: GEMglColor4us +description: set the current RGBA color using 16-bit unsigned integers +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the red component of the current color. + default: 0 + - type: float + description: Specifies the green component of the current color. + default: 0 + - type: float + description: Specifies the blue component of the current color. + default: 0 + - type: float + description: Specifies the alpha component of the current color. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the red component of the current color. + 3rd: + - type: float + description: Specifies the green component of the current color. + 4th: + - type: float + description: Specifies the blue component of the current color. + 5th: + - type: float + description: Specifies the alpha component of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColor4usv.md b/Resources/Documentation/Gem/GEMglColor4usv.md new file mode 100644 index 0000000000..fcbd57315c --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColor4usv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglColor4usv +description: set the current RGBA color using 16-bit unsigned integers from an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. + default: [0, 0, 0, 1] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the red, green, blue, and alpha components of the current color. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColorMask.md b/Resources/Documentation/Gem/GEMglColorMask.md new file mode 100644 index 0000000000..8a6987bf9f --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColorMask.md @@ -0,0 +1,24 @@ + +--- +title: GEMglColorMask +description: enable and disable writing of frame buffer color components +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies whether red, green, blue, and alpha can be written into the frame buffer. + default: [true, true, true, true] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies whether red, green, blue, and alpha can be written into the frame buffer. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglColorMaterial.md b/Resources/Documentation/Gem/GEMglColorMaterial.md new file mode 100644 index 0000000000..3dc722122b --- /dev/null +++ b/Resources/Documentation/Gem/GEMglColorMaterial.md @@ -0,0 +1,36 @@ + +--- +title: GEMglColorMaterial +description: specify the material parameters for the lighting model +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies which material face or faces are being updated. + default: GL_FRONT_AND_BACK + - type: float + description: Specifies which of the ambient, diffuse, specular, and emission material parameters are being set. + default: GL_AMBIENT_AND_DIFFUSE + - type: float + description: If GL_TRUE, the material parameter is set to the corresponding current color. If GL_FALSE, the corresponding material parameter is set to the value specified by params. + default: GL_FALSE +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies which material face or faces are being updated. + 3rd: + - type: float + description: Specifies which of the ambient, diffuse, specular, and emission material parameters are being set. + 4th: + - type: float + description: If GL_TRUE, the material parameter is set to the corresponding current color. If GL_FALSE, the corresponding material parameter is set to the value specified by params. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglCopyPixels.md b/Resources/Documentation/Gem/GEMglCopyPixels.md new file mode 100644 index 0000000000..f9768807c4 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglCopyPixels.md @@ -0,0 +1,48 @@ + +--- +title: GEMglCopyPixels +description: copy pixels into a 1D or 2D texture image +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the left x-coordinate, in pixels, of the source rectangle. + default: 0 + - type: float + description: Specifies the lower y-coordinate, in pixels, of the source rectangle. + default: 0 + - type: float + description: Specifies the width of the texture image. + default: 0 + - type: float + description: Specifies the height of the texture image. + default: 0 + - type: float + description: Specifies the format of the pixel data. + default: GL_COLOR +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the left x-coordinate, in pixels, of the source rectangle. + 3rd: + - type: float + description: Specifies the lower y-coordinate, in pixels, of the source rectangle. + 4th: + - type: float + description: Specifies the width of the texture image. + 5th: + - type: float + description: Specifies the height of the texture image. + 6th: + - type: float + description: Specifies the format of the pixel data. +outlets: + 1st: + - type: gemlist +draft: false + + diff --git a/Resources/Documentation/Gem/GEMglCopyTexImage1D.md b/Resources/Documentation/Gem/GEMglCopyTexImage1D.md new file mode 100644 index 0000000000..85df4eec8a --- /dev/null +++ b/Resources/Documentation/Gem/GEMglCopyTexImage1D.md @@ -0,0 +1,54 @@ + +--- +title: GEMglCopyTexImage1D +description: copy pixels into a 1D texture image +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target texture. + default: GL_TEXTURE_1D + - type: float + description: Specifies the level-of-detail number. Level 0 is the base image level. Level n is the nth mipmap reduction image. + default: 0 + - type: float + description: Specifies the internal format of the texture image. + default: GL_RGBA + - type: float + description: Specifies the xoffset parameter, the lower left corner of the texture image is (xoffset, 0). + default: 0 + - type: float + description: Specifies the width of the texture image. + default: 0 + - type: float + description: Specifies the border of the texture. Must be 0. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target texture. + 3rd: + - type: float + description: Specifies the level-of-detail number. Level 0 is the base image level. Level n is the nth mipmap reduction image. + 4th: + - type: float + description: Specifies the internal format of the texture image. + 5th: + - type: float + description: Specifies the xoffset parameter, the lower left corner of the texture image is (xoffset, 0). + 6th: + - type: float + description: Specifies the width of the texture image. + 7th: + - type: float + description: Specifies the border of the texture. Must be 0. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglCopyTexImage2D.md b/Resources/Documentation/Gem/GEMglCopyTexImage2D.md new file mode 100644 index 0000000000..93e291bc7b --- /dev/null +++ b/Resources/Documentation/Gem/GEMglCopyTexImage2D.md @@ -0,0 +1,54 @@ + +--- +title: GEMglCopyTexImage2D +description: copy pixels into a 2D texture image +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target texture. + default: GL_TEXTURE_2D + - type: float + description: Specifies the level-of-detail number. Level 0 is the base image level. Level n is the nth mipmap reduction image. + default: 0 + - type: float + description: Specifies the internal format of the texture image. + default: GL_RGBA + - type: float + description: Specifies the xoffset and yoffset parameters. The lower left corner of the texture image is (xoffset, yoffset). + default: [0, 0] + - type: float + description: Specifies the width and height of the texture image. + default: [0, 0] + - type: float + description: Specifies the border of the texture. Must be 0. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target texture. + 3rd: + - type: float + description: Specifies the level-of-detail number. Level 0 is the base image level. Level n is the nth mipmap reduction image. + 4th: + - type: float + description: Specifies the internal format of the texture image. + 5th: + - type: float + description: Specifies the xoffset and yoffset parameters. The lower left corner of the texture image is (xoffset, yoffset). + 6th: + - type: float + description: Specifies the width and height of the texture image. + 7th: + - type: float + description: Specifies the border of the texture. Must be 0. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglCopyTexSubImage1D.md b/Resources/Documentation/Gem/GEMglCopyTexSubImage1D.md new file mode 100644 index 0000000000..9ff5444ce2 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglCopyTexSubImage1D.md @@ -0,0 +1,42 @@ + +--- +title: GEMglCopyTexSubImage1D +description: copy a one-dimensional texture subimage +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target texture. + default: GL_TEXTURE_1D + - type: float + description: Specifies the level-of-detail number. Level 0 is the base image level. Level n is the nth mipmap reduction image. + default: 0 + - type: float + description: Specifies the xoffset parameter. The lower left corner of the texture image is (xoffset, 0). + default: 0 + - type: float + description: Specifies the x, y, and width parameters, which specify a subregion of the framebuffer. + default: [0, 0, 0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target texture. + 3rd: + - type: float + description: Specifies the level-of-detail number. Level 0 is the base image level. Level n is the nth mipmap reduction image. + 4th: + - type: float + description: Specifies the xoffset parameter. The lower left corner of the texture image is (xoffset, 0). + 5th: + - type: float + description: Specifies the x, y, and width parameters, which specify a subregion of the framebuffer. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglCopyTexSubImage2D.md b/Resources/Documentation/Gem/GEMglCopyTexSubImage2D.md new file mode 100644 index 0000000000..7e7781d845 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglCopyTexSubImage2D.md @@ -0,0 +1,42 @@ + +--- +title: GEMglCopyTexSubImage2D +description: copy a two-dimensional texture subimage +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target texture. + default: GL_TEXTURE_2D + - type: float + description: Specifies the level-of-detail number. Level 0 is the base image level. Level n is the nth mipmap reduction image. + default: 0 + - type: float + description: Specifies the xoffset and yoffset parameters. The lower left corner of the texture image is (xoffset, yoffset). + default: [0, 0] + - type: float + description: Specifies the x, y, width, and height parameters, which specify a subregion of the framebuffer. + default: [0, 0, 0, 0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target texture. + 3rd: + - type: float + description: Specifies the level-of-detail number. Level 0 is the base image level. Level n is the nth mipmap reduction image. + 4th: + - type: float + description: Specifies the xoffset and yoffset parameters. The lower left corner of the texture image is (xoffset, yoffset). + 5th: + - type: float + description: Specifies the x, y, width, and height parameters, which specify a subregion of the framebuffer. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglCullFace.md b/Resources/Documentation/Gem/GEMglCullFace.md new file mode 100644 index 0000000000..a34d613eca --- /dev/null +++ b/Resources/Documentation/Gem/GEMglCullFace.md @@ -0,0 +1,23 @@ + +--- +title: GEMglCullFace +description: specify whether front- or back-facing facets can be culled +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the culling mode. + default: GL_BACK +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the culling mode. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglDeleteTextures.md b/Resources/Documentation/Gem/GEMglDeleteTextures.md new file mode 100644 index 0000000000..d8d07ebeb0 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglDeleteTextures.md @@ -0,0 +1,30 @@ + +--- +title: GEMglDeleteTextures +description: delete named textures +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the number of textures to be deleted. + default: 0 + - type: float + description: Specifies an array of textures to be deleted. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the number of textures to be deleted. + 3rd: + - type: float + description: Specifies an array of textures to be deleted. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglDepthFunc.md b/Resources/Documentation/Gem/GEMglDepthFunc.md new file mode 100644 index 0000000000..d4e9df13d5 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglDepthFunc.md @@ -0,0 +1,24 @@ + +--- +title: GEMglDepthFunc +description: set the function used for depth buffer comparisons +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the depth comparison function. + default: GL_LESS +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the depth comparison function. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglDepthMask.md b/Resources/Documentation/Gem/GEMglDepthMask.md new file mode 100644 index 0000000000..c4764abaaa --- /dev/null +++ b/Resources/Documentation/Gem/GEMglDepthMask.md @@ -0,0 +1,24 @@ + +--- +title: GEMglDepthMask +description: enable or disable writing into the depth buffer +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies whether the depth buffer is enabled for writing. + default: GL_TRUE +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies whether the depth buffer is enabled for writing. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglDepthRange.md b/Resources/Documentation/Gem/GEMglDepthRange.md new file mode 100644 index 0000000000..f75bd4b92d --- /dev/null +++ b/Resources/Documentation/Gem/GEMglDepthRange.md @@ -0,0 +1,30 @@ + +--- +title: GEMglDepthRange +description: specify mapping of depth values from normalized device coordinates to window coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the mapping of the near clipping plane to window coordinates. + default: 0 + - type: float + description: Specifies the mapping of the far clipping plane to window coordinates. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the mapping of the near clipping plane to window coordinates. + 3rd: + - type: float + description: Specifies the mapping of the far clipping plane to window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglDisable.md b/Resources/Documentation/Gem/GEMglDisable.md new file mode 100644 index 0000000000..214d192085 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglDisable.md @@ -0,0 +1,23 @@ + +--- +title: GEMglDisable +description: disable server-side GL capabilities +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the GL capability to disable. + default: GL_TEXTURE_2D +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the GL capability to disable. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglDisableClientState.md b/Resources/Documentation/Gem/GEMglDisableClientState.md new file mode 100644 index 0000000000..d59844a638 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglDisableClientState.md @@ -0,0 +1,24 @@ + +--- +title: GEMglDisableClientState +description: disable client-side capability +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the array to disable. + default: GL_VERTEX_ARRAY +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the array to disable. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglDrawArrays.md b/Resources/Documentation/Gem/GEMglDrawArrays.md new file mode 100644 index 0000000000..3d68b523a9 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglDrawArrays.md @@ -0,0 +1,36 @@ + +--- +title: GEMglDrawArrays +description: render primitives from array data +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies what kind of primitives to render. + default: GL_TRIANGLES + - type: float + description: Specifies the starting index in the enabled arrays. + default: 0 + - type: float + description: Specifies the number of indices to be rendered. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies what kind of primitives to render. + 3rd: + - type: float + description: Specifies the starting index in the enabled arrays. + 4th: + - type: float + description: Specifies the number of indices to be rendered. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglDrawBuffer.md b/Resources/Documentation/Gem/GEMglDrawBuffer.md new file mode 100644 index 0000000000..2050de3c8a --- /dev/null +++ b/Resources/Documentation/Gem/GEMglDrawBuffer.md @@ -0,0 +1,24 @@ + +--- +title: GEMglDrawBuffer +description: specify color buffers to draw into +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the color buffer. + default: GL_BACK +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the color buffer. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglDrawElements.md b/Resources/Documentation/Gem/GEMglDrawElements.md new file mode 100644 index 0000000000..f838cdb2bb --- /dev/null +++ b/Resources/Documentation/Gem/GEMglDrawElements.md @@ -0,0 +1,42 @@ + +--- +title: GEMglDrawElements +description: render primitives from array data +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies what kind of primitives to render. + default: GL_TRIANGLES + - type: float + description: Specifies the number of indices to be rendered. + default: 0 + - type: float + description: Specifies the type of the values in indices. + default: GL_UNSIGNED_INT + - type: float + description: Specifies an offset in the array buffer bound to GL_ELEMENT_ARRAY_BUFFER. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies what kind of primitives to render. + 3rd: + - type: float + description: Specifies the number of indices to be rendered. + 4th: + - type: float + description: Specifies the type of the values in indices. + 5th: + - type: float + description: Specifies an offset in the array buffer bound to GL_ELEMENT_ARRAY_BUFFER. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglEdgeFlag.md b/Resources/Documentation/Gem/GEMglEdgeFlag.md new file mode 100644 index 0000000000..cca6139d32 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEdgeFlag.md @@ -0,0 +1,23 @@ + +--- +title: GEMglEdgeFlag +description: set the current edge flag +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the current edge flag. + default: GL_TRUE +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the current edge flag. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglEnable.md b/Resources/Documentation/Gem/GEMglEnable.md new file mode 100644 index 0000000000..c1e01991c6 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEnable.md @@ -0,0 +1,24 @@ + +--- +title: GEMglEnable +description: enable server-side GL capabilities +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the GL capability to enable. + default: GL_TEXTURE_2D +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the GL capability to enable. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglEnableClientState.md b/Resources/Documentation/Gem/GEMglEnableClientState.md new file mode 100644 index 0000000000..27b58b5818 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEnableClientState.md @@ -0,0 +1,24 @@ + +--- +title: GEMglEnableClientState +description: enable client-side capability +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the array to enable. + default: GL_VERTEX_ARRAY +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the array to enable. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglEnd.md b/Resources/Documentation/Gem/GEMglEnd.md new file mode 100644 index 0000000000..3158e26044 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEnd.md @@ -0,0 +1,17 @@ + +--- +title: GEMglEnd +description: end the definition of a vertex, primitive, or pixel group +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglEndList.md b/Resources/Documentation/Gem/GEMglEndList.md new file mode 100644 index 0000000000..3a59c64520 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEndList.md @@ -0,0 +1,17 @@ + +--- +title: GEMglEndList +description: end the definition of a display list +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglEvalCoord1d.md b/Resources/Documentation/Gem/GEMglEvalCoord1d.md new file mode 100644 index 0000000000..cc29b91cf9 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEvalCoord1d.md @@ -0,0 +1,23 @@ + +--- +title: GEMglEvalCoord1d +description: evaluate enabled one-dimensional maps at the specified parametric coordinate +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the parametric coordinate at which to evaluate the maps. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the parametric coordinate at which to evaluate the maps. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglEvalCoord1dv.md b/Resources/Documentation/Gem/GEMglEvalCoord1dv.md new file mode 100644 index 0000000000..ff75a99d8a --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEvalCoord1dv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglEvalCoord1dv +description: evaluate enabled one-dimensional maps at the specified parametric coordinate +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the parametric coordinate at which to evaluate the maps. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the parametric coordinate at which to evaluate the maps. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglEvalCoord1f.md b/Resources/Documentation/Gem/GEMglEvalCoord1f.md new file mode 100644 index 0000000000..ec91473a7d --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEvalCoord1f.md @@ -0,0 +1,24 @@ + +--- +title: GEMglEvalCoord1f +description: evaluate enabled one-dimensional maps at the specified parametric coordinate +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the parametric coordinate at which to evaluate the maps. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the parametric coordinate at which to evaluate the maps. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglEvalCoord1fv.md b/Resources/Documentation/Gem/GEMglEvalCoord1fv.md new file mode 100644 index 0000000000..b1e5a14d15 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEvalCoord1fv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglEvalCoord1fv +description: evaluate enabled one-dimensional maps at the specified parametric coordinate +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the parametric coordinate at which to evaluate the maps. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the parametric coordinate at which to evaluate the maps. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglEvalCoord2d.md b/Resources/Documentation/Gem/GEMglEvalCoord2d.md new file mode 100644 index 0000000000..3e12215456 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEvalCoord2d.md @@ -0,0 +1,28 @@ + +--- +title: GEMglEvalCoord2d +description: evaluate enabled two-dimensional maps at the specified parametric coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the parametric coordinates at which to evaluate the maps. + default: 0 + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the parametric coordinates at which to evaluate the maps. + 3rd: + - type: float + description: Specifies the parametric coordinates at which to evaluate the maps. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglEvalCoord2dv.md b/Resources/Documentation/Gem/GEMglEvalCoord2dv.md new file mode 100644 index 0000000000..10f24df1d4 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEvalCoord2dv.md @@ -0,0 +1,27 @@ + +--- +title: GEMglEvalCoord2dv +description: evaluate enabled two-dimensional maps at the specified parametric coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the parametric coordinates at which to evaluate the maps. + default: 0 + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the parametric coordinates at which to evaluate the maps. + 3rd: + - type: float + description: Specifies the parametric coordinates at which to evaluate the maps. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglEvalCoord2f.md b/Resources/Documentation/Gem/GEMglEvalCoord2f.md new file mode 100644 index 0000000000..f2776a24c3 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEvalCoord2f.md @@ -0,0 +1,28 @@ + +--- +title: GEMglEvalCoord2f +description: evaluate enabled two-dimensional maps at the specified parametric coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the parametric coordinates at which to evaluate the maps. + default: 0 + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the parametric coordinates at which to evaluate the maps. + 3rd: + - type: float + description: Specifies the parametric coordinates at which to evaluate the maps. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglEvalCoord2fv.md b/Resources/Documentation/Gem/GEMglEvalCoord2fv.md new file mode 100644 index 0000000000..2c2900842b --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEvalCoord2fv.md @@ -0,0 +1,28 @@ + +--- +title: GEMglEvalCoord2fv +description: evaluate enabled two-dimensional maps at the specified parametric coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the parametric coordinates at which to evaluate the maps. + default: 0 + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the parametric coordinates at which to evaluate the maps. + 3rd: + - type: float + description: Specifies the parametric coordinates at which to evaluate the maps. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglEvalMesh1.md b/Resources/Documentation/Gem/GEMglEvalMesh1.md new file mode 100644 index 0000000000..c6606d67c2 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEvalMesh1.md @@ -0,0 +1,36 @@ + +--- +title: GEMglEvalMesh1 +description: compute a one-dimensional mesh +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the mesh type. + default: GL_LINE + - type: float + description: Specifies the starting grid coordinate. + default: 0 + - type: float + description: Specifies the number of steps in the grid. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the mesh type. + 3rd: + - type: float + description: Specifies the starting grid coordinate. + 4th: + - type: float + description: Specifies the number of steps in the grid. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglEvalMesh2.md b/Resources/Documentation/Gem/GEMglEvalMesh2.md new file mode 100644 index 0000000000..5d70bd950d --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEvalMesh2.md @@ -0,0 +1,41 @@ + +--- +title: GEMglEvalMesh2 +description: compute a two-dimensional mesh +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the mesh type. + default: GL_FILL + - type: float + description: Specifies the starting grid coordinates. + default: 0 + default: 0 + - type: float + description: Specifies the number of steps in the grid. + default: 1 + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the mesh type. + 3rd: + - type: float + description: Specifies the starting grid coordinates. + 4th: + - type: float + description: Specifies the number of steps in the grid. + 5th: + - type: float + description: Specifies the number of steps in the grid. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglEvalPoint1.md b/Resources/Documentation/Gem/GEMglEvalPoint1.md new file mode 100644 index 0000000000..531ae1e54d --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEvalPoint1.md @@ -0,0 +1,29 @@ + +--- +title: GEMglEvalPoint1 +description: compute a one-dimensional grid of points +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the starting grid coordinate. + default: 0 + - type: float + description: Specifies the number of steps in the grid. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the starting grid coordinate. + 3rd: + - type: float + description: Specifies the number of steps in the grid. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglEvalPoint2.md b/Resources/Documentation/Gem/GEMglEvalPoint2.md new file mode 100644 index 0000000000..9b81991b96 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglEvalPoint2.md @@ -0,0 +1,35 @@ + +--- +title: GEMglEvalPoint2 +description: compute a two-dimensional grid of points +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the starting grid coordinates. + default: 0 + default: 0 + - type: float + description: Specifies the number of steps in the grid. + default: 1 + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the starting grid coordinates. + 3rd: + - type: float + description: Specifies the number of steps in the grid. + 4th: + - type: float + description: Specifies the number of steps in the grid. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglFeedbackBuffer.md b/Resources/Documentation/Gem/GEMglFeedbackBuffer.md new file mode 100644 index 0000000000..58779b86f6 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglFeedbackBuffer.md @@ -0,0 +1,30 @@ + +--- +title: GEMglFeedbackBuffer +description: returns information about primitive groups in the feedback buffer +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the size of the buffer. + default: 0 + - type: float + description: Specifies the type of data to be returned. + default: GL_3D_COLOR +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the size of the buffer. + 3rd: + - type: float + description: Specifies the type of data to be returned. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglFinish.md b/Resources/Documentation/Gem/GEMglFinish.md new file mode 100644 index 0000000000..34ed6e5778 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglFinish.md @@ -0,0 +1,17 @@ + +--- +title: GEMglFinish +description: block until all GL execution is complete +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglFlush.md b/Resources/Documentation/Gem/GEMglFlush.md new file mode 100644 index 0000000000..a12703b57d --- /dev/null +++ b/Resources/Documentation/Gem/GEMglFlush.md @@ -0,0 +1,17 @@ + +--- +title: GEMglFlush +description: force execution of GL commands in finite time +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglFogf.md b/Resources/Documentation/Gem/GEMglFogf.md new file mode 100644 index 0000000000..4e2a0647ab --- /dev/null +++ b/Resources/Documentation/Gem/GEMglFogf.md @@ -0,0 +1,29 @@ + +--- +title: GEMglFogf +description: specify fog parameters +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the fog parameter to be set. + default: GL_FOG_MODE + - type: float + description: Specifies the value of the parameter. + default: GL_EXP +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the fog parameter to be set. + 3rd: + - type: float + description: Specifies the value of the parameter. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglFogfv.md b/Resources/Documentation/Gem/GEMglFogfv.md new file mode 100644 index 0000000000..e8a93102cb --- /dev/null +++ b/Resources/Documentation/Gem/GEMglFogfv.md @@ -0,0 +1,30 @@ + +--- +title: GEMglFogfv +description: specify fog parameters +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the fog parameter to be set. + default: GL_FOG_MODE + - type: float + description: Specifies the value of the parameter. + default: GL_EXP +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the fog parameter to be set. + 3rd: + - type: float + description: Specifies the value of the parameter. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglFogi.md b/Resources/Documentation/Gem/GEMglFogi.md new file mode 100644 index 0000000000..f290f870a5 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglFogi.md @@ -0,0 +1,30 @@ + +--- +title: GEMglFogi +description: specify fog parameters +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the fog parameter to be set. + default: GL_FOG_MODE + - type: float + description: Specifies the value of the parameter. + default: GL_EXP +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the fog parameter to be set. + 3rd: + - type: float + description: Specifies the value of the parameter. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglFogiv.md b/Resources/Documentation/Gem/GEMglFogiv.md new file mode 100644 index 0000000000..ab74ec78d0 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglFogiv.md @@ -0,0 +1,30 @@ + +--- +title: GEMglFogiv +description: specify fog parameters +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the fog parameter to be set. + default: GL_FOG_MODE + - type: float + description: Specifies the value of the parameter. + default: GL_EXP +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the fog parameter to be set. + 3rd: + - type: float + description: Specifies the value of the parameter. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglFrontFace.md b/Resources/Documentation/Gem/GEMglFrontFace.md new file mode 100644 index 0000000000..f03c32f577 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglFrontFace.md @@ -0,0 +1,24 @@ + +--- +title: GEMglFrontFace +description: define front- and back-facing polygons +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the orientation of front-facing polygons. + default: GL_CCW +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the orientation of front-facing polygons. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglFrustum.md b/Resources/Documentation/Gem/GEMglFrustum.md new file mode 100644 index 0000000000..edf8f5178b --- /dev/null +++ b/Resources/Documentation/Gem/GEMglFrustum.md @@ -0,0 +1,38 @@ + +--- +title: GEMglFrustum +description: define a perspective matrix +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the coordinates for the left and right vertical clipping planes. + default: 0 + default: 0 + - type: float + description: Specifies the coordinates for the bottom and top horizontal clipping planes. + default: 0 + default: 0 + - type: float + description: Specifies the distances to the near and far depth clipping planes. + default: 0 + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the coordinates for the left and right vertical clipping planes. + 3rd: + - type: float + description: Specifies the coordinates for the bottom and top horizontal clipping planes. + 4th: + - type: float + description: Specifies the distances to the near and far depth clipping planes. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglGenLists.md b/Resources/Documentation/Gem/GEMglGenLists.md new file mode 100644 index 0000000000..2afed81013 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglGenLists.md @@ -0,0 +1,24 @@ + +--- +title: GEMglGenLists +description: generate a contiguous set of empty display lists +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the number of contiguous empty display lists to be generated. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the number of contiguous empty display lists to be generated. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglGenProgramsARB.md b/Resources/Documentation/Gem/GEMglGenProgramsARB.md new file mode 100644 index 0000000000..cca3cf6007 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglGenProgramsARB.md @@ -0,0 +1,24 @@ + +--- +title: GEMglGenProgramsARB +description: generate program object names +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the number of program object names to generate. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the number of program object names to generate. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglGenTextures.md b/Resources/Documentation/Gem/GEMglGenTextures.md new file mode 100644 index 0000000000..eac6ab4e4c --- /dev/null +++ b/Resources/Documentation/Gem/GEMglGenTextures.md @@ -0,0 +1,24 @@ + +--- +title: GEMglGenTextures +description: generate texture names +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the number of texture names to generate. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the number of texture names to generate. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglGenerateMipmap.md b/Resources/Documentation/Gem/GEMglGenerateMipmap.md new file mode 100644 index 0000000000..2b95059965 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglGenerateMipmap.md @@ -0,0 +1,24 @@ + +--- +title: GEMglGenerateMipmap +description: generate mipmaps for a specified texture target +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target texture. + default: GL_TEXTURE_2D +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target texture. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglGetError.md b/Resources/Documentation/Gem/GEMglGetError.md new file mode 100644 index 0000000000..a9a8420ff4 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglGetError.md @@ -0,0 +1,17 @@ + +--- +title: GEMglGetError +description: return error information +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: float + description: Returns the error code. +draft: false + diff --git a/Resources/Documentation/Gem/GEMglGetFloatv.md b/Resources/Documentation/Gem/GEMglGetFloatv.md new file mode 100644 index 0000000000..5c2a22c9f4 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglGetFloatv.md @@ -0,0 +1,27 @@ + +--- +title: GEMglGetFloatv +description: return the value or values of a selected parameter +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the parameter value to retrieve. + default: GL_MODELVIEW_MATRIX +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the parameter value to retrieve. + 3rd: + - type: float + description: Returns the value or values of the specified parameter. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglGetIntegerv.md b/Resources/Documentation/Gem/GEMglGetIntegerv.md new file mode 100644 index 0000000000..b4e899e4ff --- /dev/null +++ b/Resources/Documentation/Gem/GEMglGetIntegerv.md @@ -0,0 +1,27 @@ + +--- +title: GEMglGetIntegerv +description: return the value or values of a selected parameter +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the parameter value to retrieve. + default: GL_VIEWPORT +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the parameter value to retrieve. + 3rd: + - type: float + description: Returns the value or values of the specified parameter. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglGetMapdv.md b/Resources/Documentation/Gem/GEMglGetMapdv.md new file mode 100644 index 0000000000..3b25d5eee9 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglGetMapdv.md @@ -0,0 +1,33 @@ + +--- +title: GEMglGetMapdv +description: return the coefficients of the specified map +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the map to query. + default: GL_COEFF + - type: float + description: Specifies the target coordinate space. + default: GL_OBJECT_LINEAR +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the map to query. + 3rd: + - type: float + description: Specifies the target coordinate space. + 4th: + - type: float + description: Returns the coefficients of the specified map. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglGetMapfv.md b/Resources/Documentation/Gem/GEMglGetMapfv.md new file mode 100644 index 0000000000..7f760ba3d2 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglGetMapfv.md @@ -0,0 +1,33 @@ + +--- +title: GEMglGetMapfv +description: return the coefficients of the specified map +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the map to query. + default: GL_COEFF + - type: float + description: Specifies the target coordinate space. + default: GL_OBJECT_LINEAR +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the map to query. + 3rd: + - type: float + description: Specifies the target coordinate space. + 4th: + - type: float + description: Returns the coefficients of the specified map. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglGetMapiv.md b/Resources/Documentation/Gem/GEMglGetMapiv.md new file mode 100644 index 0000000000..bc96d12141 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglGetMapiv.md @@ -0,0 +1,32 @@ + +--- +title: GEMglGetMapiv +description: return the coefficients of the specified map +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the map to query. + default: GL_ORDER + - type: float + description: Specifies the target coordinate space. + default: GL_OBJECT_LINEAR +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the map to query. + 3rd: + - type: float + description: Specifies the target coordinate space. + 4th: + - type: float + description: Returns the coefficients of the specified map. +outlets: + 1st: + - type: gemlist +draft: false + diff --git a/Resources/Documentation/Gem/GEMglGetPointerv.md b/Resources/Documentation/Gem/GEMglGetPointerv.md new file mode 100644 index 0000000000..12dbbad585 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglGetPointerv.md @@ -0,0 +1,27 @@ + +--- +title: GEMglGetPointerv +description: return the address of the specified pointer +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the pointer to be returned. + default: GL_VERTEX_ARRAY_POINTER +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the pointer to be returned. + 3rd: + - type: gemlist + description: Returns the address of the specified pointer. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglGetString.md b/Resources/Documentation/Gem/GEMglGetString.md new file mode 100644 index 0000000000..0b45274ad8 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglGetString.md @@ -0,0 +1,27 @@ + +--- +title: GEMglGetString +description: return a string describing the current GL connection +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the information to return. + default: GL_VENDOR +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the information to return. + 3rd: + - type: gemlist + description: Returns a string describing the current GL connection. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglHint.md b/Resources/Documentation/Gem/GEMglHint.md new file mode 100644 index 0000000000..d133852201 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglHint.md @@ -0,0 +1,30 @@ + +--- +title: GEMglHint +description: specify implementation-specific hints +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target. + default: GL_PERSPECTIVE_CORRECTION_HINT + - type: float + description: Specifies the mode. + default: GL_FASTEST +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target. + 3rd: + - type: float + description: Specifies the mode. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglIndexMask.md b/Resources/Documentation/Gem/GEMglIndexMask.md new file mode 100644 index 0000000000..2dd5510ac0 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglIndexMask.md @@ -0,0 +1,24 @@ + +--- +title: GEMglIndexMask +description: set the write enable mask for the color index buffers +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies a bit mask to enable and disable writing of individual bits in the color index buffers. + default: 0xFFFFFFFF +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies a bit mask to enable and disable writing of individual bits in the color index buffers. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglIndexd.md b/Resources/Documentation/Gem/GEMglIndexd.md new file mode 100644 index 0000000000..ead42eda3b --- /dev/null +++ b/Resources/Documentation/Gem/GEMglIndexd.md @@ -0,0 +1,24 @@ + +--- +title: GEMglIndexd +description: set the current color index +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the color index. + default: 1.0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the color index. +outlets: + 1st: + - type: gemlist +draft: false + + diff --git a/Resources/Documentation/Gem/GEMglIndexdv.md b/Resources/Documentation/Gem/GEMglIndexdv.md new file mode 100644 index 0000000000..3a1fe821d4 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglIndexdv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglIndexdv +description: set the current color index +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the color index. + default: 1.0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the color index. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglIndexf.md b/Resources/Documentation/Gem/GEMglIndexf.md new file mode 100644 index 0000000000..b13cde343e --- /dev/null +++ b/Resources/Documentation/Gem/GEMglIndexf.md @@ -0,0 +1,24 @@ + +--- +title: GEMglIndexf +description: set the current color index +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the color index. + default: 1.0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the color index. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglIndexfv.md b/Resources/Documentation/Gem/GEMglIndexfv.md new file mode 100644 index 0000000000..0f9df2d74d --- /dev/null +++ b/Resources/Documentation/Gem/GEMglIndexfv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglIndexfv +description: set the current color index +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the color index. + default: 1.0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the color index. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglIndexi.md b/Resources/Documentation/Gem/GEMglIndexi.md new file mode 100644 index 0000000000..f9af4e40d3 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglIndexi.md @@ -0,0 +1,24 @@ + +--- +title: GEMglIndexi +description: set the current color index +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the color index. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the color index. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglIndexiv.md b/Resources/Documentation/Gem/GEMglIndexiv.md new file mode 100644 index 0000000000..ba84920b17 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglIndexiv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglIndexiv +description: set the current color index +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the color index. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the color index. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglIndexs.md b/Resources/Documentation/Gem/GEMglIndexs.md new file mode 100644 index 0000000000..d3da571e3e --- /dev/null +++ b/Resources/Documentation/Gem/GEMglIndexs.md @@ -0,0 +1,24 @@ + +--- +title: GEMglIndexs +description: set the current color index +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the color index. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the color index. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglIndexsv.md b/Resources/Documentation/Gem/GEMglIndexsv.md new file mode 100644 index 0000000000..25181a706c --- /dev/null +++ b/Resources/Documentation/Gem/GEMglIndexsv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglIndexsv +description: set the current color index +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the color index. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the color index. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglIndexub.md b/Resources/Documentation/Gem/GEMglIndexub.md new file mode 100644 index 0000000000..3f10563500 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglIndexub.md @@ -0,0 +1,24 @@ + +--- +title: GEMglIndexub +description: set the current color index +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the color index. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the color index. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglIndexubv.md b/Resources/Documentation/Gem/GEMglIndexubv.md new file mode 100644 index 0000000000..c104c47078 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglIndexubv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglIndexubv +description: set the current color index +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the color index. + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the color index. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglInitNames.md b/Resources/Documentation/Gem/GEMglInitNames.md new file mode 100644 index 0000000000..21d870e9d2 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglInitNames.md @@ -0,0 +1,17 @@ + +--- +title: GEMglInitNames +description: initialize the name stack +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglIsEnabled.md b/Resources/Documentation/Gem/GEMglIsEnabled.md new file mode 100644 index 0000000000..4ecf1b05f7 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglIsEnabled.md @@ -0,0 +1,25 @@ + +--- +title: GEMglIsEnabled +description: test whether a specific GL capability is enabled +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the capability to test. + default: GL_LIGHTING +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the capability to test. +outlets: + 1st: + - type: float + description: Returns GL_TRUE if the capability is enabled, and GL_FALSE otherwise. +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglIsList.md b/Resources/Documentation/Gem/GEMglIsList.md new file mode 100644 index 0000000000..e4f296a15e --- /dev/null +++ b/Resources/Documentation/Gem/GEMglIsList.md @@ -0,0 +1,24 @@ + +--- +title: GEMglIsList +description: test if a name corresponds to a display list +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the name of a display list. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the name of a display list. +outlets: + 1st: + - type: float + description: Returns GL_TRUE if the name corresponds to a display list, and GL_FALSE otherwise. +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglIsTexture.md b/Resources/Documentation/Gem/GEMglIsTexture.md new file mode 100644 index 0000000000..3bd459b9b8 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglIsTexture.md @@ -0,0 +1,24 @@ + +--- +title: GEMglIsTexture +description: determine if a name corresponds to a texture +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the name of a texture. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the name of a texture. +outlets: + 1st: + - type: float + description: Returns GL_TRUE if the name corresponds to a texture, and GL_FALSE otherwise. +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglLightModelf.md b/Resources/Documentation/Gem/GEMglLightModelf.md new file mode 100644 index 0000000000..8717981b54 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglLightModelf.md @@ -0,0 +1,30 @@ + +--- +title: GEMglLightModelf +description: set the value of a light model parameter +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the light model parameter. + default: GL_LIGHT_MODEL_TWO_SIDE + - type: float + description: Specifies the parameter value. + default: 0.0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the light model parameter. + 3rd: + - type: float + description: Specifies the parameter value. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglLightModeli.md b/Resources/Documentation/Gem/GEMglLightModeli.md new file mode 100644 index 0000000000..de7660f95d --- /dev/null +++ b/Resources/Documentation/Gem/GEMglLightModeli.md @@ -0,0 +1,29 @@ + +--- +title: GEMglLightModeli +description: set the value of a light model parameter +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the light model parameter. + default: GL_LIGHT_MODEL_TWO_SIDE + - type: float + description: Specifies the parameter value. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the light model parameter. + 3rd: + - type: float + description: Specifies the parameter value. +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/GEMglLightf.md b/Resources/Documentation/Gem/GEMglLightf.md new file mode 100644 index 0000000000..751fe2ec08 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglLightf.md @@ -0,0 +1,36 @@ + +--- +title: GEMglLightf +description: set the value of a light source parameter +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the light source. + default: GL_LIGHT0 + - type: float + description: Specifies the light source parameter. + default: GL_SPOT_EXPONENT + - type: float + description: Specifies the parameter value. + default: 0.0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the light source. + 3rd: + - type: float + description: Specifies the light source parameter. + 4th: + - type: float + description: Specifies the parameter value. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglLighti.md b/Resources/Documentation/Gem/GEMglLighti.md new file mode 100644 index 0000000000..94574700c2 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglLighti.md @@ -0,0 +1,36 @@ + +--- +title: GEMglLighti +description: set the value of a light source parameter +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the light source. + default: GL_LIGHT0 + - type: float + description: Specifies the light source parameter. + default: GL_SPOT_EXPONENT + - type: float + description: Specifies the parameter value. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the light source. + 3rd: + - type: float + description: Specifies the light source parameter. + 4th: + - type: float + description: Specifies the parameter value. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglLineStipple.md b/Resources/Documentation/Gem/GEMglLineStipple.md new file mode 100644 index 0000000000..e32a75a489 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglLineStipple.md @@ -0,0 +1,30 @@ + +--- +title: GEMglLineStipple +description: specify the line stipple pattern +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the number of bits in the stipple pattern. + default: 1 + - type: float + description: Specifies the bit pattern for the line stipple. + default: 0x0101 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the number of bits in the stipple pattern. + 3rd: + - type: float + description: Specifies the bit pattern for the line stipple. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglLineWidth.md b/Resources/Documentation/Gem/GEMglLineWidth.md new file mode 100644 index 0000000000..c9e62f9cfa --- /dev/null +++ b/Resources/Documentation/Gem/GEMglLineWidth.md @@ -0,0 +1,24 @@ + +--- +title: GEMglLineWidth +description: specify the width of rasterized lines +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the width of rasterized lines. + default: 1.0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the width of rasterized lines. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglLoadIdentity.md b/Resources/Documentation/Gem/GEMglLoadIdentity.md new file mode 100644 index 0000000000..eeeab01588 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglLoadIdentity.md @@ -0,0 +1,17 @@ + +--- +title: GEMglLoadIdentity +description: replace the current matrix with the identity matrix +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglLoadMatrixd.md b/Resources/Documentation/Gem/GEMglLoadMatrixd.md new file mode 100644 index 0000000000..b4b73ef5e4 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglLoadMatrixd.md @@ -0,0 +1,23 @@ + +--- +title: GEMglLoadMatrixd +description: replace the current matrix with a double-precision matrix +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 column-major matrix. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 column-major matrix. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglLoadMatrixf.md b/Resources/Documentation/Gem/GEMglLoadMatrixf.md new file mode 100644 index 0000000000..8421f90a5a --- /dev/null +++ b/Resources/Documentation/Gem/GEMglLoadMatrixf.md @@ -0,0 +1,23 @@ + +--- +title: GEMglLoadMatrixf +description: replace the current matrix with a single-precision matrix +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 column-major matrix. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 column-major matrix. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglLoadName.md b/Resources/Documentation/Gem/GEMglLoadName.md new file mode 100644 index 0000000000..fb804e00d5 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglLoadName.md @@ -0,0 +1,23 @@ + +--- +title: GEMglLoadName +description: load a single unsigned integer value into the name stack +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the name to be loaded into the name stack. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the name to be loaded into the name stack. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglLoadTransposeMatrixd.md b/Resources/Documentation/Gem/GEMglLoadTransposeMatrixd.md new file mode 100644 index 0000000000..b781fb7903 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglLoadTransposeMatrixd.md @@ -0,0 +1,23 @@ + +--- +title: GEMglLoadTransposeMatrixd +description: replace the current matrix with the transpose of a double-precision matrix +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 row-major matrix. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 row-major matrix. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglLoadTransposeMatrixf.md b/Resources/Documentation/Gem/GEMglLoadTransposeMatrixf.md new file mode 100644 index 0000000000..b29238ef65 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglLoadTransposeMatrixf.md @@ -0,0 +1,23 @@ + +--- +title: GEMglLoadTransposeMatrixf +description: replace the current matrix with the transpose of a single-precision matrix +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 row-major matrix. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 row-major matrix. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglLogicOp.md b/Resources/Documentation/Gem/GEMglLogicOp.md new file mode 100644 index 0000000000..3afb08cf95 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglLogicOp.md @@ -0,0 +1,24 @@ + +--- +title: GEMglLogicOp +description: set the pixel transfer operation for rendering +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies a symbolic constant that selects a logical operation. + default: GL_COPY +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies a symbolic constant that selects a logical operation. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMap1d.md b/Resources/Documentation/Gem/GEMglMap1d.md new file mode 100644 index 0000000000..a061511cde --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMap1d.md @@ -0,0 +1,29 @@ + +--- +title: GEMglMap1d +description: define a one-dimensional evaluator +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the kind of values being mapped. + default: GL_MAP1_VERTEX_3 + - type: float + description: Specifies the coefficients for the map. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the kind of values being mapped. + 3rd: + - type: float + description: Specifies the coefficients for the map. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMap1f.md b/Resources/Documentation/Gem/GEMglMap1f.md new file mode 100644 index 0000000000..78368279a6 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMap1f.md @@ -0,0 +1,29 @@ + +--- +title: GEMglMap1f +description: define a one-dimensional evaluator +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the kind of values being mapped. + default: GL_MAP1_VERTEX_3 + - type: float + description: Specifies the coefficients for the map. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the kind of values being mapped. + 3rd: + - type: float + description: Specifies the coefficients for the map. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMap2d.md b/Resources/Documentation/Gem/GEMglMap2d.md new file mode 100644 index 0000000000..c25983d700 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMap2d.md @@ -0,0 +1,29 @@ + +--- +title: GEMglMap2d +description: define a two-dimensional evaluator +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the kind of values being mapped. + default: GL_MAP2_VERTEX_3 + - type: float + description: Specifies the coefficients for the map. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the kind of values being mapped. + 3rd: + - type: float + description: Specifies the coefficients for the map. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMap2f.md b/Resources/Documentation/Gem/GEMglMap2f.md new file mode 100644 index 0000000000..7feb928f8e --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMap2f.md @@ -0,0 +1,28 @@ + +--- +title: GEMglMap2f +description: define a two-dimensional evaluator +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the kind of values being mapped. + default: GL_MAP2_VERTEX_3 + - type: float + description: Specifies the coefficients for the map. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the kind of values being mapped. + 3rd: + - type: float + description: Specifies the coefficients for the map. +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/GEMglMapGrid1d.md b/Resources/Documentation/Gem/GEMglMapGrid1d.md new file mode 100644 index 0000000000..02899519b3 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMapGrid1d.md @@ -0,0 +1,29 @@ + +--- +title: GEMglMapGrid1d +description: define a one-dimensional grid for evaluators +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the number of partitions in the grid. + default: 1 + - type: float + description: Specifies the coordinates of the two end points of the grid. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the number of partitions in the grid. + 3rd: + - type: float + description: Specifies the coordinates of the two end points of the grid. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMapGrid1f.md b/Resources/Documentation/Gem/GEMglMapGrid1f.md new file mode 100644 index 0000000000..5d151493ea --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMapGrid1f.md @@ -0,0 +1,29 @@ + +--- +title: GEMglMapGrid1f +description: define a one-dimensional grid for evaluators +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the number of partitions in the grid. + default: 1 + - type: float + description: Specifies the coordinates of the two end points of the grid. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the number of partitions in the grid. + 3rd: + - type: float + description: Specifies the coordinates of the two end points of the grid. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMapGrid2d.md b/Resources/Documentation/Gem/GEMglMapGrid2d.md new file mode 100644 index 0000000000..c7b182575a --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMapGrid2d.md @@ -0,0 +1,40 @@ + +--- +title: GEMglMapGrid2d +description: define a two-dimensional grid for evaluators +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the number of partitions in the u direction of the grid. + default: 1 + - type: float + description: Specifies the coordinates of the two end points of the u grid. + - type: float + description: Specifies the number of partitions in the v direction of the grid. + default: 1 + - type: float + description: Specifies the coordinates of the two end points of the v grid. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the number of partitions in the u direction of the grid. + 3rd: + - type: float + description: Specifies the coordinates of the two end points of the u grid. + 4th: + - type: float + description: Specifies the number of partitions in the v direction of the grid. + 5th: + - type: float + description: Specifies the coordinates of the two end points of the v grid. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMapGrid2f.md b/Resources/Documentation/Gem/GEMglMapGrid2f.md new file mode 100644 index 0000000000..5b4ac50288 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMapGrid2f.md @@ -0,0 +1,40 @@ + +--- +title: GEMglMapGrid2f +description: define a two-dimensional grid for evaluators +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the number of partitions in the u direction of the grid. + default: 1 + - type: float + description: Specifies the coordinates of the two end points of the u grid. + - type: float + description: Specifies the number of partitions in the v direction of the grid. + default: 1 + - type: float + description: Specifies the coordinates of the two end points of the v grid. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the number of partitions in the u direction of the grid. + 3rd: + - type: float + description: Specifies the coordinates of the two end points of the u grid. + 4th: + - type: float + description: Specifies the number of partitions in the v direction of the grid. + 5th: + - type: float + description: Specifies the coordinates of the two end points of the v grid. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMaterialf.md b/Resources/Documentation/Gem/GEMglMaterialf.md new file mode 100644 index 0000000000..34011a3ed2 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMaterialf.md @@ -0,0 +1,36 @@ + +--- +title: GEMglMaterialf +description: specify material parameters for lighting +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the face and the material parameter. + default: GL_FRONT + - type: float + description: Specifies the material parameter. + default: GL_DIFFUSE + - type: float + description: Specifies the parameter value. + default: 0.2 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the face and the material parameter. + 3rd: + - type: float + description: Specifies the material parameter. + 4th: + - type: float + description: Specifies the parameter value. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMaterialfv.md b/Resources/Documentation/Gem/GEMglMaterialfv.md new file mode 100644 index 0000000000..632a9cb00b --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMaterialfv.md @@ -0,0 +1,36 @@ + +--- +title: GEMglMaterialfv +description: specify material parameters for lighting with float values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the face and the material parameter. + default: GL_FRONT + - type: float + description: Specifies the material parameter. + default: GL_DIFFUSE + - type: float + description: Specifies the RGBA parameter values. + default: [0.2, 0.2, 0.2, 1.0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the face and the material parameter. + 3rd: + - type: float + description: Specifies the material parameter. + 4th: + - type: list + description: Specifies the RGBA parameter values. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMateriali.md b/Resources/Documentation/Gem/GEMglMateriali.md new file mode 100644 index 0000000000..368c6b303f --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMateriali.md @@ -0,0 +1,36 @@ + +--- +title: GEMglMateriali +description: specify material parameters for lighting with integer values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the face and the material parameter. + default: GL_FRONT + - type: float + description: Specifies the material parameter. + default: GL_DIFFUSE + - type: float + description: Specifies the parameter value. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the face and the material parameter. + 3rd: + - type: float + description: Specifies the material parameter. + 4th: + - type: float + description: Specifies the parameter value. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMatrixMode.md b/Resources/Documentation/Gem/GEMglMatrixMode.md new file mode 100644 index 0000000000..69d54c6698 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMatrixMode.md @@ -0,0 +1,24 @@ + +--- +title: GEMglMatrixMode +description: set the current matrix mode +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the matrix mode. + default: GL_MODELVIEW +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the matrix mode. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMultMatrixd.md b/Resources/Documentation/Gem/GEMglMultMatrixd.md new file mode 100644 index 0000000000..65de835552 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMultMatrixd.md @@ -0,0 +1,23 @@ + +--- +title: GEMglMultMatrixd +description: multiply the current matrix with a double-precision matrix +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 column-major matrix. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 column-major matrix. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMultMatrixf.md b/Resources/Documentation/Gem/GEMglMultMatrixf.md new file mode 100644 index 0000000000..35638230cd --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMultMatrixf.md @@ -0,0 +1,23 @@ + +--- +title: GEMglMultMatrixf +description: multiply the current matrix with a single-precision matrix +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 column-major matrix. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 column-major matrix. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMultTransposeMatrixf.md b/Resources/Documentation/Gem/GEMglMultTransposeMatrixf.md new file mode 100644 index 0000000000..7516d1b206 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMultTransposeMatrixf.md @@ -0,0 +1,23 @@ + +--- +title: GEMglMultTransposeMatrixf +description: multiply the current matrix with the transpose of a single-precision matrix +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 column-major matrix. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 column-major matrix. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMultiTexCoord2f.md b/Resources/Documentation/Gem/GEMglMultiTexCoord2f.md new file mode 100644 index 0000000000..2011be45c0 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMultiTexCoord2f.md @@ -0,0 +1,32 @@ + +--- +title: GEMglMultiTexCoord2f +description: set the texture coordinates for a specified texture unit with two float values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the texture unit. + default: GL_TEXTURE0 + - type: float + description: Specifies the s and t texture coordinates. + default: 0.0 + min: 0.0 + max: 1.0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the texture unit. + 3rd: + - type: float + description: Specifies the s and t texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglMultiTexCoord2fARB.md b/Resources/Documentation/Gem/GEMglMultiTexCoord2fARB.md new file mode 100644 index 0000000000..9a2d2accc8 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglMultiTexCoord2fARB.md @@ -0,0 +1,32 @@ + +--- +title: GEMglMultiTexCoord2fARB +description: set the texture coordinates for a specified texture unit with two float values (ARB extension) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the texture unit. + default: GL_TEXTURE0_ARB + - type: float + description: Specifies the s and t texture coordinates. + default: 0.0 + min: 0.0 + max: 1.0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the texture unit. + 3rd: + - type: float + description: Specifies the s and t texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglNewList.md b/Resources/Documentation/Gem/GEMglNewList.md new file mode 100644 index 0000000000..8cba105978 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglNewList.md @@ -0,0 +1,31 @@ + +--- +title: GEMglNewList +description: create or replace a display list +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies a unique integer that represents the display list. + default: 1 + - type: float + description: Specifies the compilation mode. + default: GL_COMPILE +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies a unique integer that represents the display list. + 3rd: + - type: float + description: Specifies the compilation mode. +outlets: + 1st: + - type: gemlist +draft: false +--- + + diff --git a/Resources/Documentation/Gem/GEMglNormal3b.md b/Resources/Documentation/Gem/GEMglNormal3b.md new file mode 100644 index 0000000000..f683a2e6e0 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglNormal3b.md @@ -0,0 +1,24 @@ + +--- +title: GEMglNormal3b +description: set the current normal vector with three signed byte values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z components of the current normal vector. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z components of the current normal vector. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglNormal3bv.md b/Resources/Documentation/Gem/GEMglNormal3bv.md new file mode 100644 index 0000000000..32fe888567 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglNormal3bv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglNormal3bv +description: set the current normal vector with a pointer to three signed byte values +categories: + - object +pdcategory: Graphics +arguments: + - type: pointer + description: Specifies a pointer to an array of three signed bytes. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: pointer + description: Specifies a pointer to an array of three signed bytes. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglNormal3d.md b/Resources/Documentation/Gem/GEMglNormal3d.md new file mode 100644 index 0000000000..17fc3ff9b2 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglNormal3d.md @@ -0,0 +1,24 @@ + +--- +title: GEMglNormal3d +description: set the current normal vector with three double-precision values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z components of the current normal vector. + default: 0.0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z components of the current normal vector. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglNormal3dv.md b/Resources/Documentation/Gem/GEMglNormal3dv.md new file mode 100644 index 0000000000..c0434fe24d --- /dev/null +++ b/Resources/Documentation/Gem/GEMglNormal3dv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglNormal3dv +description: set the current normal vector with a pointer to three double-precision values +categories: + - object +pdcategory: Graphics +arguments: + - type: pointer + description: Specifies a pointer to an array of three double-precision values. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: pointer + description: Specifies a pointer to an array of three double-precision values. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglNormal3f.md b/Resources/Documentation/Gem/GEMglNormal3f.md new file mode 100644 index 0000000000..858a1c95ef --- /dev/null +++ b/Resources/Documentation/Gem/GEMglNormal3f.md @@ -0,0 +1,24 @@ + +--- +title: GEMglNormal3f +description: set the current normal vector with three single-precision values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z components of the current normal vector. + default: 0.0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z components of the current normal vector. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglNormal3fv.md b/Resources/Documentation/Gem/GEMglNormal3fv.md new file mode 100644 index 0000000000..dd0b9dd6b9 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglNormal3fv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglNormal3fv +description: set the current normal vector with a pointer to three single-precision values +categories: + - object +pdcategory: Graphics +arguments: + - type: pointer + description: Specifies a pointer to an array of three single-precision values. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: pointer + description: Specifies a pointer to an array of three single-precision values. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglNormal3i.md b/Resources/Documentation/Gem/GEMglNormal3i.md new file mode 100644 index 0000000000..cacd97e093 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglNormal3i.md @@ -0,0 +1,24 @@ + +--- +title: GEMglNormal3i +description: set the current normal vector with three integer values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z components of the current normal vector. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z components of the current normal vector. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglNormal3iv.md b/Resources/Documentation/Gem/GEMglNormal3iv.md new file mode 100644 index 0000000000..71c6912ff7 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglNormal3iv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglNormal3iv +description: set the current normal vector with a pointer to three integer values +categories: + - object +pdcategory: Graphics +arguments: + - type: pointer + description: Specifies a pointer to an array of three integers. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: pointer + description: Specifies a pointer to an array of three integers. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglNormal3s.md b/Resources/Documentation/Gem/GEMglNormal3s.md new file mode 100644 index 0000000000..450fa642c8 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglNormal3s.md @@ -0,0 +1,24 @@ + +--- +title: GEMglNormal3s +description: set the current normal vector with three short integer values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z components of the current normal vector. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z components of the current normal vector. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglNormal3sv.md b/Resources/Documentation/Gem/GEMglNormal3sv.md new file mode 100644 index 0000000000..23c9c7de66 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglNormal3sv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglNormal3sv +description: set the current normal vector with a pointer to three short integer values +categories: + - object +pdcategory: Graphics +arguments: + - type: pointer + description: Specifies a pointer to an array of three short integers. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: pointer + description: Specifies a pointer to an array of three short integers. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglOrtho.md b/Resources/Documentation/Gem/GEMglOrtho.md new file mode 100644 index 0000000000..0831f2f3d6 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglOrtho.md @@ -0,0 +1,36 @@ + +--- +title: GEMglOrtho +description: multiply the current matrix with an orthographic matrix +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the coordinates for the left and right vertical clipping planes. + default: 0.0 + - type: float + description: Specifies the coordinates for the bottom and top horizontal clipping planes. + default: 0.0 + - type: float + description: Specifies the coordinates for the near and far depth clipping planes. + default: 0.0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the coordinates for the left and right vertical clipping planes. + 3rd: + - type: float + description: Specifies the coordinates for the bottom and top horizontal clipping planes. + 4th: + - type: float + description: Specifies the coordinates for the near and far depth clipping planes. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPassThrough.md b/Resources/Documentation/Gem/GEMglPassThrough.md new file mode 100644 index 0000000000..f25a95321a --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPassThrough.md @@ -0,0 +1,23 @@ + +--- +title: GEMglPassThrough +description: pass a token through the feedback pipeline +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies a token value to be passed through the feedback pipeline. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies a token value to be passed through the feedback pipeline. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPixelStoref.md b/Resources/Documentation/Gem/GEMglPixelStoref.md new file mode 100644 index 0000000000..849ca16f5a --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPixelStoref.md @@ -0,0 +1,28 @@ + +--- +title: GEMglPixelStoref +description: set pixel storage modes for read pixels +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the symbolic name of the parameter to be set. + - type: float + description: Specifies the value to be set. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the symbolic name of the parameter to be set. + 3rd: + - type: float + description: Specifies the value to be set. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPixelStorei.md b/Resources/Documentation/Gem/GEMglPixelStorei.md new file mode 100644 index 0000000000..72b5fa9a7a --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPixelStorei.md @@ -0,0 +1,28 @@ + +--- +title: GEMglPixelStorei +description: set pixel storage modes for read pixels (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the symbolic name of the parameter to be set. + - type: float + description: Specifies the value to be set. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the symbolic name of the parameter to be set. + 3rd: + - type: float + description: Specifies the value to be set. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPixelTransferf.md b/Resources/Documentation/Gem/GEMglPixelTransferf.md new file mode 100644 index 0000000000..9db11dd5d0 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPixelTransferf.md @@ -0,0 +1,28 @@ + +--- +title: GEMglPixelTransferf +description: set pixel transfer modes for read pixels +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the symbolic name of the parameter to be set. + - type: float + description: Specifies the value to be set. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the symbolic name of the parameter to be set. + 3rd: + - type: float + description: Specifies the value to be set. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPixelTransferi.md b/Resources/Documentation/Gem/GEMglPixelTransferi.md new file mode 100644 index 0000000000..86bbcdf405 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPixelTransferi.md @@ -0,0 +1,28 @@ + +--- +title: GEMglPixelTransferi +description: set pixel transfer modes for read pixels (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the symbolic name of the parameter to be set. + - type: float + description: Specifies the value to be set. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the symbolic name of the parameter to be set. + 3rd: + - type: float + description: Specifies the value to be set. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPixelZoom.md b/Resources/Documentation/Gem/GEMglPixelZoom.md new file mode 100644 index 0000000000..99eae86ea7 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPixelZoom.md @@ -0,0 +1,30 @@ + +--- +title: GEMglPixelZoom +description: set pixel zoom factors for read pixels +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x and y zoom factors. + default: 1.0 + - type: float + description: Specifies the x and y zoom factors. + default: 1.0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x and y zoom factors. + 3rd: + - type: float + description: Specifies the x and y zoom factors. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPointSize.md b/Resources/Documentation/Gem/GEMglPointSize.md new file mode 100644 index 0000000000..3c12058111 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPointSize.md @@ -0,0 +1,24 @@ + +--- +title: GEMglPointSize +description: specify the rasterized point size +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the size of rasterized points. + default: 1.0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the size of rasterized points. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPolygonMode.md b/Resources/Documentation/Gem/GEMglPolygonMode.md new file mode 100644 index 0000000000..95d5957a23 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPolygonMode.md @@ -0,0 +1,28 @@ + +--- +title: GEMglPolygonMode +description: set the front and back polygon fill mode +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the face for which the polygon mode is set. + - type: float + description: Specifies how the polygons will be rasterized. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the face for which the polygon mode is set. + 3rd: + - type: float + description: Specifies how the polygons will be rasterized. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPolygonOffset.md b/Resources/Documentation/Gem/GEMglPolygonOffset.md new file mode 100644 index 0000000000..e9479888b3 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPolygonOffset.md @@ -0,0 +1,30 @@ + +--- +title: GEMglPolygonOffset +description: set the scale and units used to calculate depth values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies a scale factor that is used to create a variable depth offset for each polygon. + default: 0.0 + - type: float + description: Specifies a constant factor that is used to create a variable depth offset for each polygon. + default: 0.0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies a scale factor that is used to create a variable depth offset for each polygon. + 3rd: + - type: float + description: Specifies a constant factor that is used to create a variable depth offset for each polygon. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPopAttrib.md b/Resources/Documentation/Gem/GEMglPopAttrib.md new file mode 100644 index 0000000000..31dc49da25 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPopAttrib.md @@ -0,0 +1,18 @@ + +--- +title: GEMglPopAttrib +description: restore the state of the attribute stack +categories: + - object +pdcategory: Graphics +arguments: null +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPopClientAttrib.md b/Resources/Documentation/Gem/GEMglPopClientAttrib.md new file mode 100644 index 0000000000..45dbf50dcc --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPopClientAttrib.md @@ -0,0 +1,18 @@ + +--- +title: GEMglPopClientAttrib +description: restore the state of the client attribute stack +categories: + - object +pdcategory: Graphics +arguments: null +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPopMatrix.md b/Resources/Documentation/Gem/GEMglPopMatrix.md new file mode 100644 index 0000000000..3a8bd43866 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPopMatrix.md @@ -0,0 +1,18 @@ + +--- +title: GEMglPopMatrix +description: pop the current matrix stack +categories: + - object +pdcategory: Graphics +arguments: null +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPopName.md b/Resources/Documentation/Gem/GEMglPopName.md new file mode 100644 index 0000000000..dceed74418 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPopName.md @@ -0,0 +1,18 @@ + +--- +title: GEMglPopName +description: pop the top name from the name stack +categories: + - object +pdcategory: Graphics +arguments: null +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPrioritizeTextures.md b/Resources/Documentation/Gem/GEMglPrioritizeTextures.md new file mode 100644 index 0000000000..d3b50f9c80 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPrioritizeTextures.md @@ -0,0 +1,33 @@ + +--- +title: GEMglPrioritizeTextures +description: set texture object priorities +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the number of texture names in textures. + - type: float + description: Specifies an array containing the texture names to be prioritized. + - type: float + description: Specifies an array of priorities for the corresponding texture names in textures. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the number of texture names in textures. + 3rd: + - type: float + description: Specifies an array containing the texture names to be prioritized. + 4th: + - type: float + description: Specifies an array of priorities for the corresponding texture names in textures. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglProgramEnvParameter4dARB.md b/Resources/Documentation/Gem/GEMglProgramEnvParameter4dARB.md new file mode 100644 index 0000000000..a067ac7da5 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglProgramEnvParameter4dARB.md @@ -0,0 +1,48 @@ + +--- +title: GEMglProgramEnvParameter4dARB +description: specify a double-precision environment parameter for a program object +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target program object. + - type: float + description: Specifies the index of the parameter to be modified. + - type: float + description: Specifies the new parameter value. + - type: float + description: Specifies the new parameter value. + - type: float + description: Specifies the new parameter value. + - type: float + description: Specifies the new parameter value. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target program object. + 3rd: + - type: float + description: Specifies the index of the parameter to be modified. + 4th: + - type: float + description: Specifies the new parameter value. + 5th: + - type: float + description: Specifies the new parameter value. + 6th: + - type: float + description: Specifies the new parameter value. + 7th: + - type: float + description: Specifies the new parameter value. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglProgramEnvParameter4fvARB.md b/Resources/Documentation/Gem/GEMglProgramEnvParameter4fvARB.md new file mode 100644 index 0000000000..61998fb56f --- /dev/null +++ b/Resources/Documentation/Gem/GEMglProgramEnvParameter4fvARB.md @@ -0,0 +1,48 @@ + +--- +title: GEMglProgramEnvParameter4fvARB +description: specify a float-precision environment parameter for a program object +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target program object. + - type: float + description: Specifies the index of the parameter to be modified. + - type: float + description: Specifies the new parameter value. + - type: float + description: Specifies the new parameter value. + - type: float + description: Specifies the new parameter value. + - type: float + description: Specifies the new parameter value. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target program object. + 3rd: + - type: float + description: Specifies the index of the parameter to be modified. + 4th: + - type: float + description: Specifies the new parameter value. + 5th: + - type: float + description: Specifies the new parameter value. + 6th: + - type: float + description: Specifies the new parameter value. + 7th: + - type: float + description: Specifies the new parameter value. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglProgramLocalParameter4fvARB.md b/Resources/Documentation/Gem/GEMglProgramLocalParameter4fvARB.md new file mode 100644 index 0000000000..e7f4afc2a7 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglProgramLocalParameter4fvARB.md @@ -0,0 +1,48 @@ + +--- +title: GEMglProgramLocalParameter4fvARB +description: specify a float-precision local parameter for a program object +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target program object. + - type: float + description: Specifies the index of the parameter to be modified. + - type: float + description: Specifies the new parameter value. + - type: float + description: Specifies the new parameter value. + - type: float + description: Specifies the new parameter value. + - type: float + description: Specifies the new parameter value. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target program object. + 3rd: + - type: float + description: Specifies the index of the parameter to be modified. + 4th: + - type: float + description: Specifies the new parameter value. + 5th: + - type: float + description: Specifies the new parameter value. + 6th: + - type: float + description: Specifies the new parameter value. + 7th: + - type: float + description: Specifies the new parameter value. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglProgramStringARB.md b/Resources/Documentation/Gem/GEMglProgramStringARB.md new file mode 100644 index 0000000000..afbbb4726a --- /dev/null +++ b/Resources/Documentation/Gem/GEMglProgramStringARB.md @@ -0,0 +1,43 @@ + +--- +title: GEMglProgramStringARB +description: load a program object with a string +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target program object. + - type: float + description: Specifies the format of the shader strings. + - type: float + description: Specifies the number of strings in the program. + - type: float + description: Specifies an array of pointers to the source code strings. + - type: float + description: Specifies the length of the source code strings. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target program object. + 3rd: + - type: float + description: Specifies the format of the shader strings. + 4th: + - type: float + description: Specifies the number of strings in the program. + 5th: + - type: float + description: Specifies an array of pointers to the source code strings. + 6th: + - type: float + description: Specifies the length of the source code strings. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPushAttrib.md b/Resources/Documentation/Gem/GEMglPushAttrib.md new file mode 100644 index 0000000000..8539aff50f --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPushAttrib.md @@ -0,0 +1,23 @@ + +--- +title: GEMglPushAttrib +description: push and save attribute state +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies which groups of attributes to save in the display list. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies which groups of attributes to save in the display list. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPushClientAttrib.md b/Resources/Documentation/Gem/GEMglPushClientAttrib.md new file mode 100644 index 0000000000..8560c0284e --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPushClientAttrib.md @@ -0,0 +1,23 @@ + +--- +title: GEMglPushClientAttrib +description: push and save client attribute state +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies which groups of client attributes to save. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies which groups of client attributes to save. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPushMatrix.md b/Resources/Documentation/Gem/GEMglPushMatrix.md new file mode 100644 index 0000000000..19aa4779e5 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPushMatrix.md @@ -0,0 +1,18 @@ + +--- +title: GEMglPushMatrix +description: push the current matrix stack +categories: + - object +pdcategory: Graphics +arguments: null +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglPushName.md b/Resources/Documentation/Gem/GEMglPushName.md new file mode 100644 index 0000000000..a1f1966243 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglPushName.md @@ -0,0 +1,23 @@ + +--- +title: GEMglPushName +description: push a name onto the name stack +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies a name to be pushed onto the name stack. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies a name to be pushed onto the name stack. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos2d.md b/Resources/Documentation/Gem/GEMglRasterPos2d.md new file mode 100644 index 0000000000..eb94099a46 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos2d.md @@ -0,0 +1,28 @@ + +--- +title: GEMglRasterPos2d +description: set the current raster position +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos2dv.md b/Resources/Documentation/Gem/GEMglRasterPos2dv.md new file mode 100644 index 0000000000..9e69759fa4 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos2dv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglRasterPos2dv +description: set the current raster position (double-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos2f.md b/Resources/Documentation/Gem/GEMglRasterPos2f.md new file mode 100644 index 0000000000..5930cdcfec --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos2f.md @@ -0,0 +1,28 @@ + +--- +title: GEMglRasterPos2f +description: set the current raster position +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos2fv.md b/Resources/Documentation/Gem/GEMglRasterPos2fv.md new file mode 100644 index 0000000000..9a0f8bb396 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos2fv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglRasterPos2fv +description: set the current raster position (single-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos2i.md b/Resources/Documentation/Gem/GEMglRasterPos2i.md new file mode 100644 index 0000000000..294ecac6fd --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos2i.md @@ -0,0 +1,28 @@ + +--- +title: GEMglRasterPos2i +description: set the current raster position +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos2iv.md b/Resources/Documentation/Gem/GEMglRasterPos2iv.md new file mode 100644 index 0000000000..1e0a3f98e4 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos2iv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglRasterPos2iv +description: set the current raster position (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos2s.md b/Resources/Documentation/Gem/GEMglRasterPos2s.md new file mode 100644 index 0000000000..356e25a5e1 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos2s.md @@ -0,0 +1,28 @@ + +--- +title: GEMglRasterPos2s +description: set the current raster position +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos2sv.md b/Resources/Documentation/Gem/GEMglRasterPos2sv.md new file mode 100644 index 0000000000..2ea10328f4 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos2sv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglRasterPos2sv +description: set the current raster position (short-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos3d.md b/Resources/Documentation/Gem/GEMglRasterPos3d.md new file mode 100644 index 0000000000..0195e053d4 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos3d.md @@ -0,0 +1,33 @@ + +--- +title: GEMglRasterPos3d +description: set the current raster position +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. + 4th: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos3dv.md b/Resources/Documentation/Gem/GEMglRasterPos3dv.md new file mode 100644 index 0000000000..e98f0ac935 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos3dv.md @@ -0,0 +1,26 @@ + +--- +title: GEMglRasterPos3dv +description: set the current raster position (double-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos3f.md b/Resources/Documentation/Gem/GEMglRasterPos3f.md new file mode 100644 index 0000000000..6c32c37abc --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos3f.md @@ -0,0 +1,33 @@ + +--- +title: GEMglRasterPos3f +description: set the current raster position +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. + 4th: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos3fv.md b/Resources/Documentation/Gem/GEMglRasterPos3fv.md new file mode 100644 index 0000000000..9277b31087 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos3fv.md @@ -0,0 +1,26 @@ + +--- +title: GEMglRasterPos3fv +description: set the current raster position (single-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos3i.md b/Resources/Documentation/Gem/GEMglRasterPos3i.md new file mode 100644 index 0000000000..b27b9612e8 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos3i.md @@ -0,0 +1,33 @@ + +--- +title: GEMglRasterPos3i +description: set the current raster position +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. + 4th: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos3iv.md b/Resources/Documentation/Gem/GEMglRasterPos3iv.md new file mode 100644 index 0000000000..357341e8a0 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos3iv.md @@ -0,0 +1,26 @@ + +--- +title: GEMglRasterPos3iv +description: set the current raster position (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos3s.md b/Resources/Documentation/Gem/GEMglRasterPos3s.md new file mode 100644 index 0000000000..a8b87b8b05 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos3s.md @@ -0,0 +1,33 @@ + +--- +title: GEMglRasterPos3s +description: set the current raster position +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. + 4th: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos3sv.md b/Resources/Documentation/Gem/GEMglRasterPos3sv.md new file mode 100644 index 0000000000..4bfe5ea243 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos3sv.md @@ -0,0 +1,26 @@ + +--- +title: GEMglRasterPos3sv +description: set the current raster position (short-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos4d.md b/Resources/Documentation/Gem/GEMglRasterPos4d.md new file mode 100644 index 0000000000..ff1a532faa --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos4d.md @@ -0,0 +1,38 @@ + +--- +title: GEMglRasterPos4d +description: set the current raster position +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. + 4th: + - type: float + description: Specifies the x, y, and z window coordinates. + 5th: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos4dv.md b/Resources/Documentation/Gem/GEMglRasterPos4dv.md new file mode 100644 index 0000000000..3867314b98 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos4dv.md @@ -0,0 +1,32 @@ + +--- +title: GEMglRasterPos4dv +description: set the current raster position (double-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. + 4th: + - type: float + description: Specifies the x, y, and z window coordinates. + 5th: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos4f.md b/Resources/Documentation/Gem/GEMglRasterPos4f.md new file mode 100644 index 0000000000..d85cd04353 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos4f.md @@ -0,0 +1,38 @@ + +--- +title: GEMglRasterPos4f +description: set the current raster position +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. + 4th: + - type: float + description: Specifies the x, y, and z window coordinates. + 5th: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos4fv.md b/Resources/Documentation/Gem/GEMglRasterPos4fv.md new file mode 100644 index 0000000000..40e4f015e1 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos4fv.md @@ -0,0 +1,32 @@ + +--- +title: GEMglRasterPos4fv +description: set the current raster position (single-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. + 4th: + - type: float + description: Specifies the x, y, and z window coordinates. + 5th: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos4i.md b/Resources/Documentation/Gem/GEMglRasterPos4i.md new file mode 100644 index 0000000000..6c318e80bb --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos4i.md @@ -0,0 +1,38 @@ + +--- +title: GEMglRasterPos4i +description: set the current raster position +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. + 4th: + - type: float + description: Specifies the x, y, and z window coordinates. + 5th: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos4iv.md b/Resources/Documentation/Gem/GEMglRasterPos4iv.md new file mode 100644 index 0000000000..ff2a4a09b8 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos4iv.md @@ -0,0 +1,32 @@ + +--- +title: GEMglRasterPos4iv +description: set the current raster position (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. + 4th: + - type: float + description: Specifies the x, y, and z window coordinates. + 5th: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos4s.md b/Resources/Documentation/Gem/GEMglRasterPos4s.md new file mode 100644 index 0000000000..dde18f4186 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos4s.md @@ -0,0 +1,38 @@ + +--- +title: GEMglRasterPos4s +description: set the current raster position +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. + 4th: + - type: float + description: Specifies the x, y, and z window coordinates. + 5th: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRasterPos4sv.md b/Resources/Documentation/Gem/GEMglRasterPos4sv.md new file mode 100644 index 0000000000..7eb535f984 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRasterPos4sv.md @@ -0,0 +1,32 @@ + +--- +title: GEMglRasterPos4sv +description: set the current raster position (short-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates. + 4th: + - type: float + description: Specifies the x, y, and z window coordinates. + 5th: + - type: float + description: Specifies the x, y, and z window coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRectd.md b/Resources/Documentation/Gem/GEMglRectd.md new file mode 100644 index 0000000000..7049d35f6b --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRectd.md @@ -0,0 +1,34 @@ + +--- +title: GEMglRectd +description: draw a rectangle +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates of the first corner of the rectangle. + - type: float + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates of the first corner of the rectangle. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. + 4th: + - type: float + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. + 5th: + - type: float + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRectf.md b/Resources/Documentation/Gem/GEMglRectf.md new file mode 100644 index 0000000000..87ccb47c18 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRectf.md @@ -0,0 +1,34 @@ + +--- +title: GEMglRectf +description: draw a rectangle +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates of the first corner of the rectangle. + - type: float + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates of the first corner of the rectangle. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. + 4th: + - type: float + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. + 5th: + - type: float + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRecti.md b/Resources/Documentation/Gem/GEMglRecti.md new file mode 100644 index 0000000000..d475387b03 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRecti.md @@ -0,0 +1,34 @@ + +--- +title: GEMglRecti +description: draw a rectangle +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z window coordinates of the first corner of the rectangle. + - type: float + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z window coordinates of the first corner of the rectangle. + 3rd: + - type: float + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. + 4th: + - type: float + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. + 5th: + - type: float + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRects.md b/Resources/Documentation/Gem/GEMglRects.md new file mode 100644 index 0000000000..1969a776f0 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRects.md @@ -0,0 +1,34 @@ + +--- +title: GEMglRects +description: draw a rectangle +categories: + - object +pdcategory: Graphics +arguments: + - type: GLshort + description: Specifies the x, y, and z window coordinates of the first corner of the rectangle. + - type: GLshort + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: GLshort + description: Specifies the x, y, and z window coordinates of the first corner of the rectangle. + 3rd: + - type: GLshort + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. + 4th: + - type: GLshort + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. + 5th: + - type: GLshort + description: Specifies the x, y, and z window coordinates of the second corner of the rectangle. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRenderMode.md b/Resources/Documentation/Gem/GEMglRenderMode.md new file mode 100644 index 0000000000..b851710527 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRenderMode.md @@ -0,0 +1,16 @@ + +--- +title: GEMglRenderMode +description: set OpenGL rendering mode +categories: + - object +pdcategory: Graphics +arguments: + - type: GLenum + description: Specifies the rendering mode. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglReportError.md b/Resources/Documentation/Gem/GEMglReportError.md new file mode 100644 index 0000000000..b4a707dc57 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglReportError.md @@ -0,0 +1,13 @@ + +--- +title: GEMglReportError +description: get and clear OpenGL error flag +categories: + - object +pdcategory: Graphics +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRotated.md b/Resources/Documentation/Gem/GEMglRotated.md new file mode 100644 index 0000000000..153f0110ca --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRotated.md @@ -0,0 +1,20 @@ + +--- +title: GEMglRotated +description: multiply the current matrix by a rotation matrix (double-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: GLdouble + description: Specifies the angle of rotation, in degrees. + - type: GLdouble + description: Specifies the x, y, and z coordinates of a vector, respectively. + - type: GLdouble + description: Specifies the angle of rotation, in degrees. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglRotatef.md b/Resources/Documentation/Gem/GEMglRotatef.md new file mode 100644 index 0000000000..14417be53b --- /dev/null +++ b/Resources/Documentation/Gem/GEMglRotatef.md @@ -0,0 +1,19 @@ + +--- +title: GEMglRotatef +description: multiply the current matrix by a rotation matrix +categories: + - object +pdcategory: Graphics +arguments: + - type: GLfloat + description: Specifies the angle of rotation, in degrees. + - type: GLfloat + description: Specifies the x, y, and z coordinates of a vector, respectively. + - type: GLfloat + description: Specifies the angle of rotation, in degrees. +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/GEMglScaled.md b/Resources/Documentation/Gem/GEMglScaled.md new file mode 100644 index 0000000000..1175dfc308 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglScaled.md @@ -0,0 +1,20 @@ + +--- +title: GEMglScaled +description: multiply the current matrix by a general scaling matrix (double-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the scale factors along the x, y, and z axes, respectively. + - type: float + description: Specifies the scale factors along the x, y, and z axes, respectively. + - type: float + description: Specifies the scale factors along the x, y, and z axes, respectively. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglScalef.md b/Resources/Documentation/Gem/GEMglScalef.md new file mode 100644 index 0000000000..4d2421c7e8 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglScalef.md @@ -0,0 +1,20 @@ + +--- +title: GEMglScalef +description: multiply the current matrix by a general scaling matrix +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the scale factors along the x, y, and z axes, respectively. + - type: float + description: Specifies the scale factors along the x, y, and z axes, respectively. + - type: float + description: Specifies the scale factors along the x, y, and z axes, respectively. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglScissor.md b/Resources/Documentation/Gem/GEMglScissor.md new file mode 100644 index 0000000000..0e82f8de78 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglScissor.md @@ -0,0 +1,38 @@ + +--- +title: GEMglScissor +description: define the scissor box +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the lower left corner of the scissor box. + - type: float + description: Specifies the lower left corner of the scissor box. + - type: float + description: Specifies the width and height of the scissor box. + - type: float + description: Specifies the width and height of the scissor box. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the lower left corner of the scissor box. + 3rd: + - type: float + description: Specifies the lower left corner of the scissor box. + 4th: + - type: float + description: Specifies the width and height of the scissor box. + 5th: + - type: float + description: Specifies the width and height of the scissor box. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglSelectBuffer.md b/Resources/Documentation/Gem/GEMglSelectBuffer.md new file mode 100644 index 0000000000..d2a8545a2d --- /dev/null +++ b/Resources/Documentation/Gem/GEMglSelectBuffer.md @@ -0,0 +1,23 @@ + +--- +title: GEMglSelectBuffer +description: establish a buffer for selection mode +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the size of the selection buffer. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the size of the selection buffer. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglShadeModel.md b/Resources/Documentation/Gem/GEMglShadeModel.md new file mode 100644 index 0000000000..7ee42a8ac4 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglShadeModel.md @@ -0,0 +1,23 @@ + +--- +title: GEMglShadeModel +description: select flat or smooth shading +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the shading model. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the shading model. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglStencilFunc.md b/Resources/Documentation/Gem/GEMglStencilFunc.md new file mode 100644 index 0000000000..a45fc8a085 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglStencilFunc.md @@ -0,0 +1,33 @@ + +--- +title: GEMglStencilFunc +description: set front and back function and reference value for stencil testing +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the stencil test function. + - type: float + description: Specifies the reference value for the stencil test. + - type: float + description: Specifies a mask that is ANDed with both the reference value and the stored stencil value when the test is done. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the stencil test function. + 3rd: + - type: float + description: Specifies the reference value for the stencil test. + 4th: + - type: float + description: Specifies a mask that is ANDed with both the reference value and the stored stencil value when the test is done. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglStencilMask.md b/Resources/Documentation/Gem/GEMglStencilMask.md new file mode 100644 index 0000000000..a9defddcf0 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglStencilMask.md @@ -0,0 +1,23 @@ + +--- +title: GEMglStencilMask +description: control the writing of individual bits in the stencil planes +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the mask that is ANDed with the stencil value when it is written. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the mask that is ANDed with the stencil value when it is written. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglStencilOp.md b/Resources/Documentation/Gem/GEMglStencilOp.md new file mode 100644 index 0000000000..d0aaec7a08 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglStencilOp.md @@ -0,0 +1,33 @@ + +--- +title: GEMglStencilOp +description: set front and back stencil test actions +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the action to take when the stencil test fails. + - type: float + description: Specifies the action to take when the stencil test passes, but the depth test fails. + - type: float + description: Specifies the action to take when both the stencil test and the depth test pass. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the action to take when the stencil test fails. + 3rd: + - type: float + description: Specifies the action to take when the stencil test passes, but the depth test fails. + 4th: + - type: float + description: Specifies the action to take when both the stencil test and the depth test pass. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord1d.md b/Resources/Documentation/Gem/GEMglTexCoord1d.md new file mode 100644 index 0000000000..b4589e25c7 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord1d.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTexCoord1d +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s texture coordinate. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s texture coordinate. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord1dv.md b/Resources/Documentation/Gem/GEMglTexCoord1dv.md new file mode 100644 index 0000000000..dedbecdf92 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord1dv.md @@ -0,0 +1,15 @@ + +--- +title: GEMglTexCoord1dv +description: set the current texture coordinates (double-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s texture coordinate. +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/GEMglTexCoord1f.md b/Resources/Documentation/Gem/GEMglTexCoord1f.md new file mode 100644 index 0000000000..6df59ffa18 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord1f.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTexCoord1f +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s texture coordinate. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s texture coordinate. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord1fv.md b/Resources/Documentation/Gem/GEMglTexCoord1fv.md new file mode 100644 index 0000000000..92e73c3419 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord1fv.md @@ -0,0 +1,16 @@ + +--- +title: GEMglTexCoord1fv +description: set the current texture coordinates (floating-point version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s texture coordinate. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord1i.md b/Resources/Documentation/Gem/GEMglTexCoord1i.md new file mode 100644 index 0000000000..3d85344acb --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord1i.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTexCoord1i +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s texture coordinate. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s texture coordinate. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord1iv.md b/Resources/Documentation/Gem/GEMglTexCoord1iv.md new file mode 100644 index 0000000000..690427c656 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord1iv.md @@ -0,0 +1,16 @@ + +--- +title: GEMglTexCoord1iv +description: set the current texture coordinates (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s texture coordinate. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord1s.md b/Resources/Documentation/Gem/GEMglTexCoord1s.md new file mode 100644 index 0000000000..fa9ab09e2d --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord1s.md @@ -0,0 +1,22 @@ + +--- +title: GEMglTexCoord1s +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s texture coordinate. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s texture coordinate. +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/GEMglTexCoord1sv.md b/Resources/Documentation/Gem/GEMglTexCoord1sv.md new file mode 100644 index 0000000000..74787faf96 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord1sv.md @@ -0,0 +1,16 @@ + +--- +title: GEMglTexCoord1sv +description: set the current texture coordinates (short integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s and t texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord2d.md b/Resources/Documentation/Gem/GEMglTexCoord2d.md new file mode 100644 index 0000000000..54f1a85ce4 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord2d.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTexCoord2d +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s and t texture coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s and t texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord2dv.md b/Resources/Documentation/Gem/GEMglTexCoord2dv.md new file mode 100644 index 0000000000..1287c92bf7 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord2dv.md @@ -0,0 +1,16 @@ + +--- +title: GEMglTexCoord2dv +description: set the current texture coordinates (double-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s and t texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord2f.md b/Resources/Documentation/Gem/GEMglTexCoord2f.md new file mode 100644 index 0000000000..5ddb45b187 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord2f.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTexCoord2f +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s and t texture coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s and t texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord2fv.md b/Resources/Documentation/Gem/GEMglTexCoord2fv.md new file mode 100644 index 0000000000..78fa600497 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord2fv.md @@ -0,0 +1,16 @@ + +--- +title: GEMglTexCoord2fv +description: set the current texture coordinates (floating-point version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s and t texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord2i.md b/Resources/Documentation/Gem/GEMglTexCoord2i.md new file mode 100644 index 0000000000..2b1549fc27 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord2i.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTexCoord2i +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, and r texture coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s, t, and r texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord2iv.md b/Resources/Documentation/Gem/GEMglTexCoord2iv.md new file mode 100644 index 0000000000..0032f02b66 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord2iv.md @@ -0,0 +1,16 @@ + +--- +title: GEMglTexCoord2iv +description: set the current texture coordinates (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, and r texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord2s.md b/Resources/Documentation/Gem/GEMglTexCoord2s.md new file mode 100644 index 0000000000..e1214b988e --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord2s.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTexCoord2s +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, and r texture coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s, t, and r texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord2sv.md b/Resources/Documentation/Gem/GEMglTexCoord2sv.md new file mode 100644 index 0000000000..df5acb91c7 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord2sv.md @@ -0,0 +1,16 @@ + +--- +title: GEMglTexCoord2sv +description: set the current texture coordinates (short integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, and r texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord3d.md b/Resources/Documentation/Gem/GEMglTexCoord3d.md new file mode 100644 index 0000000000..1d949fe4ab --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord3d.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTexCoord3d +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, and r texture coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s, t, and r texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord3dv.md b/Resources/Documentation/Gem/GEMglTexCoord3dv.md new file mode 100644 index 0000000000..46f8092b81 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord3dv.md @@ -0,0 +1,16 @@ + +--- +title: GEMglTexCoord3dv +description: set the current texture coordinates (double-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, and r texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord3f.md b/Resources/Documentation/Gem/GEMglTexCoord3f.md new file mode 100644 index 0000000000..c6788600d8 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord3f.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTexCoord3f +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, and r texture coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s, t, and r texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord3fv.md b/Resources/Documentation/Gem/GEMglTexCoord3fv.md new file mode 100644 index 0000000000..ed8b9d947d --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord3fv.md @@ -0,0 +1,16 @@ + +--- +title: GEMglTexCoord3fv +description: set the current texture coordinates (floating-point version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, and r texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord3i.md b/Resources/Documentation/Gem/GEMglTexCoord3i.md new file mode 100644 index 0000000000..1e8fd429f7 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord3i.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTexCoord3i +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, and r texture coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s, t, and r texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord3iv.md b/Resources/Documentation/Gem/GEMglTexCoord3iv.md new file mode 100644 index 0000000000..00fa61e114 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord3iv.md @@ -0,0 +1,16 @@ + +--- +title: GEMglTexCoord3iv +description: set the current texture coordinates (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, and r texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord3s.md b/Resources/Documentation/Gem/GEMglTexCoord3s.md new file mode 100644 index 0000000000..55bbf40039 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord3s.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTexCoord3s +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, and r texture coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s, t, and r texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord3sv.md b/Resources/Documentation/Gem/GEMglTexCoord3sv.md new file mode 100644 index 0000000000..c2182363d8 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord3sv.md @@ -0,0 +1,16 @@ + +--- +title: GEMglTexCoord3sv +description: set the current texture coordinates (short integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, and r texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord4d.md b/Resources/Documentation/Gem/GEMglTexCoord4d.md new file mode 100644 index 0000000000..bc9e8dbee8 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord4d.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTexCoord4d +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, r, and q texture coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s, t, r, and q texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord4dv.md b/Resources/Documentation/Gem/GEMglTexCoord4dv.md new file mode 100644 index 0000000000..3d2db5c994 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord4dv.md @@ -0,0 +1,16 @@ + +--- +title: GEMglTexCoord4dv +description: set the current texture coordinates (double-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, r, and q texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord4f.md b/Resources/Documentation/Gem/GEMglTexCoord4f.md new file mode 100644 index 0000000000..0fead22a89 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord4f.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTexCoord4f +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, r, and q texture coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s, t, r, and q texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord4fv.md b/Resources/Documentation/Gem/GEMglTexCoord4fv.md new file mode 100644 index 0000000000..3cdcf87e29 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord4fv.md @@ -0,0 +1,16 @@ + +--- +title: GEMglTexCoord4fv +description: set the current texture coordinates (floating-point version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, r, and q texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord4i.md b/Resources/Documentation/Gem/GEMglTexCoord4i.md new file mode 100644 index 0000000000..db9f665f13 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord4i.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTexCoord4i +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, r, and q texture coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s, t, r, and q texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord4iv.md b/Resources/Documentation/Gem/GEMglTexCoord4iv.md new file mode 100644 index 0000000000..390eda3bfc --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord4iv.md @@ -0,0 +1,16 @@ + +--- +title: GEMglTexCoord4iv +description: set the current texture coordinates (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, r, and q texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord4s.md b/Resources/Documentation/Gem/GEMglTexCoord4s.md new file mode 100644 index 0000000000..4c884497e0 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord4s.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTexCoord4s +description: set the current texture coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, r, and q texture coordinates. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the s, t, r, and q texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexCoord4sv.md b/Resources/Documentation/Gem/GEMglTexCoord4sv.md new file mode 100644 index 0000000000..24df6b7311 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexCoord4sv.md @@ -0,0 +1,16 @@ + +--- +title: GEMglTexCoord4sv +description: set the current texture coordinates (short integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the s, t, r, and q texture coordinates. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexEnvf.md b/Resources/Documentation/Gem/GEMglTexEnvf.md new file mode 100644 index 0000000000..7784e80931 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexEnvf.md @@ -0,0 +1,33 @@ + +--- +title: GEMglTexEnvf +description: set texture environment parameters (floating-point version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the texture environment target. + - type: float + description: Specifies the texture environment parameter to set. + - type: float + description: Specifies the value to set the specified parameter. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the texture environment target. + 3rd: + - type: float + description: Specifies the texture environment parameter to set. + 4th: + - type: float + description: Specifies the value to set the specified parameter. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexEnvi.md b/Resources/Documentation/Gem/GEMglTexEnvi.md new file mode 100644 index 0000000000..02cf3fe31a --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexEnvi.md @@ -0,0 +1,33 @@ + +--- +title: GEMglTexEnvi +description: set texture environment parameters (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the texture environment target. + - type: float + description: Specifies the texture environment parameter to set. + - type: float + description: Specifies the value to set the specified parameter. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the texture environment target. + 3rd: + - type: float + description: Specifies the texture environment parameter to set. + 4th: + - type: float + description: Specifies the value to set the specified parameter. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexGend.md b/Resources/Documentation/Gem/GEMglTexGend.md new file mode 100644 index 0000000000..49ee524b28 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexGend.md @@ -0,0 +1,33 @@ + +--- +title: GEMglTexGend +description: set texture coordinate generation parameters (double-precision version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the texture coordinate function. + - type: float + description: Specifies the parameter to set for the texture coordinate function. + - type: float + description: Specifies the value to set the specified parameter. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the texture coordinate function. + 3rd: + - type: float + description: Specifies the parameter to set for the texture coordinate function. + 4th: + - type: float + description: Specifies the value to set the specified parameter. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexGenf.md b/Resources/Documentation/Gem/GEMglTexGenf.md new file mode 100644 index 0000000000..f402f4f9ac --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexGenf.md @@ -0,0 +1,33 @@ + +--- +title: GEMglTexGenf +description: set texture coordinate generation parameters (floating-point version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the texture coordinate function. + - type: float + description: Specifies the parameter to set for the texture coordinate function. + - type: float + description: Specifies the value to set the specified parameter. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the texture coordinate function. + 3rd: + - type: float + description: Specifies the parameter to set for the texture coordinate function. + 4th: + - type: float + description: Specifies the value to set the specified parameter. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexGenfv.md b/Resources/Documentation/Gem/GEMglTexGenfv.md new file mode 100644 index 0000000000..452238a0ef --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexGenfv.md @@ -0,0 +1,33 @@ + +--- +title: GEMglTexGenfv +description: set texture coordinate generation parameters (floating-point version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the texture coordinate function. + - type: float + description: Specifies the parameter to set for the texture coordinate function. + - type: float + description: Specifies the value to set the specified parameter. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the texture coordinate function. + 3rd: + - type: float + description: Specifies the parameter to set for the texture coordinate function. + 4th: + - type: float + description: Specifies the value to set the specified parameter. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexGeni.md b/Resources/Documentation/Gem/GEMglTexGeni.md new file mode 100644 index 0000000000..f2374f1215 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexGeni.md @@ -0,0 +1,33 @@ + +--- +title: GEMglTexGeni +description: set texture coordinate generation parameters (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the texture coordinate function. + - type: float + description: Specifies the parameter to set for the texture coordinate function. + - type: float + description: Specifies the value to set the specified parameter. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the texture coordinate function. + 3rd: + - type: float + description: Specifies the parameter to set for the texture coordinate function. + 4th: + - type: float + description: Specifies the value to set the specified parameter. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexImage2D.md b/Resources/Documentation/Gem/GEMglTexImage2D.md new file mode 100644 index 0000000000..7012305df3 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexImage2D.md @@ -0,0 +1,63 @@ + +--- +title: GEMglTexImage2D +description: specify a two-dimensional texture image +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target texture. + - type: float + description: Specifies the level-of-detail number. Level 0 is the base image level. + - type: float + description: Specifies the internal format of the texture. + - type: float + description: Specifies the width of the texture image. + - type: float + description: Specifies the height of the texture image. + - type: float + description: Specifies the border of the texture. + - type: float + description: Specifies the format of the pixel data. + - type: float + description: Specifies the data type of the pixel data. + - type: const GLvoid* + description: Specifies a pointer to the image data in memory. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target texture. + 3rd: + - type: float + description: Specifies the level-of-detail number. Level 0 is the base image level. + 4th: + - type: float + description: Specifies the internal format of the texture. + 5th: + - type: float + description: Specifies the width of the texture image. + 6th: + - type: float + description: Specifies the height of the texture image. + 7th: + - type: float + description: Specifies the border of the texture. + 8th: + - type: float + description: Specifies the format of the pixel data. + 9th: + - type: float + description: Specifies the data type of the pixel data. + 10th: + - type: const GLvoid* + description: Specifies a pointer to the image data in memory. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexParameterf.md b/Resources/Documentation/Gem/GEMglTexParameterf.md new file mode 100644 index 0000000000..e0382cd5d0 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexParameterf.md @@ -0,0 +1,33 @@ + +--- +title: GEMglTexParameterf +description: set texture parameters (floating-point version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target texture. + - type: float + description: Specifies the parameter to set for the texture. + - type: float + description: Specifies the value to set the specified parameter. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target texture. + 3rd: + - type: float + description: Specifies the parameter to set for the texture. + 4th: + - type: float + description: Specifies the value to set the specified parameter. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexParameteri.md b/Resources/Documentation/Gem/GEMglTexParameteri.md new file mode 100644 index 0000000000..27ec662490 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexParameteri.md @@ -0,0 +1,33 @@ + +--- +title: GEMglTexParameteri +description: set texture parameters (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target texture. + - type: float + description: Specifies the parameter to set for the texture. + - type: float + description: Specifies the value to set the specified parameter. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target texture. + 3rd: + - type: float + description: Specifies the parameter to set for the texture. + 4th: + - type: float + description: Specifies the value to set the specified parameter. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexSubImage1D.md b/Resources/Documentation/Gem/GEMglTexSubImage1D.md new file mode 100644 index 0000000000..7ad35b3563 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexSubImage1D.md @@ -0,0 +1,53 @@ + +--- +title: GEMglTexSubImage1D +description: define a subregion of a one-dimensional texture image +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target texture. + - type: float + description: Specifies the level-of-detail number. + - type: float + description: Specifies the xoffset of the subregion. + - type: float + description: Specifies the width of the subregion. + - type: float + description: Specifies the format of the pixel data. + - type: float + description: Specifies the data type of the pixel data. + - type: const GLvoid* + description: Specifies a pointer to the image data in memory. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target texture. + 3rd: + - type: float + description: Specifies the level-of-detail number. + 4th: + - type: float + description: Specifies the xoffset of the subregion. + 5th: + - type: float + description: Specifies the width of the subregion. + 6th: + - type: float + description: Specifies the format of the pixel data. + 7th: + - type: float + description: Specifies the data type of the pixel data. + 8th: + - type: const GLvoid* + description: Specifies a pointer to the image data in memory. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTexSubImage2D.md b/Resources/Documentation/Gem/GEMglTexSubImage2D.md new file mode 100644 index 0000000000..26bae28b87 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTexSubImage2D.md @@ -0,0 +1,63 @@ + +--- +title: GEMglTexSubImage2D +description: define a subregion of a two-dimensional texture image +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the target texture. + - type: float + description: Specifies the level-of-detail number. + - type: float + description: Specifies the xoffset of the subregion. + - type: float + description: Specifies the yoffset of the subregion. + - type: float + description: Specifies the width of the subregion. + - type: float + description: Specifies the height of the subregion. + - type: float + description: Specifies the format of the pixel data. + - type: float + description: Specifies the data type of the pixel data. + - type: const GLvoid* + description: Specifies a pointer to the image data in memory. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the target texture. + 3rd: + - type: float + description: Specifies the level-of-detail number. + 4th: + - type: float + description: Specifies the xoffset of the subregion. + 5th: + - type: float + description: Specifies the yoffset of the subregion. + 6th: + - type: float + description: Specifies the width of the subregion. + 7th: + - type: float + description: Specifies the height of the subregion. + 8th: + - type: float + description: Specifies the format of the pixel data. + 9th: + - type: float + description: Specifies the data type of the pixel data. + 10th: + - type: const GLvoid* + description: Specifies a pointer to the image data in memory. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTranslated.md b/Resources/Documentation/Gem/GEMglTranslated.md new file mode 100644 index 0000000000..400e3d6df6 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTranslated.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTranslated +description: multiply the current matrix by a translation matrix +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z offsets for the translation. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z offsets for the translation. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglTranslatef.md b/Resources/Documentation/Gem/GEMglTranslatef.md new file mode 100644 index 0000000000..ea54b9d292 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglTranslatef.md @@ -0,0 +1,23 @@ + +--- +title: GEMglTranslatef +description: multiply the current matrix by a translation matrix +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z offsets for the translation. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z offsets for the translation. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglUniform1f.md b/Resources/Documentation/Gem/GEMglUniform1f.md new file mode 100644 index 0000000000..65c36dbca7 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglUniform1f.md @@ -0,0 +1,28 @@ + +--- +title: GEMglUniform1f +description: load a floating-point uniform variable to a program object +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the location of the uniform variable. + - type: float + description: Specifies the new values to be used for the specified uniform variable. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the location of the uniform variable. + 3rd: + - type: float + description: Specifies the new values to be used for the specified uniform variable. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglUniform1fARB.md b/Resources/Documentation/Gem/GEMglUniform1fARB.md new file mode 100644 index 0000000000..50b5e38633 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglUniform1fARB.md @@ -0,0 +1,28 @@ + +--- +title: GEMglUniform1fARB +description: load a floating-point uniform variable to a program object (ARB version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the location of the uniform variable. + - type: float + description: Specifies the new values to be used for the specified uniform variable. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the location of the uniform variable. + 3rd: + - type: float + description: Specifies the new values to be used for the specified uniform variable. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglUseProgramObjectARB.md b/Resources/Documentation/Gem/GEMglUseProgramObjectARB.md new file mode 100644 index 0000000000..64a968f995 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglUseProgramObjectARB.md @@ -0,0 +1,23 @@ + +--- +title: GEMglUseProgramObjectARB +description: install a program object as part of current rendering state (ARB version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the handle of the program object whose executables are to be used as part of rendering state. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the handle of the program object whose executables are to be used as part of rendering state. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex2d.md b/Resources/Documentation/Gem/GEMglVertex2d.md new file mode 100644 index 0000000000..82f9004f3d --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex2d.md @@ -0,0 +1,30 @@ + +--- +title: GEMglVertex2d +description: specify a two-dimensional vertex with double-precision coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: double + description: Specifies the x-coordinate of the vertex. + default: 0 + - type: double + description: Specifies the y-coordinate of the vertex. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: double + description: Specifies the x-coordinate of the vertex. + 3rd: + - type: double + description: Specifies the y-coordinate of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex2dv.md b/Resources/Documentation/Gem/GEMglVertex2dv.md new file mode 100644 index 0000000000..e50d60f8f8 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex2dv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglVertex2dv +description: specify a two-dimensional vertex with double-precision coordinates using an array +categories: + - object +pdcategory: Graphics +arguments: + - type: double + description: Specifies an array containing the x and y coordinates of the vertex. + default: [0, 0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: double + description: Specifies an array containing the x and y coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex2f.md b/Resources/Documentation/Gem/GEMglVertex2f.md new file mode 100644 index 0000000000..4c4d6585e7 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex2f.md @@ -0,0 +1,30 @@ + +--- +title: GEMglVertex2f +description: specify a two-dimensional vertex with single-precision coordinates +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x-coordinate of the vertex. + default: 0 + - type: float + description: Specifies the y-coordinate of the vertex. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x-coordinate of the vertex. + 3rd: + - type: float + description: Specifies the y-coordinate of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex2fv.md b/Resources/Documentation/Gem/GEMglVertex2fv.md new file mode 100644 index 0000000000..18826a3003 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex2fv.md @@ -0,0 +1,24 @@ + +--- +title: GEMglVertex2fv +description: specify a two-dimensional vertex with single-precision coordinates using an array +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies an array containing the x and y coordinates of the vertex. + default: [0, 0] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies an array containing the x and y coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex2i.md b/Resources/Documentation/Gem/GEMglVertex2i.md new file mode 100644 index 0000000000..6d7409b19a --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex2i.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex2i +description: specify a two-dimensional vertex +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x and y coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x and y coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex2iv.md b/Resources/Documentation/Gem/GEMglVertex2iv.md new file mode 100644 index 0000000000..b8b145b1b8 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex2iv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex2iv +description: specify a two-dimensional vertex (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: const float* + description: Specifies a pointer to an array of two elements, which are the x and y coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: const float* + description: Specifies a pointer to an array of two elements, which are the x and y coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex2s.md b/Resources/Documentation/Gem/GEMglVertex2s.md new file mode 100644 index 0000000000..52a906c1ea --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex2s.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex2s +description: specify a two-dimensional vertex (short version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x and y coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x and y coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex2sv.md b/Resources/Documentation/Gem/GEMglVertex2sv.md new file mode 100644 index 0000000000..0fcb3a20f0 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex2sv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex2sv +description: specify a two-dimensional vertex (short version) +categories: + - object +pdcategory: Graphics +arguments: + - type: const float* + description: Specifies a pointer to an array of two elements, which are the x and y coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: const float* + description: Specifies a pointer to an array of two elements, which are the x and y coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex3d.md b/Resources/Documentation/Gem/GEMglVertex3d.md new file mode 100644 index 0000000000..b58fdab274 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex3d.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex3d +description: specify a three-dimensional vertex (double version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex3dv.md b/Resources/Documentation/Gem/GEMglVertex3dv.md new file mode 100644 index 0000000000..11d907c151 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex3dv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex3dv +description: specify a three-dimensional vertex (double version) +categories: + - object +pdcategory: Graphics +arguments: + - type: const float* + description: Specifies a pointer to an array of three elements, which are the x, y, and z coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: const float* + description: Specifies a pointer to an array of three elements, which are the x, y, and z coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex3f.md b/Resources/Documentation/Gem/GEMglVertex3f.md new file mode 100644 index 0000000000..4555e0ee5a --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex3f.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex3f +description: specify a three-dimensional vertex (float version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex3fv.md b/Resources/Documentation/Gem/GEMglVertex3fv.md new file mode 100644 index 0000000000..f3e41c31b3 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex3fv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex3fv +description: specify a three-dimensional vertex (float version) +categories: + - object +pdcategory: Graphics +arguments: + - type: const float* + description: Specifies a pointer to an array of three elements, which are the x, y, and z coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: const float* + description: Specifies a pointer to an array of three elements, which are the x, y, and z coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex3i.md b/Resources/Documentation/Gem/GEMglVertex3i.md new file mode 100644 index 0000000000..4862da9a28 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex3i.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex3i +description: specify a three-dimensional vertex (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex3iv.md b/Resources/Documentation/Gem/GEMglVertex3iv.md new file mode 100644 index 0000000000..b08d3070e1 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex3iv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex3iv +description: specify a three-dimensional vertex (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: const float* + description: Specifies a pointer to an array of three elements, which are the x, y, and z coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: const float* + description: Specifies a pointer to an array of three elements, which are the x, y, and z coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex3s.md b/Resources/Documentation/Gem/GEMglVertex3s.md new file mode 100644 index 0000000000..956827a497 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex3s.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex3s +description: specify a three-dimensional vertex (short version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, and z coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, and z coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex3sv.md b/Resources/Documentation/Gem/GEMglVertex3sv.md new file mode 100644 index 0000000000..65bae56b49 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex3sv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex3sv +description: specify a three-dimensional vertex (short version) +categories: + - object +pdcategory: Graphics +arguments: + - type: const float* + description: Specifies a pointer to an array of three elements, which are the x, y, and z coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: const float* + description: Specifies a pointer to an array of three elements, which are the x, y, and z coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex4d.md b/Resources/Documentation/Gem/GEMglVertex4d.md new file mode 100644 index 0000000000..1b014a3c90 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex4d.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex4d +description: specify a four-dimensional vertex (double version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, z, and w coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, z, and w coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex4dv.md b/Resources/Documentation/Gem/GEMglVertex4dv.md new file mode 100644 index 0000000000..67e14b8947 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex4dv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex4dv +description: specify a four-dimensional vertex (double version) +categories: + - object +pdcategory: Graphics +arguments: + - type: const float* + description: Specifies a pointer to an array of four elements, which are the x, y, z, and w coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: const float* + description: Specifies a pointer to an array of four elements, which are the x, y, z, and w coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex4f.md b/Resources/Documentation/Gem/GEMglVertex4f.md new file mode 100644 index 0000000000..b06410ed1c --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex4f.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex4f +description: specify a four-dimensional vertex (float version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, z, and w coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, z, and w coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex4fv.md b/Resources/Documentation/Gem/GEMglVertex4fv.md new file mode 100644 index 0000000000..5503b3e209 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex4fv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex4fv +description: specify a four-dimensional vertex (float version) +categories: + - object +pdcategory: Graphics +arguments: + - type: const float* + description: Specifies a pointer to an array of four elements, which are the x, y, z, and w coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: const float* + description: Specifies a pointer to an array of four elements, which are the x, y, z, and w coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex4i.md b/Resources/Documentation/Gem/GEMglVertex4i.md new file mode 100644 index 0000000000..a7a9591a9a --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex4i.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex4i +description: specify a four-dimensional vertex (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, z, and w coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, z, and w coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex4iv.md b/Resources/Documentation/Gem/GEMglVertex4iv.md new file mode 100644 index 0000000000..8d91233805 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex4iv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex4iv +description: specify a four-dimensional vertex (integer version) +categories: + - object +pdcategory: Graphics +arguments: + - type: const float* + description: Specifies a pointer to an array of four elements, which are the x, y, z, and w coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: const float* + description: Specifies a pointer to an array of four elements, which are the x, y, z, and w coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex4s.md b/Resources/Documentation/Gem/GEMglVertex4s.md new file mode 100644 index 0000000000..b2ec97c7d2 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex4s.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex4s +description: specify a four-dimensional vertex (short version) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the x, y, z, and w coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the x, y, z, and w coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglVertex4sv.md b/Resources/Documentation/Gem/GEMglVertex4sv.md new file mode 100644 index 0000000000..337d4ad20a --- /dev/null +++ b/Resources/Documentation/Gem/GEMglVertex4sv.md @@ -0,0 +1,23 @@ + +--- +title: GEMglVertex4sv +description: specify a four-dimensional vertex (short version) +categories: + - object +pdcategory: Graphics +arguments: + - type: const float* + description: Specifies a pointer to an array of four elements, which are the x, y, z, and w coordinates of the vertex. +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: const float* + description: Specifies a pointer to an array of four elements, which are the x, y, z, and w coordinates of the vertex. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMglViewport.md b/Resources/Documentation/Gem/GEMglViewport.md new file mode 100644 index 0000000000..9481b86c24 --- /dev/null +++ b/Resources/Documentation/Gem/GEMglViewport.md @@ -0,0 +1,30 @@ + +--- +title: GEMglViewport +description: set the viewport +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the lower left corner of the viewport rectangle, in pixels. The initial value is (0,0). + default: 0 + - type: float + description: Specifies the width and height of the viewport. Values for width and height must be greater than 0. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the lower left corner of the viewport rectangle, in pixels. The initial value is (0,0). + 3rd: + - type: float + description: Specifies the width and height of the viewport. Values for width and height must be greater than 0. +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/GEMgluLookAt.md b/Resources/Documentation/Gem/GEMgluLookAt.md new file mode 100644 index 0000000000..6bd24a7517 --- /dev/null +++ b/Resources/Documentation/Gem/GEMgluLookAt.md @@ -0,0 +1,43 @@ + +--- +title: GEMgluLookAt +description: define a viewing transformation +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the position of the eye point. + default: 0 + - type: float + description: Specifies the position of the reference point. + default: 0 + - type: float + description: Specifies the direction of the up vector. + default: 1 + - type: float + description: Specifies the direction of the eye-to-object vector. + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the position of the eye point. + 3rd: + - type: float + description: Specifies the position of the reference point. + 4th: + - type: float + description: Specifies the direction of the up vector. + 5th: + - type: float + description: Specifies the direction of the eye-to-object vector. +outlets: + 1st: + - type: gemlist +draft: false +--- + + diff --git a/Resources/Documentation/Gem/GEMgluPerspective.md b/Resources/Documentation/Gem/GEMgluPerspective.md new file mode 100644 index 0000000000..a439570536 --- /dev/null +++ b/Resources/Documentation/Gem/GEMgluPerspective.md @@ -0,0 +1,42 @@ + +--- +title: GEMgluPerspective +description: set up a perspective projection matrix +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: Specifies the field of view angle, in degrees, in the y direction. + default: 0 + - type: float + description: Specifies the aspect ratio that determines the field of view in the x direction. The aspect ratio is the ratio of x (width) to y + default: 0 + - type: float + description: Specifies the distance from the viewer to the near clipping plane (always positive). + default: 0 + - type: float + description: Specifies the distance from the viewer to the far clipping plane (always positive). + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: Specifies the field of view angle, in degrees, in the y direction. + 3rd: + - type: float + description: Specifies the aspect ratio that determines the field of view in the x direction. The aspect ratio is the ratio of x (width) to y + 4th: + - type: float + description: Specifies the distance from the viewer to the near clipping plane (always positive). + 5th: + - type: float + description: Specifies the distance from the viewer to the far clipping plane (always positive). +outlets: + 1st: + - type: gemlist +draft: false + + diff --git a/Resources/Documentation/Gem/GLdefine.md b/Resources/Documentation/Gem/GLdefine.md new file mode 100644 index 0000000000..b8a26b618c --- /dev/null +++ b/Resources/Documentation/Gem/GLdefine.md @@ -0,0 +1,19 @@ +--- +title: GLdefine +description: Gets the value of an OpenGL constant. +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: bang + description: Trigger to retrieve the OpenGL constant value + 2nd: + - type: message + description: Name of the OpenGL constant (e.g., GL_LINES or GL_POLYGON) +outlets: + 1st: + - type: float +draft: false +--- + diff --git a/Resources/Documentation/Gem/accumrotate.md b/Resources/Documentation/Gem/accumrotate.md new file mode 100644 index 0000000000..802b47fc4a --- /dev/null +++ b/Resources/Documentation/Gem/accumrotate.md @@ -0,0 +1,36 @@ +--- +title: accumrotate +description: Accumulated rotation. +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: initial rotations around X, Y, Z-axes +methods: + - type: reset + description: + - type: float + description: delta-rotation around X-axis (in deg) + - type: float + description: delta-rotation around Y-axis (in deg) + - type: float + description: delta-rotation around Z-axis (in deg) +inlets: + 1st: + - type: float + description: initial rotations around X, Y, Z-axes + 2nd: + - type: float + description: delta-rotation around X-axis (in deg) + 3rd: + - type: float + description: delta-rotation around Y-axis (in deg) + 4th: + - type: float + description: delta-rotation around Z-axis (in deg) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/alpha.md b/Resources/Documentation/Gem/alpha.md new file mode 100644 index 0000000000..edce745018 --- /dev/null +++ b/Resources/Documentation/Gem/alpha.md @@ -0,0 +1,28 @@ +--- +title: alpha +description: enable alpha blending +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: turn alpha blending on/off + default: 1 + - type: float + description: blending function + default: GL_ONE_MINUS_SRC_ALPHA +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: blending function +outlets: + 1st: + - type: gemlist +methods: + - type: auto + description: turn on/off automatic depth detection +draft: false +--- diff --git a/Resources/Documentation/Gem/ambient.md b/Resources/Documentation/Gem/ambient.md new file mode 100644 index 0000000000..07d93415c5 --- /dev/null +++ b/Resources/Documentation/Gem/ambient.md @@ -0,0 +1,22 @@ +--- +title: ambient +description: ambient coloring +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: a list of 3 (RGB) or 4 (RGBA) float-values + default: 0.2, 0.2, 0.2, 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: 3(RGB) or 4(RGBA) float values +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/ambientRGB.md b/Resources/Documentation/Gem/ambientRGB.md new file mode 100644 index 0000000000..f03bc58aca --- /dev/null +++ b/Resources/Documentation/Gem/ambientRGB.md @@ -0,0 +1,40 @@ +--- +title: ambientRGB +description: ambient coloring with RGB values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: red value + default: 0 + - type: float + description: green value + default: 0 + - type: float + description: blue value + default: 0 + - type: float + description: alpha value + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: red value + 3rd: + - type: float + description: green value + 4th: + - type: float + description: blue value + 5th: + - type: float + description: alpha value +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/circle.md b/Resources/Documentation/Gem/circle.md new file mode 100644 index 0000000000..34b3da54f8 --- /dev/null +++ b/Resources/Documentation/Gem/circle.md @@ -0,0 +1,22 @@ +--- +title: circle +description: renders a circle +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: size of the circle + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: size of the circle +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/color.md b/Resources/Documentation/Gem/color.md new file mode 100644 index 0000000000..c36e85fd5c --- /dev/null +++ b/Resources/Documentation/Gem/color.md @@ -0,0 +1,22 @@ +--- +title: color +description: sets the color for subsequent shape and vertex operations +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: 3 (RGB) or 4 (RGBA) float-values + default: 0, 0, 0, 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: 3 (RGB) or 4 (RGBA) float values +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/colorRGB.md b/Resources/Documentation/Gem/colorRGB.md new file mode 100644 index 0000000000..0578b43814 --- /dev/null +++ b/Resources/Documentation/Gem/colorRGB.md @@ -0,0 +1,37 @@ +--- +title: colorRGB +description: sets the color for subsequent shape and vertex operations using RGB values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: red value + - type: float + description: green value + - type: float + description: blue value + - type: float + description: alpha value + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: red value + 3rd: + - type: float + description: green value + 4th: + - type: float + description: blue value + 5th: + - type: float + description: alpha value +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/colorSquare.md b/Resources/Documentation/Gem/colorSquare.md new file mode 100644 index 0000000000..95d50fb7bd --- /dev/null +++ b/Resources/Documentation/Gem/colorSquare.md @@ -0,0 +1,33 @@ +--- +title: colorSquare +description: renders a square with several colors +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: size of the square +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: size of the square + 3rd: + - type: list + description: 3(RGB) float values for the lower-left corner + 4th: + - type: list + description: 3(RGB) float values for the lower-right corner + 5th: + - type: list + description: 3(RGB) float values for the upper-right corner + 6th: + - type: list + description: 3(RGB) float values for the upper-left corner +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/cone.md b/Resources/Documentation/Gem/cone.md new file mode 100644 index 0000000000..e823eaff7f --- /dev/null +++ b/Resources/Documentation/Gem/cone.md @@ -0,0 +1,26 @@ +--- +title: cone +description: renders a cone +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: size of the cone + - type: float + description: number of segments +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: size of the cone + 3rd: + - type: float + description: number of segments +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/cube.md b/Resources/Documentation/Gem/cube.md new file mode 100644 index 0000000000..fbd0560640 --- /dev/null +++ b/Resources/Documentation/Gem/cube.md @@ -0,0 +1,21 @@ +--- +title: cube +description: renders a cube +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: size of the cube +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: size of the cube +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/cuboid.md b/Resources/Documentation/Gem/cuboid.md new file mode 100644 index 0000000000..f4c9366d3f --- /dev/null +++ b/Resources/Documentation/Gem/cuboid.md @@ -0,0 +1,31 @@ +--- +title: cuboid +description: renders a cuboid box +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: length (dimX) + - type: float + description: height (dimY) + - type: float + description: depth (dimZ) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: length (dimX) + 3rd: + - type: float + description: height (dimY) + 4th: + - type: float + description: depth (dimZ) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/curve.md b/Resources/Documentation/Gem/curve.md new file mode 100644 index 0000000000..e5c73027e6 --- /dev/null +++ b/Resources/Documentation/Gem/curve.md @@ -0,0 +1,21 @@ +--- +title: curve +description: renders a bezier-curve +categories: + - object +pdcategory: Graphics +arguments: + - type: + description: number of control-points of the curve (mandatory) +inlets: + 1st: + - type: gemlist + description: + nth: + - type: list + description: 3(XYZ) float values (control point) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/curve3d.md b/Resources/Documentation/Gem/curve3d.md new file mode 100644 index 0000000000..17daf89a40 --- /dev/null +++ b/Resources/Documentation/Gem/curve3d.md @@ -0,0 +1,26 @@ +--- +title: curve3d +description: renders a 3D bezier curve +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: not used +outlets: + 1st: + - type: gemlist +methods: + - type: draw + description: line, fill or point + - type: grid + description: set grid size (X, Y are 2 int) + - type: res + description: set resolution (X, Y are 2 int) + - type: set + description: set control point position (Mx,My = position in the matrix; X,Y,Z = position of the control point) +draft: false +--- diff --git a/Resources/Documentation/Gem/cylinder.md b/Resources/Documentation/Gem/cylinder.md new file mode 100644 index 0000000000..5279b72706 --- /dev/null +++ b/Resources/Documentation/Gem/cylinder.md @@ -0,0 +1,26 @@ +--- +title: cylinder +description: renders a cylinder +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: size + - type: float + description: number of segments +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: size + 3rd: + - type: float + description: number of segments +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/depth.md b/Resources/Documentation/Gem/depth.md new file mode 100644 index 0000000000..2275d7af2e --- /dev/null +++ b/Resources/Documentation/Gem/depth.md @@ -0,0 +1,20 @@ +--- +title: depth +description: Activate / Deactivate depth test. +categories: + - object +pdcategory: Graphics +arguments: null +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: depth test on/off (0/1) +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/diffuse.md b/Resources/Documentation/Gem/diffuse.md new file mode 100644 index 0000000000..effaab9ba4 --- /dev/null +++ b/Resources/Documentation/Gem/diffuse.md @@ -0,0 +1,22 @@ +--- +title: diffuse +description: diffuse coloring +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: a list of 3 (RGB) or 4 (RGBA) float-values + default: 0.8, 0.8, 0.8, 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: RGB or RGBA float values +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/diffuseRGB.md b/Resources/Documentation/Gem/diffuseRGB.md new file mode 100644 index 0000000000..513b8821df --- /dev/null +++ b/Resources/Documentation/Gem/diffuseRGB.md @@ -0,0 +1,40 @@ +--- +title: diffuseRGB +description: diffuse coloring with RGB values +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: red value + default: 0 + - type: float + description: green value + default: 0 + - type: float + description: blue value + default: 0 + - type: float + description: alpha value + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: red value + 3rd: + - type: float + description: green value + 4th: + - type: float + description: blue value + 5th: + - type: float + description: alpha value +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/disk.md b/Resources/Documentation/Gem/disk.md new file mode 100644 index 0000000000..f7b50ee35f --- /dev/null +++ b/Resources/Documentation/Gem/disk.md @@ -0,0 +1,34 @@ +--- +title: disk +description: renders a disk +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: size (= outer radius) + default: 1 + - type: float + description: number of segments + default: 10 + - type: float + description: inner radius + default: 0 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: size (= outer radius) + 3rd: + - type: float + description: number of segments + 4th: + - type: float + description: inner radius +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/emission.md b/Resources/Documentation/Gem/emission.md new file mode 100644 index 0000000000..fd882d2a4e --- /dev/null +++ b/Resources/Documentation/Gem/emission.md @@ -0,0 +1,22 @@ +--- +title: emission +description: emission coloring +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: a list of 3 (RGB) or 4 (RGBA) float-values + default: [0, 0, 0, 1] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: RGB or RGBA float values. +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/emissionRGB.md b/Resources/Documentation/Gem/emissionRGB.md new file mode 100644 index 0000000000..198d86cd5d --- /dev/null +++ b/Resources/Documentation/Gem/emissionRGB.md @@ -0,0 +1,31 @@ +--- +title: emissionRGB +description: emission coloring with individual RGB values. +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: a list of 3 (RGB) or 4 (RGBA) float-values. + default: [0, 0, 0, 1] +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: red value + 3rd: + - type: float + description: green value + 4th: + - type: float + description: blue value + 5th: + - type: float + description: alpha value +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/fragment_program.md b/Resources/Documentation/Gem/fragment_program.md new file mode 100644 index 0000000000..6211aba38d --- /dev/null +++ b/Resources/Documentation/Gem/fragment_program.md @@ -0,0 +1,18 @@ +--- +title: fragment_program +description: load and apply an ARB fragment shader +categories: + - object +pdcategory: Graphics +methods: + - type: open + description: fragment shader program to load +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/gemargs.md b/Resources/Documentation/Gem/gemargs.md new file mode 100644 index 0000000000..9c0935ac22 --- /dev/null +++ b/Resources/Documentation/Gem/gemargs.md @@ -0,0 +1,19 @@ +--- +title: gemargs +description: pass the initial message and arguments for the Gem subsystem +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: bang + description: send a bang to initiate Gem subsystem +outlets: + 1st: + - type: list + label: initialization Messages + 2nd: + - type: list + label: arguments +draft: false +--- diff --git a/Resources/Documentation/Gem/gemcubeframebuffer.md b/Resources/Documentation/Gem/gemcubeframebuffer.md new file mode 100644 index 0000000000..db6b0c3615 --- /dev/null +++ b/Resources/Documentation/Gem/gemcubeframebuffer.md @@ -0,0 +1,26 @@ +--- +title: gemcubeframebuffer +description: renders a scene to faces of a GL cubemap texture +categories: + - object +pdcategory: Graphics +methods: + - type: dimen + description: set the dimensions of the framebuffer + - type: format + description: set the color format of the framebuffer + - type: color + description: set the background color of the framebuffer +inlets: + 1st: + - type: gemstate + description: +outlets: + 1st: + - type: gemstate + description: + 2nd: + - type: gemstate + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/gemframebuffer.md b/Resources/Documentation/Gem/gemframebuffer.md new file mode 100644 index 0000000000..1579131067 --- /dev/null +++ b/Resources/Documentation/Gem/gemframebuffer.md @@ -0,0 +1,32 @@ +--- +title: gemframebuffer +description: renders a scene in a texture for later use +categories: + - object +pdcategory: Graphics +methods: + - type: type + description: type of the framebuffer data (BYTE, INT or FLOAT]) + - type: dimen + description: dimension of the framebuffer texture + - type: color + description: background color of the framebuffer + - type: texunit + description: change texunit of the texture + - type: rectangle + description: texturing mode; rectangle (1) or normalized (0) + - type: perspec + description: frustum of the framebuffer + - type: format + description: color format of the framebuffer (RGB|RGBA|RGB32|RGBA32F|YUV) +inlets: + 1st: + - type: gemlist +outlets: + 1st: + - type: gemlist + 2nd: + - type: list + description: texture info +draft: false +--- diff --git a/Resources/Documentation/Gem/gemhead.md b/Resources/Documentation/Gem/gemhead.md new file mode 100644 index 0000000000..6df96c8241 --- /dev/null +++ b/Resources/Documentation/Gem/gemhead.md @@ -0,0 +1,27 @@ +--- +title: gemhead +description: start a rendering chain +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: priority + default: 50 +methods: + - type: set + description: change priority value of this chain + - type: context + description: change rendering context (for multiple windows) + +inlets: + 1st: + - type: float (1/0) + description: turn rendering on/off + - type: bang + description: force rendering now +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/gemkeyboard.md b/Resources/Documentation/Gem/gemkeyboard.md new file mode 100644 index 0000000000..ffb9f547d8 --- /dev/null +++ b/Resources/Documentation/Gem/gemkeyboard.md @@ -0,0 +1,14 @@ +--- +title: gemkeyboard +description: output keyboard events in the GEM window +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: non-used +outlets: + 1st: + - type: keyCode +draft: false +--- diff --git a/Resources/Documentation/Gem/gemkeyname.md b/Resources/Documentation/Gem/gemkeyname.md new file mode 100644 index 0000000000..a355075df9 --- /dev/null +++ b/Resources/Documentation/Gem/gemkeyname.md @@ -0,0 +1,19 @@ +--- +title: gemkeyname +description: output keyboard events in the GEM window +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: non-used +outlets: + 1st: + - type: float + description: key state + 2nd: + - type: symbol + description: key name + +draft: false +--- diff --git a/Resources/Documentation/Gem/gemlist.md b/Resources/Documentation/Gem/gemlist.md new file mode 100644 index 0000000000..f4bd1695c3 --- /dev/null +++ b/Resources/Documentation/Gem/gemlist.md @@ -0,0 +1,22 @@ +--- +title: gemlist +description: Store a gemlist. +categories: + - object +pdcategory: Graphics + +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: bang + description: + 3rd: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/gemlist_info.md b/Resources/Documentation/Gem/gemlist_info.md new file mode 100644 index 0000000000..552fec4c8f --- /dev/null +++ b/Resources/Documentation/Gem/gemlist_info.md @@ -0,0 +1,28 @@ +--- +title: gemlist_info +description: get current transformation of a gemlist +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + 2nd: + - type: list + description: RotationX, Y, and Z + 3rd: + - type: list + description: Shear YX, YZ, and ZX + 4th: + - type: list + description: Scale X, Y, and Z + 5th: + - type: list + description: TranslationX, Y, and Z + +draft: false +--- diff --git a/Resources/Documentation/Gem/gemlist_matrix.md b/Resources/Documentation/Gem/gemlist_matrix.md new file mode 100644 index 0000000000..7324f49a40 --- /dev/null +++ b/Resources/Documentation/Gem/gemlist_matrix.md @@ -0,0 +1,18 @@ +--- +title: gemlist_matrix +description: get the current transformation matrix of a gemlist +categories: + - object +pdcategory: Graphics + +inlets: + 1st: + - type: gemlist +outlets: + 1st: + - type: gemlist + 2nd: + - type: list + description: Transformation matrix (16 floats) +draft: false +--- diff --git a/Resources/Documentation/Gem/gemmanager.md b/Resources/Documentation/Gem/gemmanager.md new file mode 100644 index 0000000000..3ca7ac53c2 --- /dev/null +++ b/Resources/Documentation/Gem/gemmanager.md @@ -0,0 +1,11 @@ +--- +title: gemmanager +description: interact with the global gemstate +categories: + - object +pdcategory: Graphics +methods: + - type: dimen + description: set global window dimensions +draft: false +--- \ No newline at end of file diff --git a/Resources/Documentation/Gem/gemmouse.md b/Resources/Documentation/Gem/gemmouse.md new file mode 100644 index 0000000000..492487b8f6 --- /dev/null +++ b/Resources/Documentation/Gem/gemmouse.md @@ -0,0 +1,32 @@ +--- +title: gemmouse +description: output mouse events in the GEM window +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: x-normalization + - type: float + description: y-normalization +inlets: + 1st: + - type: non-used +outlets: + 1st: + - type: float + description: x position + 2nd: + - type: float + description: y position + 3rd: + - type: float + description: left button state + 4th: + - type: float + description: middle button state + 5th: + - type: float + description: right button state +draft: false +--- diff --git a/Resources/Documentation/Gem/gemreceive.md b/Resources/Documentation/Gem/gemreceive.md new file mode 100644 index 0000000000..7362d81b46 --- /dev/null +++ b/Resources/Documentation/Gem/gemreceive.md @@ -0,0 +1,19 @@ +--- +title: gemreceive +description: receive messages from Gem +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: symbol + description: change the receiver name + 2nd: + - type: float + description: change the priority on the fly +outlets: + 1st: + - type: anything + description: received messages +draft: false +--- diff --git a/Resources/Documentation/Gem/gemrepeat.md b/Resources/Documentation/Gem/gemrepeat.md new file mode 100644 index 0000000000..7db7159677 --- /dev/null +++ b/Resources/Documentation/Gem/gemrepeat.md @@ -0,0 +1,24 @@ +--- +title: gemrepeat +description: repeat a gemlist several times +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: number of repeats + default: 0 + +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: number of repeats (integer) +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/gemvertexbuffer.md b/Resources/Documentation/Gem/gemvertexbuffer.md new file mode 100644 index 0000000000..7c00bfc3a5 --- /dev/null +++ b/Resources/Documentation/Gem/gemvertexbuffer.md @@ -0,0 +1,40 @@ +--- +title: gemvertexbuffer +description: renders a vertex buffer +categories: + - object +pdcategory: Graphics +arguments: null +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +methods: + - type: position / posX / posY / posZ + description: update vertex positions from tables + - type: color / colorR / colorG / colorB / colorA + description: update vertex colors from tables + - type: normal / normalX / normalY / normalZ + description: update vertex normals from tables + - type: texture / textureU / textureV + description: update vertex texcoords from tables + - type: resize + description: change the number of vertices to use + - type: draw_range + description: set the range for partial draw + - type: program + description: set the ID for GLSL program. + - type: position_enable , color_enable , texture_enable , normal_enable , attribute_enable + description: enable/disable the use of position, color, texture, normal, and attribute data + - type: attribute (offset) + description: add attribute/update attribute from table + - type: reset_attributes + description: clear attribute data + - type: print_attributes + description: print active attributes +draft: false +--- diff --git a/Resources/Documentation/Gem/gemwin.md b/Resources/Documentation/Gem/gemwin.md new file mode 100644 index 0000000000..e2778f2c48 --- /dev/null +++ b/Resources/Documentation/Gem/gemwin.md @@ -0,0 +1,47 @@ +--- +title: gemwin + +description: interact with Gem window + +categories: + - graphics + +pdcategory: Graphics + +arguments: +- type: float + description: frames per second + default: 20 +- type: symbol + description: context name + default: 20 + +methods: +- type: create + description: create Gem window +- type: destroy + description: remove Gem window +- type: reset + description: reset Gem window attributes +- type: dimen + description: set Gem window size +- type: offset + description: set Gem window position +- type: FSAA + description: enable anti-aliasing +- type: frame + description: set frames per second +- type: color + description: set background colour +- type: print + description: print information + +inlets: + 1st: + - type: float + description: start/stop rendering + +outlets: + +draft: false +--- \ No newline at end of file diff --git a/Resources/Documentation/Gem/glsl_fragment.md b/Resources/Documentation/Gem/glsl_fragment.md new file mode 100644 index 0000000000..2f4d6c367a --- /dev/null +++ b/Resources/Documentation/Gem/glsl_fragment.md @@ -0,0 +1,25 @@ +--- +title: glsl_fragment +description: loads and compiles a GLSL fragment shader into a module +categories: + - object +pdcategory: Graphics +methods: + - type: open + description: filename to load as GLSL fragment shader module + - type: print + description: print info about the GLSL-support in your openGL implementation + +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: ID of the GLSL-module +draft: false +--- diff --git a/Resources/Documentation/Gem/glsl_geometry.md b/Resources/Documentation/Gem/glsl_geometry.md new file mode 100644 index 0000000000..03883c3c6a --- /dev/null +++ b/Resources/Documentation/Gem/glsl_geometry.md @@ -0,0 +1,25 @@ +--- +title: glsl_geometry +description: loads and compiles a GLSL geometry shader into a module +categories: + - object +pdcategory: Graphics +methods: + - type: open + description: filename to load as GLSL geometry shader module + - type: print + description: print info about the GLSL-support in your openGL implementation + +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: ID of the GLSL-module +draft: false +--- diff --git a/Resources/Documentation/Gem/glsl_program.md b/Resources/Documentation/Gem/glsl_program.md new file mode 100644 index 0000000000..6fa26548fe --- /dev/null +++ b/Resources/Documentation/Gem/glsl_program.md @@ -0,0 +1,41 @@ +--- +title: glsl_program +description: links GLSL-modules into a shader program +categories: + - object +pdcategory: Graphics +methods: + - type: shader + description: list of shader-module IDs as reported generated by [glsl_fragment] and [glsl_vertex] + - type: link + description: link the shader-modules given via the "shader"-message + - type: link + description: link the shader-modules given (this is the same as "shader "+"link") + - type: print + description: print info about the GLSL-support in your openGL implementation and about the linked program + - type: ... + description: set the uniform variable of name uniformName to the (list of) uniformParms. this is only valid after successfully linking a program + - type: geometry_intype + description: input type of geometry + - type: geometry_outtype + description: output type of geometry + - type: geometry_type + description: combination of "intype" and "outtype" + - type: geometry_outvertices <#vertices> + description: number of vertices to be created + - type: keepuniforms + description: enable(DEFAULT) or disable the caching of uniform variables across reload + +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: ID of the GLSL-program +draft: false +--- diff --git a/Resources/Documentation/Gem/glsl_vertex.md b/Resources/Documentation/Gem/glsl_vertex.md new file mode 100644 index 0000000000..fb4b529457 --- /dev/null +++ b/Resources/Documentation/Gem/glsl_vertex.md @@ -0,0 +1,25 @@ +--- +title: glsl_vertex +description: loads and compiles a GLSL vertex shader into a module +categories: + - object +pdcategory: Graphics +methods: + - type: open + description: filename to load as GLSL vertex shader module + - type: print + description: print info about the GLSL-support in your openGL implementation + +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: ID of the GLSL-module +draft: false +--- diff --git a/Resources/Documentation/Gem/hsv2rgb.md b/Resources/Documentation/Gem/hsv2rgb.md new file mode 100644 index 0000000000..ac388f50c0 --- /dev/null +++ b/Resources/Documentation/Gem/hsv2rgb.md @@ -0,0 +1,16 @@ +--- +title: hsv2rgb +description: convert RGB color values to HSV +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: list + description: RGB values +outlets: + 1st: + - type: list + description: HSV values +draft: false +--- diff --git a/Resources/Documentation/Gem/imageVert.md b/Resources/Documentation/Gem/imageVert.md new file mode 100644 index 0000000000..9bc58a915e --- /dev/null +++ b/Resources/Documentation/Gem/imageVert.md @@ -0,0 +1,14 @@ +--- +title: imagevert +description: map luminance to height +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/light.md b/Resources/Documentation/Gem/light.md new file mode 100644 index 0000000000..bd934f8b45 --- /dev/null +++ b/Resources/Documentation/Gem/light.md @@ -0,0 +1,26 @@ +--- +title: light +description: adds a point-light to the scene. +categories: + - object +pdcategory: Graphics +arguments: + - type: gemlist + description: +methods: + - type: debug + description: turn debug mode on/off. + - type: list + description: set RGB or RGBA float values for the light color. +inlets: + 1st: + - type: float + description: turn light on/off. + 2nd: + - type: list + description: RGB or RGBA float values for the light color. +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/linear_path.md b/Resources/Documentation/Gem/linear_path.md new file mode 100644 index 0000000000..c25fdce4fb --- /dev/null +++ b/Resources/Documentation/Gem/linear_path.md @@ -0,0 +1,21 @@ +--- +title: linear_path +description: reads out a table +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: dimension of the table + - type: symbol + description: name of the table +inlets: + 1st: + - type: float + description: reading point (0.0-1.0) +outlets: + 1st: + - type: list + description: A multi-dimensional table as a list of floats +draft: false +--- diff --git a/Resources/Documentation/Gem/mesh_line.md b/Resources/Documentation/Gem/mesh_line.md new file mode 100644 index 0000000000..5744e49917 --- /dev/null +++ b/Resources/Documentation/Gem/mesh_line.md @@ -0,0 +1,28 @@ +--- +title: mesh_line +description: renders a mesh in a line +categories: + - object +pdcategory: Graphics +methods: + - type: draw + description: line, default or point + +arguments: + - type: float + description: resolution of the mesh + default: 1 + +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: size +outlets: + 1st: + - type: gemlist + +draft: false +--- diff --git a/Resources/Documentation/Gem/mesh_square.md b/Resources/Documentation/Gem/mesh_square.md new file mode 100644 index 0000000000..82af99b2a6 --- /dev/null +++ b/Resources/Documentation/Gem/mesh_square.md @@ -0,0 +1,29 @@ +--- +title: mesh_square +description: renders a mesh in a square +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: resolution of the mesh + default: 1 +methods: + - type: grid + description: change the grid resolution + - type: gridX + description: change the X grid resolution + - type: gridY + description: change the Y grid resolution +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: size +outlets: + 1st: + - type: gemlist +draft: false +--- \ No newline at end of file diff --git a/Resources/Documentation/Gem/model.md b/Resources/Documentation/Gem/model.md new file mode 100644 index 0000000000..98ecdf89bf --- /dev/null +++ b/Resources/Documentation/Gem/model.md @@ -0,0 +1,35 @@ +--- +title: model +description: renders an Alias/Wavefront model +categories: + - object +pdcategory: Graphics +arguments: + - type: + description: name of an OBJ-file to be loaded +methods: + - type: open + description: load an obj-file + - type: rescale + description: normalize the model (must be set PRIOR to opening a model) + - type: material + description: use material-information (from the mtl file) + - type: group + description: draw only the specified part of the model (0 == all groups) + - type: revert + description: revert faces + - type: smooth + description: set smoothing factor (0.0 == flat, 1.0 == smooth) + - type: texture + description: 0 == linear texturing, 1 == sphere mapping, 2 == UV-mapping + - type: loader + description: choose which backend to use first to open the model +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/multimodel.md b/Resources/Documentation/Gem/multimodel.md new file mode 100644 index 0000000000..77a1f88857 --- /dev/null +++ b/Resources/Documentation/Gem/multimodel.md @@ -0,0 +1,18 @@ +--- +title: multimodel +description: load multiple Alias/Wavefront models and render one of them +categories: + - object +pdcategory: Graphics +methods: + - type: open + description: load models with a specified file name pattern, base number, top number, and skip rate. +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/newWave.md b/Resources/Documentation/Gem/newWave.md new file mode 100644 index 0000000000..820e183c65 --- /dev/null +++ b/Resources/Documentation/Gem/newWave.md @@ -0,0 +1,58 @@ +--- +title: newWaves +description: renders a waving square (mass-spring-system) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: X grid resolution + default: 3 + - type: float + description: Y grid resolution + default: X value +methods: + - type: K1 + description: weight factor + default: 0.05 + - type: D1 + description: damping factor + default: 0.1 + - type: K2 + description: weight factor + default: 0 + - type: D2 + description: damping factor + default: 0 + - type: K3 + description: weight factor + default: 0 + - type: D3 + description: damping factor + default: 0 + - type: position + description: clamp the node at (X Y) to a certain height and release it + - type: noise + description: add a random force ( -val < force < +val) to all nodes + - type: force + description: apply a force of value of arg3 onto the wave at position (arg1, arg2) + +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: size (dimX & dimY) + 3rd: + - type: float + description: height + 4th: + - type: float + description: action +outlets: + 1st: + - type: gemlist + +draft: false +--- diff --git a/Resources/Documentation/Gem/ortho.md b/Resources/Documentation/Gem/ortho.md new file mode 100644 index 0000000000..075186e4f2 --- /dev/null +++ b/Resources/Documentation/Gem/ortho.md @@ -0,0 +1,20 @@ +--- +title: ortho +description: orthographic rendering +categories: + - object +pdcategory: Graphics +methods: + - type: float + description: turn orthographic rendering ON (default) or OFF + - type: float + description: switch aspect ratio (default: 1 = 1, 0 = window width/height) +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/part_color.md b/Resources/Documentation/Gem/part_color.md new file mode 100644 index 0000000000..0903629b45 --- /dev/null +++ b/Resources/Documentation/Gem/part_color.md @@ -0,0 +1,21 @@ +--- +title: part_color +description: defines color of particles +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: color R1 G1 B1 + 3rd: + - type: list + description: color R2 G2 B2 +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/part_damp.md b/Resources/Documentation/Gem/part_damp.md new file mode 100644 index 0000000000..edaa7108c6 --- /dev/null +++ b/Resources/Documentation/Gem/part_damp.md @@ -0,0 +1,29 @@ +--- +title: part_damp +description: change velocity of particles +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: V1 + - type: float + description: V2 + - type: float + description: V3 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: V1 + - type: float + description: V2 + - type: float + description: V3 +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/part_draw.md b/Resources/Documentation/Gem/part_draw.md new file mode 100644 index 0000000000..f9a2dd61c1 --- /dev/null +++ b/Resources/Documentation/Gem/part_draw.md @@ -0,0 +1,15 @@ +--- +title: part_draw +description: draw a particle system +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/part_follow.md b/Resources/Documentation/Gem/part_follow.md new file mode 100644 index 0000000000..d8e75aec5d --- /dev/null +++ b/Resources/Documentation/Gem/part_follow.md @@ -0,0 +1,19 @@ +--- +title: part_follow +description: make particles follow each other. +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: particle follow +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + +draft: false +--- diff --git a/Resources/Documentation/Gem/part_gravity.md b/Resources/Documentation/Gem/part_gravity.md new file mode 100644 index 0000000000..74346a3519 --- /dev/null +++ b/Resources/Documentation/Gem/part_gravity.md @@ -0,0 +1,23 @@ +--- +title: part_gravity +description: sets the gravity-vector of the particle-system +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: gravitation vector x + - type: float + description: gravitation vector y + - type: float + description: gravitation vector z +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/part_head.md b/Resources/Documentation/Gem/part_head.md new file mode 100644 index 0000000000..57c4fda50d --- /dev/null +++ b/Resources/Documentation/Gem/part_head.md @@ -0,0 +1,20 @@ +--- +title: part_head +description: starts a particle-system +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: the number of particles that can exist in one instance of time + default: 1000 +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/part_info.md b/Resources/Documentation/Gem/part_info.md new file mode 100644 index 0000000000..a7a6c3962f --- /dev/null +++ b/Resources/Documentation/Gem/part_info.md @@ -0,0 +1,16 @@ +--- +title: part_info +description: gives all available information of all the particles in the system +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/part_killold.md b/Resources/Documentation/Gem/part_killold.md new file mode 100644 index 0000000000..96abb9548d --- /dev/null +++ b/Resources/Documentation/Gem/part_killold.md @@ -0,0 +1,18 @@ +--- +title: part_killold +description: kill all particles which are older than the kill time +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: kill time + default: 10 +inlets: + 1st: + - type: gemlist +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/part_killslow.md b/Resources/Documentation/Gem/part_killslow.md new file mode 100644 index 0000000000..d2d30f1c2d --- /dev/null +++ b/Resources/Documentation/Gem/part_killslow.md @@ -0,0 +1,18 @@ +--- +title: part_killslow +description: kill all particles which are slower than the kill speed +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: kill speed + default: 0.01 +inlets: + 1st: + - type: gemlist +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/part_orbitpoint.md b/Resources/Documentation/Gem/part_orbitpoint.md new file mode 100644 index 0000000000..283525ce30 --- /dev/null +++ b/Resources/Documentation/Gem/part_orbitpoint.md @@ -0,0 +1,27 @@ +--- +title: part_orbitpoint +description: will make the particles orbit about a specified 3d coordinate +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: orbipoint x + default: 0 + - type: float + description: orbipoint y + default: 0 + - type: float + description: orbipoint z + default: 0 + - type: float + description: attraction + default: 1 +inlets: + 1st: + - type: gemlist +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/part_render.md b/Resources/Documentation/Gem/part_render.md new file mode 100644 index 0000000000..4c8e2a15b6 --- /dev/null +++ b/Resources/Documentation/Gem/part_render.md @@ -0,0 +1,14 @@ +--- +title: part_render +description: draws a particle system that was set up with [part_head] and other +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/part_sink.md b/Resources/Documentation/Gem/part_sink.md new file mode 100644 index 0000000000..57a0d45990 --- /dev/null +++ b/Resources/Documentation/Gem/part_sink.md @@ -0,0 +1,25 @@ +--- +title: part_sink +description: sets up a sink for the particles within the system +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: domain, one of "point", "line", "triangle", "plane", "box", "sphere", "cylinder", "cone", "blob", "disc", "rectangle" + - type: list + description: up to 9 floats defining the specified domain +inlets: + 1st: + - type: gemlist + 2nd: + - type: symbol + description: domain, one of "point", "line", "triangle", "plane", "box", "sphere", "cylinder", "cone", "blob", "disc", "rectangle" + 3rd: + - type: list + description: up to 9 floats defining the specified domain +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/part_size.md b/Resources/Documentation/Gem/part_size.md new file mode 100644 index 0000000000..c48f7fa47f --- /dev/null +++ b/Resources/Documentation/Gem/part_size.md @@ -0,0 +1,17 @@ +--- +title: part_size +description: changes the size of the particles of a particle-system +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: particle size +inlets: + 1st: + - type: gemlist +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/part_source.md b/Resources/Documentation/Gem/part_source.md new file mode 100644 index 0000000000..798664c938 --- /dev/null +++ b/Resources/Documentation/Gem/part_source.md @@ -0,0 +1,23 @@ +--- +title: part_source +description: adds a particle-source to a particle system +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: number of particles emitted at each rendering frame +inlets: + 1st: + - type: gemlist + 2nd: + - type: symbol + description: domain, one of "point", "line", "triangle", "plane", "box", "sphere", "cylinder", "cone", "blob", "disc", "rectangle" + 3rd: + - type: list + description: up to 9 floats defining the specified domain +outlets: + 1st: + - type: gemlist +draft: false +--- \ No newline at end of file diff --git a/Resources/Documentation/Gem/part_targetcolor.md b/Resources/Documentation/Gem/part_targetcolor.md new file mode 100644 index 0000000000..cfdedcad52 --- /dev/null +++ b/Resources/Documentation/Gem/part_targetcolor.md @@ -0,0 +1,19 @@ +--- +title: part_targetcolor +description: changes the color of particles by a scale factor every frame +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: RGB or RGBA vector representing the target color + - type: float + description: scale factor (default: 0.05) +inlets: + 1st: + - type: gemlist +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/part_targetsize.md b/Resources/Documentation/Gem/part_targetsize.md new file mode 100644 index 0000000000..3441aecd62 --- /dev/null +++ b/Resources/Documentation/Gem/part_targetsize.md @@ -0,0 +1,21 @@ +--- +title: part_targetsize +description: changes the size of particles by a scale factor every frame +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: size of particles + default: 1 + - type: float + description: scale factor for size change + default: 0.05 +inlets: + 1st: + - type: gemlist +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/part_velcone.md b/Resources/Documentation/Gem/part_velcone.md new file mode 100644 index 0000000000..3447fe4fd6 --- /dev/null +++ b/Resources/Documentation/Gem/part_velcone.md @@ -0,0 +1,23 @@ +--- +title: part_velcone +description: sets a cone with a specified height and center to be the velocity-domain of emitted particles +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: default dimensions (x y z height) +inlets: + 1st: + - type: gemlist + 2nd: + - type: list + description: x, y, z coordinates + 3rd: + - type: float + description: height +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/part_velocity.md b/Resources/Documentation/Gem/part_velocity.md new file mode 100644 index 0000000000..9c6c74fe85 --- /dev/null +++ b/Resources/Documentation/Gem/part_velocity.md @@ -0,0 +1,25 @@ +--- +title: part_velocity +description: sets velocity of newly emitted particles within the system +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: domain, one of "point", "line", "triangle", "plane", "box", "sphere", "cylinder", "cone", "blob", "disc", "rectangle" + - type: list + description: up to 9 floats defining the specified domain +inlets: + 1st: + - type: gemlist + 2nd: + - type: symbol + description: domain, one of "point", "line", "triangle", "plane", "box", "sphere", "cylinder", "cone", "blob", "disc", "rectangle" + 3rd: + - type: list + description: up to 9 floats defining the specified domain +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/part_velsphere.md b/Resources/Documentation/Gem/part_velsphere.md new file mode 100644 index 0000000000..a7035f674c --- /dev/null +++ b/Resources/Documentation/Gem/part_velsphere.md @@ -0,0 +1,23 @@ +--- +title: part_velsphere +description: sets a sphere with a specified radius and centre to be the velocity-domain of emitted particles +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: default dimensions (x y z radius) +inlets: + 1st: + - type: gemlist + 2nd: + - type: list + description: x, y, z coordinates + 3rd: + - type: float + description: radius +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/part_vertex.md b/Resources/Documentation/Gem/part_vertex.md new file mode 100644 index 0000000000..4ed69af1a3 --- /dev/null +++ b/Resources/Documentation/Gem/part_vertex.md @@ -0,0 +1,17 @@ +--- +title: part_vertex +description: adds a particle at the specified offset +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: default position (x y z) +inlets: + 1st: + - type: gemlist +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_2grey.md b/Resources/Documentation/Gem/pix_2grey.md new file mode 100644 index 0000000000..f5c8e74ee9 --- /dev/null +++ b/Resources/Documentation/Gem/pix_2grey.md @@ -0,0 +1,15 @@ +--- +title: pix_2grey, pix_2gray +description: convert a pix to greyscale +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_a_2grey.md b/Resources/Documentation/Gem/pix_a_2grey.md new file mode 100644 index 0000000000..54c5eae390 --- /dev/null +++ b/Resources/Documentation/Gem/pix_a_2grey.md @@ -0,0 +1,18 @@ +--- +title: pix_a_2grey, pix_a_2gray +description: convert a pixel to greyscale based on alpha +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: + description: alpha-threshold (-1..0..+1) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_add.md b/Resources/Documentation/Gem/pix_add.md new file mode 100644 index 0000000000..385fb8623c --- /dev/null +++ b/Resources/Documentation/Gem/pix_add.md @@ -0,0 +1,16 @@ +--- +title: pix_add +description: add 2 images +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + 2nd: + - type: gemlist +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_aging.md b/Resources/Documentation/Gem/pix_aging.md new file mode 100644 index 0000000000..be91896972 --- /dev/null +++ b/Resources/Documentation/Gem/pix_aging.md @@ -0,0 +1,31 @@ +--- +title: pix_aging +description: apply a super8-like aging effect +categories: + - object +pdcategory: Graphics +methods: + - type: scratch + description: add a maximum of scratches + - type: coloraging + description: color-bleaching + - type: dust + description: add "dust" + - type: pits + description: add "pits" +inlets: + 1st: + - type: gemlist + - type: float + description: add a maximum of scratches + - type: float + description: color-bleaching + - type: float + description: add "dust" + - type: float + description: add "pits" +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_alpha.md b/Resources/Documentation/Gem/pix_alpha.md new file mode 100644 index 0000000000..06c9c2d23c --- /dev/null +++ b/Resources/Documentation/Gem/pix_alpha.md @@ -0,0 +1,26 @@ +--- +title: pix_alpha +description: set the alpha values of an RGBA-pix +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + 2nd: + - type: float + description: alpha value if passed + 3rd: + - type: float + description: alpha value if not passed + 4th: + - type: list + description: high-threshold (RGB) + 5th: + - type: list + description: low-threshold (RGB) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_background.md b/Resources/Documentation/Gem/pix_background.md new file mode 100644 index 0000000000..31e1190beb --- /dev/null +++ b/Resources/Documentation/Gem/pix_background.md @@ -0,0 +1,25 @@ +--- +title: pix_background +description: separate an object from the background +categories: + - object +pdcategory: Graphics +methods: + - type: reset + description: reset the background and capture a new image +inlets: + 1st: + - type: gemlist + description: + - type: bang + description: alias for reset + 3rd: + - type: float + description: greyscale range + - type: list + description: range +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_backlight.md b/Resources/Documentation/Gem/pix_backlight.md new file mode 100644 index 0000000000..5a58b4b05a --- /dev/null +++ b/Resources/Documentation/Gem/pix_backlight.md @@ -0,0 +1,33 @@ +--- +title: pix_backlight +description: radially displace pixels depending on their luminance value, producing a backlighting effect +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: scale-factor + default: 0.5 + - type: float + description: floor + default: 0 + - type: float + description: ceiling + default: 1 +inlets: + 1st: + - type: gemlist + 2nd: + - type: float + description: scale-factor + 3rd: + - type: float + description: floor + 4th: + - type: float + description: ceiling +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_biquad.md b/Resources/Documentation/Gem/pix_biquad.md new file mode 100644 index 0000000000..9b02a52d7f --- /dev/null +++ b/Resources/Documentation/Gem/pix_biquad.md @@ -0,0 +1,19 @@ +--- +title: pix_biquad +description: time-based IIR filter +categories: + - object +pdcategory: Graphics +methods: + - type: set + description: overwrites the filter-buffers with the next incoming image +inlets: + 1st: + - type: gemlist + - type: + description: the filter-coefficients "fb0 fb1 fb2 ff1 ff2 ff3" +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_bitmask.md b/Resources/Documentation/Gem/pix_bitmask.md new file mode 100644 index 0000000000..860bc541aa --- /dev/null +++ b/Resources/Documentation/Gem/pix_bitmask.md @@ -0,0 +1,20 @@ +--- +title: pix_bitmask +description: mask out pixels +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + 2nd: + - type: list + description: mask value for all channels (0..255) + 3rd: + - type: list + description: 3 (RGB) or 4 (RGBA) mask-values in INTeger (0..255) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_blob.md b/Resources/Documentation/Gem/pix_blob.md new file mode 100644 index 0000000000..f8a97bc3a8 --- /dev/null +++ b/Resources/Documentation/Gem/pix_blob.md @@ -0,0 +1,20 @@ +--- +title: pix_blob +description: calculate the "center-of-gravity" of a certain (combination of) channel(s) +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + 2nd: + - type: float + description: mode (0=gray, 1=red, 2=green, 3=blue, 4=alpha) + 3rd: + - type: list + description: colour weights +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_blobtracker.md b/Resources/Documentation/Gem/pix_blobtracker.md new file mode 100644 index 0000000000..52486aeefd --- /dev/null +++ b/Resources/Documentation/Gem/pix_blobtracker.md @@ -0,0 +1,22 @@ +--- +title: pix_blobtracker +description: blob detector and tracker +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: max number of blobs to detect +methods: + - type: threshold + description: minimum luminance of a pixel to be considered part of a blob + - type: blobSize + description: minimum relative size of a blob +inlets: + 1st: + - type: gemlist +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_blur.md b/Resources/Documentation/Gem/pix_blur.md new file mode 100644 index 0000000000..57d39ac9b1 --- /dev/null +++ b/Resources/Documentation/Gem/pix_blur.md @@ -0,0 +1,18 @@ +--- +title: pix_blur +description: apply a blur effect to an image +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + 2nd: + - type: float + description: blur amount +outlets: + 1st: + - type: gemlist + +draft: true +--- diff --git a/Resources/Documentation/Gem/pix_buf.md b/Resources/Documentation/Gem/pix_buf.md new file mode 100644 index 0000000000..dd4d81a5dd --- /dev/null +++ b/Resources/Documentation/Gem/pix_buf.md @@ -0,0 +1,22 @@ +--- +title: pix_buf +description: buffer a pix +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: the default value to force processing +methods: + - type: auto + description: force image-processing in subsequent objects each render-cycle +inlets: + 1st: + - type: gemlist + - type: bang + description: copy of input-data to the output and force all subsequent [pix_]-objects to process +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_buffer.md b/Resources/Documentation/Gem/pix_buffer.md new file mode 100644 index 0000000000..2cd0bd5119 --- /dev/null +++ b/Resources/Documentation/Gem/pix_buffer.md @@ -0,0 +1,27 @@ +--- +title: pix_buffer +description: a storage place for a number of images +categories: + - object +pdcategory: Graphics +methods: + - type: allocate + description: preallocate memory for images + - type: open + description: put an image into the pix_buffer at the given index + - type: resize + description: re-allocate slots in the buffer (slots will survive this) + - type: copy + description: copy a pix from one slot to another + - type: save + description: save image in a given slot to hard disk +inlets: + 1st: + - type: bang + description: get the size of the buffer in frames +outlets: + 1st: + - type: float + description: size of the buffer +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_buffer_filmopen.md b/Resources/Documentation/Gem/pix_buffer_filmopen.md new file mode 100644 index 0000000000..bbc8dd394f --- /dev/null +++ b/Resources/Documentation/Gem/pix_buffer_filmopen.md @@ -0,0 +1,24 @@ +--- +title: pix_buffer_filmopen +description: reads a movie into a named buffer in the pix_buffer object +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: buffer name +inlets: + 1st: + - type: open + description: read a filename into buffer starting at index + - type: set + description: write to another buffer +outlets: + 1st: + - type: list + description: + 2nd: + - type: bang + description: bangs when finished loading +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_buffer_read.md b/Resources/Documentation/Gem/pix_buffer_read.md new file mode 100644 index 0000000000..f6b22e789c --- /dev/null +++ b/Resources/Documentation/Gem/pix_buffer_read.md @@ -0,0 +1,24 @@ +--- +title: pix_buffer_read +description: reads an image from the named buffer provided by [pix_buffer] +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: +methods: + - type: set + description: read from another buffer +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: index of the frame in the named pix_buffer to read +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_buffer_write.md b/Resources/Documentation/Gem/pix_buffer_write.md new file mode 100644 index 0000000000..60481100a2 --- /dev/null +++ b/Resources/Documentation/Gem/pix_buffer_write.md @@ -0,0 +1,24 @@ +--- +title: pix_buffer_write +description: writes an image into a named buffer created by the [pix_buffer] object +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: +methods: + - type: set + description: write to another buffer +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: index in the named pix_buffer +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_chroma_key.md b/Resources/Documentation/Gem/pix_chroma_key.md new file mode 100644 index 0000000000..ff34e1252b --- /dev/null +++ b/Resources/Documentation/Gem/pix_chroma_key.md @@ -0,0 +1,26 @@ +--- +title: pix_chroma_key +description: mix 2 images based on their color +categories: + - object +pdcategory: Graphics +methods: + - type: float + description: which stream is the key-source (0=left stream, 1=right stream) + - type: list + description: list of 3 floats of the pixel-value to key out + - type: list + description: list of 3 floats defining the +/-range of the key +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_clearblock.md b/Resources/Documentation/Gem/pix_clearblock.md new file mode 100644 index 0000000000..146c5871df --- /dev/null +++ b/Resources/Documentation/Gem/pix_clearblock.md @@ -0,0 +1,18 @@ +--- +title: pix_clearblock +description: clear a block in an image +categories: + - object +pdcategory: Graphics + + +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_color.md b/Resources/Documentation/Gem/pix_color.md new file mode 100644 index 0000000000..26438aeb94 --- /dev/null +++ b/Resources/Documentation/Gem/pix_color.md @@ -0,0 +1,19 @@ +--- +title: pix_color, pix_colour +description: set the colour-channels of an image +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: 3 (RGB) or 4 (RGBA) values +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_coloralpha.md b/Resources/Documentation/Gem/pix_coloralpha.md new file mode 100644 index 0000000000..fb649c60b2 --- /dev/null +++ b/Resources/Documentation/Gem/pix_coloralpha.md @@ -0,0 +1,19 @@ +--- +title: pix_coloralpha, pix_colouralpha +description: calculate the Alpha-channel from the RGB-data. +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: turn on/off + default: 1 +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_colorclassify.md b/Resources/Documentation/Gem/pix_colorclassify.md new file mode 100644 index 0000000000..64c7d73a33 --- /dev/null +++ b/Resources/Documentation/Gem/pix_colorclassify.md @@ -0,0 +1,16 @@ +--- +title: pix_colorclassify, pix_colourclassify +description: detects color classes in an image. +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: RGBA image +outlets: + 1st: + - type: gemlist + description: RGBA image with encoded color class representatives for each pixel +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_colormatrix.md b/Resources/Documentation/Gem/pix_colormatrix.md new file mode 100644 index 0000000000..d489ee12a0 --- /dev/null +++ b/Resources/Documentation/Gem/pix_colormatrix.md @@ -0,0 +1,19 @@ +--- +title: pix_colormatrix, pix_colourmatrix +description: transform the pixel values by a matrix. +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: 9 (RGB) or 16 (RGBA) values, forming a matrix +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_colorreduce.md b/Resources/Documentation/Gem/pix_colorreduce.md new file mode 100644 index 0000000000..42cbdcff29 --- /dev/null +++ b/Resources/Documentation/Gem/pix_colorreduce.md @@ -0,0 +1,28 @@ +--- +title: pix_colorreduce, pix_colourreduce +description: reduce the number of colours in the image. +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: number of colours +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: number of colours + 3rd: + - type: float + description: persistency of the palette + 4th: + - type: float + description: edge smoothing +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_compare.md b/Resources/Documentation/Gem/pix_compare.md new file mode 100644 index 0000000000..0400887855 --- /dev/null +++ b/Resources/Documentation/Gem/pix_compare.md @@ -0,0 +1,28 @@ +--- +title: pix_compare +description: mix 2 images based on their luminance. +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: take lower or higher valued pixel + default: 1 +methods: + - type: direction + description: take lower or higher valued pixel +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: gemlist + description: + 3rd: + +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_composite.md b/Resources/Documentation/Gem/pix_composite.md new file mode 100644 index 0000000000..f0601a35d6 --- /dev/null +++ b/Resources/Documentation/Gem/pix_composite.md @@ -0,0 +1,19 @@ +--- +title: pix_composite +description: alpha-blend 2 images. +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_contrast.md b/Resources/Documentation/Gem/pix_contrast.md new file mode 100644 index 0000000000..dd953e264d --- /dev/null +++ b/Resources/Documentation/Gem/pix_contrast.md @@ -0,0 +1,29 @@ +--- +title: pix_contrast +description: change contrast and saturation of an image +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: contrast (>=0, default: 1) + default: 1 + - type: float + description: saturation (>=0, default: 1) + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: contrast (>=0, default: 1) + 3rd: + - type: float + description: saturation (>=0, default: 1) +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_convert.md b/Resources/Documentation/Gem/pix_convert.md new file mode 100644 index 0000000000..0f49ec9f4b --- /dev/null +++ b/Resources/Documentation/Gem/pix_convert.md @@ -0,0 +1,22 @@ +--- +title: pix_convert +description: convert the colorspace of an image +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: colorspace to convert to +methods: + - type: color + description: colorspace to convert to +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_convolve.md b/Resources/Documentation/Gem/pix_convolve.md new file mode 100644 index 0000000000..9eef4ea65f --- /dev/null +++ b/Resources/Documentation/Gem/pix_convolve.md @@ -0,0 +1,23 @@ +--- +title: pix_convolve +description: apply a convolution kernel to an image +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: matrix dimensions +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: scale factor + - type: list + description: the convolution kernel +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_coordinate.md b/Resources/Documentation/Gem/pix_coordinate.md new file mode 100644 index 0000000000..5e107570e9 --- /dev/null +++ b/Resources/Documentation/Gem/pix_coordinate.md @@ -0,0 +1,18 @@ +--- +title: pix_coordinate +description: set the texture-coordinates for an image +categories: + - object +pdcategory: Graphics +arguments: null +inlets: + 1st: + - type: gemlist + 2nd: + - type: list + description: 8 values (4 (s, t)-pairs) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_crop.md b/Resources/Documentation/Gem/pix_crop.md new file mode 100644 index 0000000000..a072353520 --- /dev/null +++ b/Resources/Documentation/Gem/pix_crop.md @@ -0,0 +1,39 @@ +--- +title: pix_crop +description: get a subimage of an image +categories: + - object +pdcategory: Graphics +arguments: + - name: offsetX + type: float + description: offset from the left edge of the image + - name: offsetY + type: float + description: offset from the bottom edge of the image + - name: dimenX + type: float + description: width of the subimage in pixels + - name: dimenY + type: float + description: height of the subimage in pixels +inlets: + 1st: + - type: gemlist + 2nd: + - type: float + description: width + 3rd: + - type: float + description: height + 4th: + - type: float + description: offset X + 5th: + - type: float + description: offset Y +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_curve.md b/Resources/Documentation/Gem/pix_curve.md new file mode 100644 index 0000000000..753d742c0c --- /dev/null +++ b/Resources/Documentation/Gem/pix_curve.md @@ -0,0 +1,25 @@ +--- +title: pix_curve +description: applies color-curves stored in arrays to an image +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: table name per channel +methods: + - type: set + description: common table for all channels + - type: set + description: separate tables for all channels + - type: set + description: separate tables for all channels (incl. alpha) +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_data.md b/Resources/Documentation/Gem/pix_data.md new file mode 100644 index 0000000000..1b21462b2f --- /dev/null +++ b/Resources/Documentation/Gem/pix_data.md @@ -0,0 +1,43 @@ +--- +title: pix_data +description: gets the color of a specified pixel within an image when triggered +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: xpos + - type: float + description: ypos +methods: + - type: normalize + description: normalize 0|1: normalization mode (0=none, 1=normalized. default=1) + - type: quality + description: quality 0|1: interpolation mode (0=none, 1=2D. default=0) +inlets: + 1st: + - type: bang + description: + - type: list + description: + 2nd: + - type: gemlist + description: + 3rd: + - type: float + description: xpos in the image + 4th: + - type: float + description: ypos in the image +outlets: + 1st: + - type: - + description: dead outlet for legacy reasons + 2nd: + - type: list + description: RGB value (normalized 0..1) + 3rd: + - type: float + description: grey value (normalized 0..1) +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_deinterlace.md b/Resources/Documentation/Gem/pix_deinterlace.md new file mode 100644 index 0000000000..9cacadc586 --- /dev/null +++ b/Resources/Documentation/Gem/pix_deinterlace.md @@ -0,0 +1,19 @@ +--- +title: pix_deinterlace +description: deinterlaces an image +categories: + - object +pdcategory: Graphics +methods: + - type: mode + description: enforce (1) or not (0) +inlets: + 1st: + - type: gemlist + description: + +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_delay.md b/Resources/Documentation/Gem/pix_delay.md new file mode 100644 index 0000000000..c1b790fccd --- /dev/null +++ b/Resources/Documentation/Gem/pix_delay.md @@ -0,0 +1,20 @@ +--- +title: pix_delay +description: delay a series of images +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: max number of delayed frames +inlets: + 1st: + - type: gemlist + description: gemlist + - type: float + description: delay (in frames) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_diff.md b/Resources/Documentation/Gem/pix_diff.md new file mode 100644 index 0000000000..d6b31acf99 --- /dev/null +++ b/Resources/Documentation/Gem/pix_diff.md @@ -0,0 +1,20 @@ +--- +title: pix_diff +description: get the absolute value of the difference between 2 images +categories: + - object +pdcategory: Graphics + + +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_dot.md b/Resources/Documentation/Gem/pix_dot.md new file mode 100644 index 0000000000..251158473a --- /dev/null +++ b/Resources/Documentation/Gem/pix_dot.md @@ -0,0 +1,21 @@ +--- +title: pix_dot +description: simplify an image by representing each segment with a white dot whose size is relative to the luminance of the original segment +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: size of the dots +inlets: + 1st: + - type: gemlist + description: gemlist + 2nd: + - type: float + description: size of the dots +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_draw.md b/Resources/Documentation/Gem/pix_draw.md new file mode 100644 index 0000000000..5a145669b0 --- /dev/null +++ b/Resources/Documentation/Gem/pix_draw.md @@ -0,0 +1,16 @@ +--- +title: pix_draw +description: draw pixels on the screen without doing any texture mapping. +categories: + - object +pdcategory: Graphics + +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_dump.md b/Resources/Documentation/Gem/pix_dump.md new file mode 100644 index 0000000000..54e9f7e173 --- /dev/null +++ b/Resources/Documentation/Gem/pix_dump.md @@ -0,0 +1,24 @@ +--- +title: pix_dump +description: dump all the pixel-data of an image +categories: + - object +pdcategory: Graphics +methods: +- type: bytemode + description: set normalization on or off +inlets: + 1st: + - type: gemlist + description: gemlist + - type: bang + description: do dump + +outlets: + 1st: + - type: gemlist + 2nd: + - type: list + description: dumped pixel data +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_duotone.md b/Resources/Documentation/Gem/pix_duotone.md new file mode 100644 index 0000000000..9e4929560f --- /dev/null +++ b/Resources/Documentation/Gem/pix_duotone.md @@ -0,0 +1,24 @@ +--- +title: pix_duotone +description: reduce the number of colours by thresholding +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: gemlist + 2nd: + - type: list + description: threshold (RGB) + 3rd: + - type: list + description: color if original pixel-value is below threshold + 4th: + - type: list + description: color if original pixel-value is above threshold +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_equal.md b/Resources/Documentation/Gem/pix_equal.md new file mode 100644 index 0000000000..82ad435295 --- /dev/null +++ b/Resources/Documentation/Gem/pix_equal.md @@ -0,0 +1,22 @@ +--- +title: pix_equal +description: marks the pixels nearly equal to a given color +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: gemlist (RGBA image) + 2nd: + - type: list + description: list of lower bounds [R G B A=1], ranging from 0 to 1 + 3rd: + - type: list + description: list of upper bounds [R G B A=1], ranging from 0 to 1 +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_film.md b/Resources/Documentation/Gem/pix_film.md new file mode 100644 index 0000000000..d49e1fd203 --- /dev/null +++ b/Resources/Documentation/Gem/pix_film.md @@ -0,0 +1,39 @@ +--- +title: pix_film +description: load in a movie-file +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: file to load initially +methods: + - type: open + description: opens the movie arg1, decodes it into the specified arg2 color-space if supported + - type: colorspace + description: RGBA, YUV or Grey + - type: auto + description: starts/stops automatic playback. (default:0) + - type: loader + description: open the film using only the specified backend(s) +inlets: + 1st: + - type: gemlist + description: + - type: bang + description: (re)send the l/w/h/fps info to the 2nd outlet + 2nd: + - type: float + description: changes the frame to be decoded on rendering +outlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: length, width, height and fps + 3rd: + - type: bang + description: indicates that the last frame has been reached +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_flip.md b/Resources/Documentation/Gem/pix_flip.md new file mode 100644 index 0000000000..8168c7a634 --- /dev/null +++ b/Resources/Documentation/Gem/pix_flip.md @@ -0,0 +1,28 @@ +--- +title: pix_flip +description: flips the image along an axis +categories: + - object +pdcategory: Graphics +methods: + - type: none + description: set flip mode to none + - type: horizontal + description: set flip mode to horizontal + - type: vertical + description: set flip mode to vertical + - type: both + description: set flip mode to both +arguments: + - type: symbol + description: flip mode (none, horizontal, vertical, both) +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_freeframe.md b/Resources/Documentation/Gem/pix_freeframe.md new file mode 100644 index 0000000000..91ac423306 --- /dev/null +++ b/Resources/Documentation/Gem/pix_freeframe.md @@ -0,0 +1,27 @@ +--- +title: pix_freeframe +description: run a FreeFrame effect +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: the plugin to load +methods: + - type: open + description: load another plugin +inlets: + 1st: + - type: gemlist + description: + - type: list + description: set parameter arg1 to value of arg2 + nth: + - type: + description: depending on the settable parameter +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_frei0r.md b/Resources/Documentation/Gem/pix_frei0r.md new file mode 100644 index 0000000000..15699a3034 --- /dev/null +++ b/Resources/Documentation/Gem/pix_frei0r.md @@ -0,0 +1,27 @@ +--- +title: pix_frei0r +description: run a Frei0r effect +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: the plugin to load +methods: + - type: load + description: load a plugin +inlets: + 1st: + - type: gemlist + description: + - type: list + description: set parameter of arg1 to value arg2 + nth: + - type: variable + description: depending on the settable parameter +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_gain.md b/Resources/Documentation/Gem/pix_gain.md new file mode 100644 index 0000000000..24170ba2b9 --- /dev/null +++ b/Resources/Documentation/Gem/pix_gain.md @@ -0,0 +1,24 @@ +--- +title: pix_gain +description: multiply pixel-values +categories: + - object +pdcategory: Graphics +arguments: + - type: + description: gain vector +inlets: + 1st: + - type: gemlist + description: + - type: + description: multiplier for all channels + 2nd: + - type: list + description: 3 (RGB) or 4 (RGBA) values as multipliers for each channel +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_grey.md b/Resources/Documentation/Gem/pix_grey.md new file mode 100644 index 0000000000..ac6319432f --- /dev/null +++ b/Resources/Documentation/Gem/pix_grey.md @@ -0,0 +1,20 @@ +--- +title: pix_grey, pix_gray +description: convert the colorspace of an image to grey +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: turn conversion on/off + default: 1 +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_halftone.md b/Resources/Documentation/Gem/pix_halftone.md new file mode 100644 index 0000000000..734aceb436 --- /dev/null +++ b/Resources/Documentation/Gem/pix_halftone.md @@ -0,0 +1,27 @@ +--- +title: pix_halftone +description: draws the input using halftone patterns +categories: + - object +pdcategory: Graphics +methods: + - type: style + description: selects a style +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: pattern size (1-32) + 3rd: + - type: float + description: orientation in degrees (0-360) + 4th: + - type: float + description: smoothness (0-1) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_histo.md b/Resources/Documentation/Gem/pix_histo.md new file mode 100644 index 0000000000..a93bb93ddf --- /dev/null +++ b/Resources/Documentation/Gem/pix_histo.md @@ -0,0 +1,25 @@ +--- +title: pix_histo +description: gets the histogram (density function) of an image. +categories: + - object +pdcategory: Graphics +arguments: + - type: + description: tables to store histogram +methods: + - type: set + description: set table to store grey-histogram + - type: set + description: set tables to store RGB-histograms + - type: set + description: set tables to store RGBA-histograms +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_hsv2rgb.md b/Resources/Documentation/Gem/pix_hsv2rgb.md new file mode 100644 index 0000000000..fc481cdfaa --- /dev/null +++ b/Resources/Documentation/Gem/pix_hsv2rgb.md @@ -0,0 +1,17 @@ +--- +title: pix_hsv2rgb +description: convert HSV into RGB +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + - type: float + description: apply/don't apply +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_image.md b/Resources/Documentation/Gem/pix_image.md new file mode 100644 index 0000000000..65261c0037 --- /dev/null +++ b/Resources/Documentation/Gem/pix_image.md @@ -0,0 +1,24 @@ +--- +title: pix_image +description: loads in an image to be used as texture, bitblit, or something else +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: filename +methods: + - type: open + description: open image file with specified filename + - type: thread + description: specify whether to use a separate thread for image loading +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_imageInPlace.md b/Resources/Documentation/Gem/pix_imageInPlace.md new file mode 100644 index 0000000000..03f8d42f83 --- /dev/null +++ b/Resources/Documentation/Gem/pix_imageInPlace.md @@ -0,0 +1,29 @@ +--- +title: pix_imageInPlace +description: loads in multiple image files +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: filename (with wildcard *) for images to load +methods: + - type: preload + description: open images (the wildcard in the filename is expanded with integer 0..#) + - type: download + description: load the "preload"ed images into texture-RAM + - type: purge + description: delete images from texture-RAM +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: select image (starting at 0) +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_info.md b/Resources/Documentation/Gem/pix_info.md new file mode 100644 index 0000000000..ecc8839d16 --- /dev/null +++ b/Resources/Documentation/Gem/pix_info.md @@ -0,0 +1,43 @@ +--- +title: pix_info +description: get information about the current image +categories: + - object +pdcategory: Graphics +flags: + - name: -m + description: message mode +methods: + - type: symbolic + description: output openGL constants as symbols rather than numbers when in message mode +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: image-width (or in message mode, all info about image) + 3rd: + - type: float + description: image-height (unused in message mode) + 4th: + - type: float + description: bytes per pixel (unused in message mode) + 5th: + - type: float + description: format (unused in message mode) + 6th: + - type: list + description: (unused in message mode) + 7th: + - type: list + description: (unused in message mode) + 8th: + - type: pointer + description: image-data (unused in message mode) +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_invert.md b/Resources/Documentation/Gem/pix_invert.md new file mode 100644 index 0000000000..5c84ccac16 --- /dev/null +++ b/Resources/Documentation/Gem/pix_invert.md @@ -0,0 +1,18 @@ +--- +title: pix_invert +description: invert an image +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + - type: float + description: apply/don't apply +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_kaleidoscope.md b/Resources/Documentation/Gem/pix_kaleidoscope.md new file mode 100644 index 0000000000..e532bc84cc --- /dev/null +++ b/Resources/Documentation/Gem/pix_kaleidoscope.md @@ -0,0 +1,37 @@ +--- +title: kaleidoscope +description: kaleidoscope effect +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: number of segments (0-64) + 3rd: + - type: float + description: rotation of the input-segment in degrees + 4th: + - type: list + description: normalized x and y centre coordinates of the of the segment of the input image. (0-1) + 5th: + - type: float + description: rotation of the output-segment (in degrees) + 6th: + - type: list + description: normalized x and y centre coordinates of the of the segments in the output image. (0-1) + 7th: + - type: float + description: reflection line proportion, controls the relative sizes of each pair of adjacent segments in the output image (0-1) + 8th: + - type: float + description: source angle proportion (0.1-10) +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_levels.md b/Resources/Documentation/Gem/pix_levels.md new file mode 100644 index 0000000000..579a834343 --- /dev/null +++ b/Resources/Documentation/Gem/pix_levels.md @@ -0,0 +1,41 @@ +--- +title: pix_levels +description: level adjustment +categories: + - object +pdcategory: Graphics +methods: + - type: uni + description: choose between uniform adjustment(1) or per-channel-adjustment (0) + - type: inv + description: allow inversion of level-correction to avoid instabilities + - type: auto + description: turns automatic level-correction on/off +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: inFloor inCeiling outFloor outCeiling (for uniform adjustment) + 3rd: + - type: list + description: inFloor inCeiling outFloor outCeiling (for by-channel adjustment red) + 4th: + - type: list + description: inFloor inCeiling outFloor outCeiling (for by-channel adjustment green) + 5th: + - type: list + description: inFloor inCeiling outFloor outCeiling (for by-channel adjustment blue) + 6th: + - type: float + description: low percentile (in auto mode) + 7th: + - type: float + description: high percentile (in auto mode) +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_lumaoffset.md b/Resources/Documentation/Gem/pix_lumaoffset.md new file mode 100644 index 0000000000..cd4d0fd51d --- /dev/null +++ b/Resources/Documentation/Gem/pix_lumaoffset.md @@ -0,0 +1,27 @@ +--- +title: pix_lumaoffset +description: offset pixels depending on the luminance +categories: + - object +pdcategory: Graphics +methods: + - type: fill + description: draw filled lines + - type: smooth + description: do smooth fill +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: offset-factor + 3rd: + - type: float + description: spacing between lines +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_mask.md b/Resources/Documentation/Gem/pix_mask.md new file mode 100644 index 0000000000..d176b1d02a --- /dev/null +++ b/Resources/Documentation/Gem/pix_mask.md @@ -0,0 +1,18 @@ +--- +title: pix_mask +description: mask out a pix + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_mean_color.md b/Resources/Documentation/Gem/pix_mean_color.md new file mode 100644 index 0000000000..ec0391a38c --- /dev/null +++ b/Resources/Documentation/Gem/pix_mean_color.md @@ -0,0 +1,19 @@ +--- +title: pix_mean_color +description: get the mean color of the current image +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: the mean color of the image +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_metaimage.md b/Resources/Documentation/Gem/pix_metaimage.md new file mode 100644 index 0000000000..51fd639f18 --- /dev/null +++ b/Resources/Documentation/Gem/pix_metaimage.md @@ -0,0 +1,30 @@ +--- +title: pix_metaimage +description: display a pix by itself +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: size +methods: + - type: cheap + description: use cheap algorithm + - type: distance + description: use distance-based algorithm +inlets: + 1st: + - type: gemlist + description: + - type: float + description: apply/don't apply + 2nd: + - type: float + description: size +outlets: + 1st: + - type: gemlist + description: + +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_mix.md b/Resources/Documentation/Gem/pix_mix.md new file mode 100644 index 0000000000..2b18be779d --- /dev/null +++ b/Resources/Documentation/Gem/pix_mix.md @@ -0,0 +1,32 @@ +--- +title: pix_mix +description: mix 2 images based on mixing factors +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: left gain + default: 0.5 + - type: float + description: right gain + default: 0.5 + +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: gemlist + description: + 3rd: + - type: list + description: weights for left/right image + - type: float + description: weight for left image, right weight will be the reciprocal value +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_motionblur.md b/Resources/Documentation/Gem/pix_motionblur.md new file mode 100644 index 0000000000..af88b2c1b7 --- /dev/null +++ b/Resources/Documentation/Gem/pix_motionblur.md @@ -0,0 +1,23 @@ +--- +title: pix_motionblur +description: apply motion blurring on a series of images +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: blur-factor (0=no blurring, 1=only blurring) + +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: blur-factor (0..no blurring, 1..only blurring) +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_movement.md b/Resources/Documentation/Gem/pix_movement.md new file mode 100644 index 0000000000..5b88895b54 --- /dev/null +++ b/Resources/Documentation/Gem/pix_movement.md @@ -0,0 +1,22 @@ +--- +title: pix_movement +description: time-based IIR filter for images +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: change threshold (0-1) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: change threshold +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_movement2.md b/Resources/Documentation/Gem/pix_movement2.md new file mode 100644 index 0000000000..049639f580 --- /dev/null +++ b/Resources/Documentation/Gem/pix_movement2.md @@ -0,0 +1,30 @@ +--- +title: pix_movement2 +description: time-based IIR-filter for motion detection +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: low threshold (0-1) + - type: float + description: initial threshold (0-1) + +inlets: + 1st: + - type: gemlist + description: + - type: bang + description: set the current frame as "background" + 2nd: + - type: float + description: low threshold + 3rd: + - type: float + description: initial threshold +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_movie.md b/Resources/Documentation/Gem/pix_movie.md new file mode 100644 index 0000000000..b0abbe54e2 --- /dev/null +++ b/Resources/Documentation/Gem/pix_movie.md @@ -0,0 +1,29 @@ +--- +title: pix_movie +description: loads in a preproduced digital video to be used as a texture, bitblit, or something else +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: file to load initially +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: sets the frame number to output +outlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: the dimensions (in frames and pixels) of a film when it gets loaded + 3rd: + - type: bang + description: indicates that the last frame has been reached + +draft: true +--- diff --git a/Resources/Documentation/Gem/pix_multiblob.md b/Resources/Documentation/Gem/pix_multiblob.md new file mode 100644 index 0000000000..c18f8a8421 --- /dev/null +++ b/Resources/Documentation/Gem/pix_multiblob.md @@ -0,0 +1,27 @@ +--- +title: pix_multiblob +description: blob detector (for multiple blobs) +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: max number N of blobs to detect +methods: + - type: threshold + description: minimum luminance of a pixel to be considered part of a blob + - type: blobSize + description: minimum relative size of a blob +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: matrix describing k detected blobs +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_multiimage.md b/Resources/Documentation/Gem/pix_multiimage.md new file mode 100644 index 0000000000..9e43941ee2 --- /dev/null +++ b/Resources/Documentation/Gem/pix_multiimage.md @@ -0,0 +1,25 @@ +--- +title: pix_multiimage +description: loads in an image to be used as a texture, bitblit, or something else. +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: filename (with wildcard *) for images to load +methods: + - type: open + description: open images +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: select image (starting at 0) +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_multiply.md b/Resources/Documentation/Gem/pix_multiply.md new file mode 100644 index 0000000000..7f36f86273 --- /dev/null +++ b/Resources/Documentation/Gem/pix_multiply.md @@ -0,0 +1,19 @@ +--- +title: pix_multiply +description: multiply 2 images +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_multitexture.md b/Resources/Documentation/Gem/pix_multitexture.md new file mode 100644 index 0000000000..cd3b2a4568 --- /dev/null +++ b/Resources/Documentation/Gem/pix_multitexture.md @@ -0,0 +1,23 @@ +--- +title: pix_multitexture +description: apply multiple texture mappings to the current network +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: number of texture to use + +inlets: + 1st: + - type: gemlist + description: + nth: + - type: float + description: texture Id $nth +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_noise.md b/Resources/Documentation/Gem/pix_noise.md new file mode 100644 index 0000000000..55e7ea9d56 --- /dev/null +++ b/Resources/Documentation/Gem/pix_noise.md @@ -0,0 +1,36 @@ +--- +title: pix_noise +description: generate a noise image +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: width + - type: float + description: height +methods: + - type: bang + description: generate new image + - type: auto + description: generate new image for each frame on/off + - type: set + description: change size of the image + - type: seed + description: change the seed of the random function\ + - type: RGB + description: set colourspace + - type: RGBA + description: set colourspace + - type: GREY + description: set colourspace +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_normalize.md b/Resources/Documentation/Gem/pix_normalize.md new file mode 100644 index 0000000000..2d71f14173 --- /dev/null +++ b/Resources/Documentation/Gem/pix_normalize.md @@ -0,0 +1,19 @@ +--- +title: pix_normalize +description: normalize an image +categories: + - object +pdcategory: Graphics + +inlets: + 1st: + - type: gemlist + description: + - type: float + description: apply/don't apply (default:1) +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_offset.md b/Resources/Documentation/Gem/pix_offset.md new file mode 100644 index 0000000000..3b2a432808 --- /dev/null +++ b/Resources/Documentation/Gem/pix_offset.md @@ -0,0 +1,24 @@ +--- +title: pix_offset +description: add an offset to the color +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + - type: float + description: apply/don't apply + 2nd: + - type: float + description: offset for all channels + 3rd: + - type: list + description: offset for each channel +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_posterize.md b/Resources/Documentation/Gem/pix_posterize.md new file mode 100644 index 0000000000..175a3a875e --- /dev/null +++ b/Resources/Documentation/Gem/pix_posterize.md @@ -0,0 +1,25 @@ +--- +title: pix_posterize +description: posterization effect +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: factor +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: posterization factor + 3rd: + - type: float + description: limit mode +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_puzzle.md b/Resources/Documentation/Gem/pix_puzzle.md new file mode 100644 index 0000000000..0bd76b4a7a --- /dev/null +++ b/Resources/Documentation/Gem/pix_puzzle.md @@ -0,0 +1,25 @@ +--- +title: pix_puzzle +description: shuffle an image +categories: + - object +pdcategory: Graphics +methods: + - type: size + description: number of elements in x/y + - type: move + description: move the empty field +inlets: + 1st: + - type: gemlist + description: + - type: float + description: apply/don't apply + - type: bang + description: reshuffle +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_rds.md b/Resources/Documentation/Gem/pix_rds.md new file mode 100644 index 0000000000..2484988b26 --- /dev/null +++ b/Resources/Documentation/Gem/pix_rds.md @@ -0,0 +1,25 @@ +--- +title: pix_rds +description: random dot stereogram for luminance +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: stride distance + default: 40 +methods: + - type: method + description: crosseyed (0) or walleyed (1) + - type: stride + description: stride distance +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_record.md b/Resources/Documentation/Gem/pix_record.md new file mode 100644 index 0000000000..bed64ebaf0 --- /dev/null +++ b/Resources/Documentation/Gem/pix_record.md @@ -0,0 +1,40 @@ +--- +title: pix_record +description: write a sequence of pixes to a movie file +categories: + - object +pdcategory: Graphics +methods: + - type: file + description: specify the file for writing + - type: record + description: start/stop recording + - type: bang + description: grab the next incoming pix + - type: auto + description: start/stop grabbing all incoming pixes + - type: codeclist + description: codeclist: enumerate a list of available codecs to the outlet#3 + - type: codex + description: select codec + - type: codec + description: select codec by short name + - type: set + description: set a property named ar1 to the value arg2 + - type: dialog + description: popup a dialog to select the codec (if available) +inlets: + 1st: + - type: gemlist + description: + +outlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: number of frames written + 3rd: + - type: list + description: info on available codecs/properties diff --git a/Resources/Documentation/Gem/pix_rectangle.md b/Resources/Documentation/Gem/pix_rectangle.md new file mode 100644 index 0000000000..45107673e1 --- /dev/null +++ b/Resources/Documentation/Gem/pix_rectangle.md @@ -0,0 +1,22 @@ +--- +title: pix_rectangle +description: draw a rectangle into a pix +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: 4 floats defining the 4 corners of the rectangle + 3rd: + - type: list + description: 3 (RGB) or 4 (RGBA) float-values defining the color of the rectangle +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_refraction.md b/Resources/Documentation/Gem/pix_refraction.md new file mode 100644 index 0000000000..8942e9ddbe --- /dev/null +++ b/Resources/Documentation/Gem/pix_refraction.md @@ -0,0 +1,28 @@ +--- +title: pix_refraction +description: display a pix through glass bricks +categories: + - object +pdcategory: Graphics +methods: + - type: width + description: width of each cell + - type: height + description: height of each cell + - type: mag + description: + - type: refract + description: +inlets: + 1st: + - type: gemlist + description: + - type: float + description: apply/don't apply +outlets: + 1st: + - type: gemlist + description: + +draft: true +--- diff --git a/Resources/Documentation/Gem/pix_resize.md b/Resources/Documentation/Gem/pix_resize.md new file mode 100644 index 0000000000..77fc8eeaf7 --- /dev/null +++ b/Resources/Documentation/Gem/pix_resize.md @@ -0,0 +1,24 @@ +--- +title: pix_resize +description: resize an image +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: xsize + - type: float + description: ysize +methods: + - type: dimen + description: change the resize dimensions +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_rgb2hsv.md b/Resources/Documentation/Gem/pix_rgb2hsv.md new file mode 100644 index 0000000000..8a3ee09837 --- /dev/null +++ b/Resources/Documentation/Gem/pix_rgb2hsv.md @@ -0,0 +1,18 @@ +--- +title: pix_rgb2hsv +description: convert RGB into HSV +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + - type: float + description: apply/don't apply +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_rgba.md b/Resources/Documentation/Gem/pix_rgba.md new file mode 100644 index 0000000000..1894dd2956 --- /dev/null +++ b/Resources/Documentation/Gem/pix_rgba.md @@ -0,0 +1,18 @@ +--- +title: pix_rgba +description: convert the colorspace of an image to RGBA +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + - type: float + description: turn conversion on/off +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_roi.md b/Resources/Documentation/Gem/pix_roi.md new file mode 100644 index 0000000000..565441eeb3 --- /dev/null +++ b/Resources/Documentation/Gem/pix_roi.md @@ -0,0 +1,28 @@ +--- +title: pix_roi +description: set the region-of-interest of an image +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: x1 + - type: float + description: y1 + - type: float + description: x2 + - type: float + description: y2 +methods: + - type: roi + description: set roi +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_roll.md b/Resources/Documentation/Gem/pix_roll.md new file mode 100644 index 0000000000..92f1851583 --- /dev/null +++ b/Resources/Documentation/Gem/pix_roll.md @@ -0,0 +1,22 @@ +--- +title: pix_roll +description: (sc)roll through an image +categories: + - object +pdcategory: Graphics +methods: + - type: axis + description: scroll(0=default) or roll(1) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: (sc)roll number of pixels +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_rtx.md b/Resources/Documentation/Gem/pix_rtx.md new file mode 100644 index 0000000000..20babdaf8c --- /dev/null +++ b/Resources/Documentation/Gem/pix_rtx.md @@ -0,0 +1,22 @@ +--- +title: pix_rtx +description: RealTime vs. X transformation +categories: + - object +pdcategory: Graphics +arguments: null +methods: + - type: set + description: sets the whole internal buffer to the next incoming frame + - type: mode + description: clamp the discontinuity to the edge or not +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_scanline.md b/Resources/Documentation/Gem/pix_scanline.md new file mode 100644 index 0000000000..483e61cdfa --- /dev/null +++ b/Resources/Documentation/Gem/pix_scanline.md @@ -0,0 +1,23 @@ +--- +title: pix_scanline +description: scan lines of an image +categories: + - object +pdcategory: Graphics +arguments: null +methods: + - type: mode + description: duplicate or draw only +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: the number of lines to duplicate or the to skip between drawing +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_set.md b/Resources/Documentation/Gem/pix_set.md new file mode 100644 index 0000000000..6d619059f2 --- /dev/null +++ b/Resources/Documentation/Gem/pix_set.md @@ -0,0 +1,37 @@ +--- +title: pix_set +description: set the pixel-data of an image +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: width + - type: float + description: height +methods: + - type: RGB + description: set colourspace + - type: RGBA + description: set colourspace + - type: GREY + description: set colourspace + - type: fill + description: set the whole image with value (value could be G, RGB or RGBA) + - type: set + description: set the size of the image +inlets: + 1st: + - type: gemlist + description: + - type: bang + description: send image (to update texture, for example) + 2nd: + - type: list + description: interleaved image-data (R1 G1 B1 A1 R2 B2...) or (R1 B1 G1 R2 B2...) or (L1 L2 L3...) +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_share_read.md b/Resources/Documentation/Gem/pix_share_read.md new file mode 100644 index 0000000000..81a41c118a --- /dev/null +++ b/Resources/Documentation/Gem/pix_share_read.md @@ -0,0 +1,32 @@ +--- +title: pix_share_read +description: read pixels from a shared memory region +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: ID + - type: float + description: width + - type: float + description: height + - type: symbol + description: colourspace +methods: + - type: set + description: +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: error number + +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_share_write.md b/Resources/Documentation/Gem/pix_share_write.md new file mode 100644 index 0000000000..a81d2e940b --- /dev/null +++ b/Resources/Documentation/Gem/pix_share_write.md @@ -0,0 +1,31 @@ +--- +title: pix_share_write +description: write pixels to a shared memory region +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: ID + - type: float + description: width + - type: float + description: height + - type: symbol + description: colourspace +methods: + - type: set + description: +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: error number +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_snap.md b/Resources/Documentation/Gem/pix_snap.md new file mode 100644 index 0000000000..8260aeab90 --- /dev/null +++ b/Resources/Documentation/Gem/pix_snap.md @@ -0,0 +1,36 @@ +--- +title: pix_snap +description: take a screenshot and convert it to a pix +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: x offset, y offset, width, height +methods: + - type: snap + description: do grab + - type: type + description: set type (BYTE|FLOAT|DOUBLE) + - type: dimen + description: set offset (x, y) +inlets: + 1st: + - type: gemlist + description: + - type: bang + description: do grab + 2nd: + - type: list + description: x offset, y offset + 3rd: + - type: list + description: width, height +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_snap2tex.md b/Resources/Documentation/Gem/pix_snap2tex.md new file mode 100644 index 0000000000..1d3666ca39 --- /dev/null +++ b/Resources/Documentation/Gem/pix_snap2tex.md @@ -0,0 +1,38 @@ +--- +title: pix_snap2tex +description: take a screenshot and texture it +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: X offset + - type: float + description: Y offset + - type: float + description: width + - type: float + description: height +methods: + - type: quality + description: turn texture-resampling on/off +inlets: + 1st: + - type: gemlist + description: + - type: bang + description: do grab! + - type: float + description: turn texturing on/off + 2nd: + - type: list + description: X and Y offset + 3rd: + - type: list + description: width adn height +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_subtract.md b/Resources/Documentation/Gem/pix_subtract.md new file mode 100644 index 0000000000..06a1b28241 --- /dev/null +++ b/Resources/Documentation/Gem/pix_subtract.md @@ -0,0 +1,19 @@ +--- +title: pix_subtract +description: subtract 2 images +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_tIIR.md b/Resources/Documentation/Gem/pix_tIIR.md new file mode 100644 index 0000000000..ed79cabd44 --- /dev/null +++ b/Resources/Documentation/Gem/pix_tIIR.md @@ -0,0 +1,24 @@ +--- +title: pix_tIIR +description: timebased IIR-filter +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: IIR coefficients +methods: + - type: set + description: overwrites the filter-buffers with the next incoming image, zero clears buffer +inlets: + 1st: + - type: gemlist + description: + nth: + - type: float + description: $nth feedback-coefficient +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_takealpha.md b/Resources/Documentation/Gem/pix_takealpha.md new file mode 100644 index 0000000000..f9eba8de84 --- /dev/null +++ b/Resources/Documentation/Gem/pix_takealpha.md @@ -0,0 +1,21 @@ +--- +title: pix_takealpha +description: transfer the alpha-channel +categories: + - object +pdcategory: Graphics +arguments: null +methods: null +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_texture.md b/Resources/Documentation/Gem/pix_texture.md new file mode 100644 index 0000000000..e06a7cc403 --- /dev/null +++ b/Resources/Documentation/Gem/pix_texture.md @@ -0,0 +1,42 @@ +--- +title: pix_texture +description: apply texture mapping +categories: + - object +pdcategory: Graphics +methods: + - type: env + description: texture environment mode (0=GL_REPLACE, 1=GL_DECAL, 2=GL_BLEND, 3=GL_ADD, 4=GL_COMBINE, >4=GL_MODULATE) + - type: quality + description: 0=GL_NEAREST, 1=GL_LINEAR + - type: repeat + description: 0=CLAMP_TO_EDGE, 1=REPEAT + - type: rectangle + description: use rectangle-texturing if available + - type: client_storage + description: use client-storage if available + - type: env + description: texture environment mode + - type: texunit + description: useful only with shader, change texunit of the texture + - type: pbo + description: change pixel buffer object number + - type: yuv + description: use native YUV-mode if available +inlets: + 1st: + - type: gemlist + description: + - type: float + description: turn texturing on/off + +outlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: texture info + +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_threshold.md b/Resources/Documentation/Gem/pix_threshold.md new file mode 100644 index 0000000000..cc022436d4 --- /dev/null +++ b/Resources/Documentation/Gem/pix_threshold.md @@ -0,0 +1,24 @@ +--- +title: pix_threshold +description: apply a threshold to pixels +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: threshold (scalar/vector) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: threshold for all channels + 3rd: + - type: list + description: threshold (RGB) or (RGBA) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_threshold_bernsen.md b/Resources/Documentation/Gem/pix_threshold_bernsen.md new file mode 100644 index 0000000000..e934f3d2f4 --- /dev/null +++ b/Resources/Documentation/Gem/pix_threshold_bernsen.md @@ -0,0 +1,33 @@ +--- +title: pix_threshold_bernsen +description: apply dynamic thresholds to pixels for binarization +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: number of tiles in x-direction + default: 16 + - type: float + description: number of tiles in y-direction + default: 16 +methods: + - type: float + description: number of tiles + - type: float + description: contrast. if the (max-min) + description: decodes the current film into the specified colorspace, if supported by the backend. + - name: device + description: the number of the input device + - name: device + description: the file path to the input device + - name: dimen + description: set various dimensions for the image + - name: enumerate + description: list all devices to the console + - name: driver + description: switch between different drivers, e.g. v4l, ieee1394, etc. + - name: driver + description: switch between different drivers, e.g. v4l, v4l2, dv... + - name: dialog + description: open a dialog to configure the input device (depending on driver / Operating System) + - name: enumProps + description: interact with driver/device properties +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + 2nd: + - type: list + description: info + +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_write.md b/Resources/Documentation/Gem/pix_write.md new file mode 100644 index 0000000000..9363eb288f --- /dev/null +++ b/Resources/Documentation/Gem/pix_write.md @@ -0,0 +1,29 @@ +--- +title: pix_write +description: Make a snapshot of the frame-buffer and write it to a file +categories: + - object +pdcategory: Graphics + +methods: + - name: file + description: set type/quality (0 = TIFF, >0 = JPG) + - name: file + description: set basefilename, and type = TIFF + - name: file + description: set basefilename and type/quality +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: x and y offset + 3rd: + - type: list + description: width and height +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_writer.md b/Resources/Documentation/Gem/pix_writer.md new file mode 100644 index 0000000000..bd02609376 --- /dev/null +++ b/Resources/Documentation/Gem/pix_writer.md @@ -0,0 +1,24 @@ +--- +title: pix_writer +description: write the current texture to a file +categories: + - object +pdcategory: Graphics + +methods: + - name: file + description: set type/quality (0 = TIFF, >0 = JPG) + - name: file + description: set basefilename, and type = TIFF + - name: file + description: set basefilename and type/quality +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_yuv.md b/Resources/Documentation/Gem/pix_yuv.md new file mode 100644 index 0000000000..8050da118b --- /dev/null +++ b/Resources/Documentation/Gem/pix_yuv.md @@ -0,0 +1,18 @@ +--- +title: pix_yuv +description: convert the colorspace of an image to YUV +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + - type: float + description: turn conversion on/off +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/pix_zoom.md b/Resources/Documentation/Gem/pix_zoom.md new file mode 100644 index 0000000000..129116a97a --- /dev/null +++ b/Resources/Documentation/Gem/pix_zoom.md @@ -0,0 +1,20 @@ +--- +title: pix_zoom +description: zooms in on an image +categories: + - object +pdcategory: Graphics + +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: zoom x, zoom y +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/polygon.md b/Resources/Documentation/Gem/polygon.md new file mode 100644 index 0000000000..d97871487e --- /dev/null +++ b/Resources/Documentation/Gem/polygon.md @@ -0,0 +1,19 @@ +--- +title: polygon +description: Renders a polygon. +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: List of X Y Z points that define the polygon + 2nd and onwards: + - type: list + description: "3(XYZ) float values representing control points of the polygon" +outlets: + 1st: + - type: gemlist + description: Rendered polygon +draft: false +--- diff --git a/Resources/Documentation/Gem/polygon_smooth.md b/Resources/Documentation/Gem/polygon_smooth.md new file mode 100644 index 0000000000..100f34d0fb --- /dev/null +++ b/Resources/Documentation/Gem/polygon_smooth.md @@ -0,0 +1,17 @@ +--- +title: polygon_smooth +description: Turn on/off polygon smoothing (antialiasing). +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: + - type: float + description: turn polygon-smoothing on (1), off (0), or leave it unchanged (-1) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/pqtorusknots.md b/Resources/Documentation/Gem/pqtorusknots.md new file mode 100644 index 0000000000..5b054f8e6a --- /dev/null +++ b/Resources/Documentation/Gem/pqtorusknots.md @@ -0,0 +1,33 @@ +--- +title: pqtorusknot +description: renders a 3D knot +categories: + - object +pdcategory: Graphics +arguments: + - type: gemlist + description: +methods: + - type: draw [line|fill|points] + description: + - type: steps + description: + - type: facets + description: + - type: thick + description: + - type: clump + description: + - type: pq + description: + - type: ivScale + description: for texturing +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/primTri.md b/Resources/Documentation/Gem/primTri.md new file mode 100644 index 0000000000..0272ab9cbb --- /dev/null +++ b/Resources/Documentation/Gem/primTri.md @@ -0,0 +1,40 @@ +--- +title: primTri +description: renders a triangle with gradient colors +categories: + - object +pdcategory: Graphics +methods: + - type: symbol + description: draw [line|fill|point] + default: fill +methods: + - type: draw [line|fill|point] + description: +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: 3(XYZ) float values (position of 1st corner) + 3rd: + - type: list + description: 3(XYZ) float values (position of 2nd corner) + 4th: + - type: list + description: 3(XYZ) float values (position of 3rd corner) + 5th: + - type: list + description: 3(RGB) or 4(RGBA) float values (color of 1st corner) + 6th: + - type: list + description: 3(RGB) or 4(RGBA) float values (color of 2nd corner) + 7th: + - type: list + description: 3(RGB) or 4(RGBA) float values (color of 3rd corner) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/rectangle.md b/Resources/Documentation/Gem/rectangle.md new file mode 100644 index 0000000000..defa06ba33 --- /dev/null +++ b/Resources/Documentation/Gem/rectangle.md @@ -0,0 +1,34 @@ +--- +title: rectangle +description: renders a rectangle +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: draw [line|fill|point] + default: fill + - type: float + description: width (default to 1) + default: 1 + - type: float + description: height (default to 1) + default: 1 +methods: + - type: draw [line|fill|point] + description: +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: width (default to 1) + 3rd: + - type: float + description: height (default to 1) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/render_trigger.md b/Resources/Documentation/Gem/render_trigger.md new file mode 100644 index 0000000000..bf7c9fa140 --- /dev/null +++ b/Resources/Documentation/Gem/render_trigger.md @@ -0,0 +1,21 @@ +--- +title: render_trigger +description: triggers on rendering +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist + 2nd: + - type: bang + description: pre-render + 3rd: + - type: bang + description: post-render +draft: false +--- diff --git a/Resources/Documentation/Gem/rgb2hsv.md b/Resources/Documentation/Gem/rgb2hsv.md new file mode 100644 index 0000000000..073d2298da --- /dev/null +++ b/Resources/Documentation/Gem/rgb2hsv.md @@ -0,0 +1,16 @@ +--- +title: rgb2hsv +description: convert RGB color values to HSV +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: list + description: RGB values +outlets: + 1st: + - type: list + description: HSV values +draft: false +--- diff --git a/Resources/Documentation/Gem/rgb2yuv.md b/Resources/Documentation/Gem/rgb2yuv.md new file mode 100644 index 0000000000..a600d72a1b --- /dev/null +++ b/Resources/Documentation/Gem/rgb2yuv.md @@ -0,0 +1,16 @@ +--- +title: rgb2yuv +description: convert RGB color values to YUV +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: list + description: RGB values +outlets: + 1st: + - type: list + description: YUV values +draft: false +--- diff --git a/Resources/Documentation/Gem/ripple.md b/Resources/Documentation/Gem/ripple.md new file mode 100644 index 0000000000..d5a371086a --- /dev/null +++ b/Resources/Documentation/Gem/ripple.md @@ -0,0 +1,34 @@ +--- +title: ripple +description: renders and distorts a square +categories: + - object +pdcategory: Graphics +arguments: + - type: message + description: draw [line|fill|point] + default: fill +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: size + 3rd: + - type: bang + description: grab + 4th: + - type: float + description: posX (centered) + 5th: + - type: float + description: posY (centered) + 6th: + - type: float + description: amount +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/rotate.md b/Resources/Documentation/Gem/rotate.md new file mode 100644 index 0000000000..05f7b4831a --- /dev/null +++ b/Resources/Documentation/Gem/rotate.md @@ -0,0 +1,26 @@ +--- +title: rotate +description: rotation +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: rotation amount around axis (in deg) + - type: list + description: rotation axis (X Y Z) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: rotation amount around axis (in deg) + 3rd: + - type: list + description: rotation axis (X Y Z) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/rotateXYZ.md b/Resources/Documentation/Gem/rotateXYZ.md new file mode 100644 index 0000000000..e2079eb411 --- /dev/null +++ b/Resources/Documentation/Gem/rotateXYZ.md @@ -0,0 +1,31 @@ +--- +title: rotateXYZ +description: rotates a gemList by the specified rotation around the X-, Y-, and Z-axes +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: rotation amount around X-axis (in deg) + - type: float + description: rotation amount around Y-axis (in deg) + - type: float + description: rotation amount around Z-axis (in deg) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: rotation amount around X-axis (in deg) + 3rd: + - type: float + description: rotation amount around Y-axis (in deg) + 4th: + - type: float + description: rotation amount around Z-axis (in deg) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/rubber.md b/Resources/Documentation/Gem/rubber.md new file mode 100644 index 0000000000..3291ea9470 --- /dev/null +++ b/Resources/Documentation/Gem/rubber.md @@ -0,0 +1,41 @@ +--- +title: rubber +description: renders and distorts a square +categories: + - object +pdcategory: Graphics +arguments: + - type: bang + description: grab/release + - type: float + description: size + - type: float + description: height + - type: float + description: posX (centered) + - type: float + description: posY (centered) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: bang + description: grab/release + 3rd: + - type: float + description: size + 4th: + - type: float + description: height + 5th: + - type: float + description: posX (centered) + 6th: + - type: float + description: posY (centered) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/scale.md b/Resources/Documentation/Gem/scale.md new file mode 100644 index 0000000000..51b250b28c --- /dev/null +++ b/Resources/Documentation/Gem/scale.md @@ -0,0 +1,26 @@ +--- +title: scale +description: scales a gemList with specified scaling factors +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: initial scale amount + - type: list + description: scaling vector (X Y Z) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: scaling amount along the vector + 3rd: + - type: list + description: scaling vector (X Y Z) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/scaleXYZ.md b/Resources/Documentation/Gem/scaleXYZ.md new file mode 100644 index 0000000000..dabae7c74c --- /dev/null +++ b/Resources/Documentation/Gem/scaleXYZ.md @@ -0,0 +1,31 @@ +--- +title: scaleXYZ +description: scales a gemList along the X-, Y-, and Z-axes +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: scaling amount along the X-axis + - type: float + description: scaling amount along the Y-axis + - type: float + description: scaling amount along the Z-axis +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: scaling amount along the X-axis + 3rd: + - type: float + description: scaling amount along the Y-axis + 4th: + - type: float + description: scaling amount along the Z-axis +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/scopeXYZ~.md b/Resources/Documentation/Gem/scopeXYZ~.md new file mode 100644 index 0000000000..dfed7e6b99 --- /dev/null +++ b/Resources/Documentation/Gem/scopeXYZ~.md @@ -0,0 +1,35 @@ +--- +title: scopeXYZ~ +description: 3D oscilloscope +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: number of signal points to be stored + default: blocksize +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: signal + description: x-values of the oscilloscope + 3rd: + - type: signal + description: y-values of the oscilloscope + 4th: + - type: signal + description: z-values of the oscilloscope +methods: + - type: draw [default|line|linestrip|fill|point|tri|tristrip|trifan|quad|quadstrip] + description: + - type: width + description: line-width (default: 1) + - type: length + description: number of signal points to be stored +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/separator.md b/Resources/Documentation/Gem/separator.md new file mode 100644 index 0000000000..b6b641d3d1 --- /dev/null +++ b/Resources/Documentation/Gem/separator.md @@ -0,0 +1,19 @@ +--- +title: separator +description: separate render chains +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: list of matrices to work on + default: "m c t p" +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/shearXY.md b/Resources/Documentation/Gem/shearXY.md new file mode 100644 index 0000000000..05fe49639f --- /dev/null +++ b/Resources/Documentation/Gem/shearXY.md @@ -0,0 +1,22 @@ +--- +title: shearXY +description: shear. +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: shear factor (XY) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: XY shear factor +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/shearXZ.md b/Resources/Documentation/Gem/shearXZ.md new file mode 100644 index 0000000000..4e64e5507d --- /dev/null +++ b/Resources/Documentation/Gem/shearXZ.md @@ -0,0 +1,22 @@ +--- +title: shearXZ +description: shear. +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: shear factor (XZ) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: XZ shear factor +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/shearYX.md b/Resources/Documentation/Gem/shearYX.md new file mode 100644 index 0000000000..2d288b8b38 --- /dev/null +++ b/Resources/Documentation/Gem/shearYX.md @@ -0,0 +1,22 @@ +--- +title: shearYX +description: shear. +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: shear factor (YX) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: YX shear factor +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/shearYZ.md b/Resources/Documentation/Gem/shearYZ.md new file mode 100644 index 0000000000..79777eadbc --- /dev/null +++ b/Resources/Documentation/Gem/shearYZ.md @@ -0,0 +1,22 @@ +--- +title: shearYZ +description: shear. +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: shear factor (YZ) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: YZ shear factor +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/shearZX.md b/Resources/Documentation/Gem/shearZX.md new file mode 100644 index 0000000000..dd94dd9e40 --- /dev/null +++ b/Resources/Documentation/Gem/shearZX.md @@ -0,0 +1,22 @@ +--- +title: shearZX +description: shear. +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: shear factor (ZX) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: ZX shear factor +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/shearZY.md b/Resources/Documentation/Gem/shearZY.md new file mode 100644 index 0000000000..6eb3063664 --- /dev/null +++ b/Resources/Documentation/Gem/shearZY.md @@ -0,0 +1,22 @@ +--- +title: shearZY +description: shear. +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: shear factor (ZY) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: ZY shear factor +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/shininess.md b/Resources/Documentation/Gem/shininess.md new file mode 100644 index 0000000000..93583cbc0f --- /dev/null +++ b/Resources/Documentation/Gem/shininess.md @@ -0,0 +1,23 @@ +--- +title: shininess +description: aets the shininess of the material +categories: +- object +pdcategory: Graphics +arguments: +- type: float + description: Shininess value (0 - 128) + default: 0 +inlets: +1st: + - type: gemlist + description: gemlist +2nd: + - type: float + description: Shininess value (0 - 128) +outlets: +1st: + - type: gemlist + description: gemlist +draft: false +--- \ No newline at end of file diff --git a/Resources/Documentation/Gem/slideSquares.md b/Resources/Documentation/Gem/slideSquares.md new file mode 100644 index 0000000000..50ab138644 --- /dev/null +++ b/Resources/Documentation/Gem/slideSquares.md @@ -0,0 +1,31 @@ +--- +title: slideSquares +description: renders sliding rectangles +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: draw [line|fill|point] + default: fill + - type: float + description: width + default: 1 + - type: float + description: height + default: 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: width + 3rd: + - type: float + description: height +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/specular.md b/Resources/Documentation/Gem/specular.md new file mode 100644 index 0000000000..ac6dce8906 --- /dev/null +++ b/Resources/Documentation/Gem/specular.md @@ -0,0 +1,22 @@ +--- +title: specular +description: specular coloring +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: a list of 3 (RGB) or 4 (RGBA) float-values + default: 0 0 0 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: list + description: 3(RGB) or 4(RGBA) float values +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/specularRGB.md b/Resources/Documentation/Gem/specularRGB.md new file mode 100644 index 0000000000..ef776fd51c --- /dev/null +++ b/Resources/Documentation/Gem/specularRGB.md @@ -0,0 +1,31 @@ +--- +title: specularRGB +description: specular coloring with individual RGB values +categories: + - object +pdcategory: Graphics +arguments: + - type: list + description: a list of 3 (RGB) or 4 (RGBA) float-values + default: 0 0 0 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: red value + 3rd: + - type: float + description: green value + 4th: + - type: float + description: blue value + 5th: + - type: float + description: alpha value +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/sphere.md b/Resources/Documentation/Gem/sphere.md new file mode 100644 index 0000000000..e151afee6b --- /dev/null +++ b/Resources/Documentation/Gem/sphere.md @@ -0,0 +1,27 @@ +--- +title: sphere +description: renders a segmented sphere. +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: draw [line|fill|point] + - type: float + description: size of the sphere +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: size + 3rd: + - type: float + description: number of segments +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/sphere3d.md b/Resources/Documentation/Gem/sphere3d.md new file mode 100644 index 0000000000..e17e90df2c --- /dev/null +++ b/Resources/Documentation/Gem/sphere3d.md @@ -0,0 +1,34 @@ +--- +title: sphere3d +description: renders a segmented sphere3d. +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: draw [line|fill|point] + - type: float + description: size of the sphere3d +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: size + 3rd: + - type: float + description: number of segments +outlets: + 1st: + - type: gemlist + description: +methods: + - type: setCartesian + description: Set Cartesian coordinates for a specific point. + - type: setSpherical + description: Set Spherical coordinates for a specific point (in degrees). + - type: print + description: Print information. +draft: false +--- diff --git a/Resources/Documentation/Gem/spline_path.md b/Resources/Documentation/Gem/spline_path.md new file mode 100644 index 0000000000..c6af8e7fb0 --- /dev/null +++ b/Resources/Documentation/Gem/spline_path.md @@ -0,0 +1,24 @@ +--- +title: spline_path +description: reads out a table +categories: + - object +pdcategory: Graphics, Data Structures +arguments: + - type: float + description: reading point (0.0 to 1.0) + - type: float + description: dimensions of the table + default: 1 + - type: symbol + description: name of the table +inlets: + 1st: + - type: float + description: reading point (0.0 to 1.0) +outlets: + 1st: + - type: list of floats + description: a list of floats +draft: false +--- diff --git a/Resources/Documentation/Gem/spot_light.md b/Resources/Documentation/Gem/spot_light.md new file mode 100644 index 0000000000..ccc30df026 --- /dev/null +++ b/Resources/Documentation/Gem/spot_light.md @@ -0,0 +1,32 @@ +--- +title: spot_light +description: Adds a spot-light to the scene +categories: +- object +pdcategory: Graphics +arguments: +- type: float + description: Turn light on/off + default: 1 +- type: debug + description: Show debugging object +- type: list + description: Set the color of the light-source +- type: list + description: Set the attenuation parameters +inlets: + 1st: + - type: float + description: Turn light on/off + 2nd: + - type: list + description: Set the color of the light-source + 3rd: + - type: list + description: Set the attenuation parameters +outlets: + 1st: + - type: gemlist + description: None +draft: false +--- \ No newline at end of file diff --git a/Resources/Documentation/Gem/square.md b/Resources/Documentation/Gem/square.md new file mode 100644 index 0000000000..63a80648dc --- /dev/null +++ b/Resources/Documentation/Gem/square.md @@ -0,0 +1,23 @@ +--- +title: square +description: renders a square. +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: draw [line|fill|point] + - type: float + description: size of the square +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: size +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/surface3d.md b/Resources/Documentation/Gem/surface3d.md new file mode 100644 index 0000000000..628ef3f10f --- /dev/null +++ b/Resources/Documentation/Gem/surface3d.md @@ -0,0 +1,35 @@ +--- +title: surface3d +description: Renders a 3d bicubic curve. +categories: +- object +pdcategory: Graphics +arguments: +- type: list + description: size of the control matrix + default: 4 4 +methods: +- type: res + description: Change the subdivision of the displayed curve. +- type: grid + description: Change the size of the control matrix. +- type: set + description: Set the position of a control point. +- type: normal + description: Enable/disable normals calculation +- type: draw + description: line, fill or point + +inlets: +- 1st: + - type: gemlist + description: + 2nd: + - type: unused + description: unused + +outlets: +- 1st: + - type: gemlist +draft: false +--- \ No newline at end of file diff --git a/Resources/Documentation/Gem/teapot.md b/Resources/Documentation/Gem/teapot.md new file mode 100644 index 0000000000..4c26ecf522 --- /dev/null +++ b/Resources/Documentation/Gem/teapot.md @@ -0,0 +1,40 @@ +--- +title: teapot + +description: renders a teapot + +categories: + - graphics + +pdcategory: Graphics + +arguments: +- type: float + description: size of the teapot + default: 1 +- type: float + description: number of slices + default: 14 + +methods: +- type: draw + description: 'fill', 'line' or 'points' + +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: teapot size + 3rd: + - type: float + description: number of slices + +outlets: + 1st: + - type: gemlist + description: + +draft: false +--- \ No newline at end of file diff --git a/Resources/Documentation/Gem/text2d.md b/Resources/Documentation/Gem/text2d.md new file mode 100644 index 0000000000..7a3e684041 --- /dev/null +++ b/Resources/Documentation/Gem/text2d.md @@ -0,0 +1,30 @@ +--- +title: text2d +description: renders a line of text without 3D transformations. +categories: + - object +pdcategory: Graphics +arguments: + - type: + description: initial text (defaults to "gem") +methods: + - type: font + description: Load a TrueType-font. + - type: text + description: Render the given text. + - type: list + description: Render the given text. + - type: alias + description: Anti-aliasing on/off (default: 1). +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: size (in points) (default: 20) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/text3d.md b/Resources/Documentation/Gem/text3d.md new file mode 100644 index 0000000000..5d12c4918e --- /dev/null +++ b/Resources/Documentation/Gem/text3d.md @@ -0,0 +1,36 @@ +--- +title: text3d +description: renders a line of text with 3D transformations. +categories: + - object +pdcategory: Graphics +arguments: + - type: + description: initial text (defaults to "gem") +methods: + - type: font + description: load a TrueType-font + - type: text + description: render the given text + - type: list + description: render the given text + - type: justify [] + description: horizontal & vertical justification + - type: string + description: render the given text as a list of unicode code points (similar to ASCII) + - type: size + description: Set the size in points (default: 20) + - type: alias 1|0 + description: Anti-aliasing on/off (default: 1) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: + description: size (in points) (default: 20) +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/textoutline.md b/Resources/Documentation/Gem/textoutline.md new file mode 100644 index 0000000000..83a3cb84a0 --- /dev/null +++ b/Resources/Documentation/Gem/textoutline.md @@ -0,0 +1,33 @@ +--- +title: textoutline +description: renders a line of outlined text with 3D transformations +categories: + - object +pdcategory: Graphics +arguments: + - type: + description: initial text + default: "gem" +methods: + - type: font + description: load a TrueType-font + - type: text + description: render the given text + - type: list + description: render the given text + - type: justify + description: horizontal & vertical justification + - type: size + description: set the size in points +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: + description: size in points +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/todo/Gem.md b/Resources/Documentation/Gem/todo/Gem.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Resources/Documentation/Gem/todo/camera.md b/Resources/Documentation/Gem/todo/camera.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Resources/Documentation/Gem/todo/pix_depot.md b/Resources/Documentation/Gem/todo/pix_depot.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Resources/Documentation/Gem/todo/pix_emboss.md b/Resources/Documentation/Gem/todo/pix_emboss.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Resources/Documentation/Gem/todo/pix_get.md b/Resources/Documentation/Gem/todo/pix_get.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Resources/Documentation/Gem/todo/pix_put.md b/Resources/Documentation/Gem/todo/pix_put.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Resources/Documentation/Gem/todo/pix_separator.md b/Resources/Documentation/Gem/todo/pix_separator.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Resources/Documentation/Gem/todo/pix_test.md b/Resources/Documentation/Gem/todo/pix_test.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Resources/Documentation/Gem/todo/pix_vpaint.md b/Resources/Documentation/Gem/todo/pix_vpaint.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Resources/Documentation/Gem/todo/vertex_info.md b/Resources/Documentation/Gem/todo/vertex_info.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Resources/Documentation/Gem/torus.md b/Resources/Documentation/Gem/torus.md new file mode 100644 index 0000000000..927b3a0d16 --- /dev/null +++ b/Resources/Documentation/Gem/torus.md @@ -0,0 +1,46 @@ +--- +title: torus + +description: renders a torus + +categories: + - graphics + +pdcategory: Graphics + +arguments: +- type: float + description: size of the torus + default: 1 +- type: float + description: number of slices + default: 14 +- type: float + description: thickness + default: 1 + +methods: +- type: draw + description: 'fill', 'line' or 'points' + +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: torus size + 3rd: + - type: float + description: number of slices + 4th: + - type: float + description: torus thickness + +outlets: + 1st: + - type: gemlist + description: + +draft: false +--- \ No newline at end of file diff --git a/Resources/Documentation/Gem/translate.md b/Resources/Documentation/Gem/translate.md new file mode 100644 index 0000000000..2f58a21be3 --- /dev/null +++ b/Resources/Documentation/Gem/translate.md @@ -0,0 +1,27 @@ +--- +title: translate +description: graphics translation +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: 1st argument: translation scale + - type: list + description: 2nd-4th argument: translation-axis (XYZ) +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: translation scale + 3rd: + - type: list + description: translation axis (X Y Z) +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/translateXYZ.md b/Resources/Documentation/Gem/translateXYZ.md new file mode 100644 index 0000000000..e1904e6903 --- /dev/null +++ b/Resources/Documentation/Gem/translateXYZ.md @@ -0,0 +1,33 @@ +--- +title: translateXYZ +description: graphics translation +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: translation along X-axis + - type: float + description: translation along Y-axis + - type: float + description: translation along Z-axis +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: translation along X-axis + 3rd: + - type: float + description: translation along Y-axis + 4th: + - type: float + description: translation along Z-axis +outlets: + 1st: + - type: gemlist + description: + +draft: false +--- diff --git a/Resources/Documentation/Gem/trapezoid.md b/Resources/Documentation/Gem/trapezoid.md new file mode 100644 index 0000000000..e0be754407 --- /dev/null +++ b/Resources/Documentation/Gem/trapezoid.md @@ -0,0 +1,26 @@ +--- +title: trapezoid +description: renders a trapezoid box +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: dimensions of the trapezoid (size topline) + default: 1 1 +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: size + 3rd: + - type: float + description: length of top line, relative to the size +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/triangle.md b/Resources/Documentation/Gem/triangle.md new file mode 100644 index 0000000000..5eb9cc1679 --- /dev/null +++ b/Resources/Documentation/Gem/triangle.md @@ -0,0 +1,22 @@ +--- +title: triangle +description: renders an isosceles triangle. +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: size of the triangle +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: size +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/tube.md b/Resources/Documentation/Gem/tube.md new file mode 100644 index 0000000000..914c6563fa --- /dev/null +++ b/Resources/Documentation/Gem/tube.md @@ -0,0 +1,71 @@ +--- +title: tube + +description: renders a tube + +categories: + - object + +pdcategory: ELSE, MIDI + +arguments: +- type: float + description: 1st circle diameter + default: 1 +- type: float + description: 2nd circle diameter + default: 1 +- type: float + description: height of the tube + default: 1 +- type: float + description: number of segments + default: 14 + +methods: +- type: draw + description: 'fill', 'line' or 'points' +- type: width + description: set line width +- type: numslices + description: set number of slices + +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: float + description: 1st circle diameter + 3rd: + - type: float + description: 2nd circle diameter + 4th: + - type: float + description: height of the tube + 5th: + - type: float + description: X translation of 1st circle + 6th: + - type: float + description: Y translation of 1st circle + 7th: + - type: float + description: X rotation of 1st circle + 8th: + - type: float + description: Y rotation of 1st circle + 9th: + - type: float + description: X rotation of 2nd circle + 10th: + - type: float + description: Y rotation of 2nd circle + +outlets: + 1st: + - type: gemlist + description: MIDI aftertouch + +draft: false +--- diff --git a/Resources/Documentation/Gem/vertex_add.md b/Resources/Documentation/Gem/vertex_add.md new file mode 100644 index 0000000000..f222c78237 --- /dev/null +++ b/Resources/Documentation/Gem/vertex_add.md @@ -0,0 +1,25 @@ + +--- +title: vertex_add +description: add vertices +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: v, c, t, or n + - type: symbol + description: v, c, t, or n +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/vertex_combine.md b/Resources/Documentation/Gem/vertex_combine.md new file mode 100644 index 0000000000..eb6751e7cb --- /dev/null +++ b/Resources/Documentation/Gem/vertex_combine.md @@ -0,0 +1,21 @@ + +--- +title: vertex_combine +description: combine two matrices +categories: + - object +pdcategory: Graphics +arguments: +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/vertex_draw.md b/Resources/Documentation/Gem/vertex_draw.md new file mode 100644 index 0000000000..134665de7e --- /dev/null +++ b/Resources/Documentation/Gem/vertex_draw.md @@ -0,0 +1,18 @@ + +--- +title: vertex_draw +description: draw a vertex array +categories: + - object +pdcategory: Graphics +arguments: +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/vertex_grid.md b/Resources/Documentation/Gem/vertex_grid.md new file mode 100644 index 0000000000..421091d7b1 --- /dev/null +++ b/Resources/Documentation/Gem/vertex_grid.md @@ -0,0 +1,21 @@ +--- +title: vertex_grid +description: create a grid of vertices +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: x + - type: float + description: y +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/vertex_mul.md b/Resources/Documentation/Gem/vertex_mul.md new file mode 100644 index 0000000000..7b7a696ee7 --- /dev/null +++ b/Resources/Documentation/Gem/vertex_mul.md @@ -0,0 +1,25 @@ + +--- +title: vertex_mul +description: multiply vertices +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: v, c, t, or n + - type: symbol + description: v, c, t, or n +inlets: + 1st: + - type: gemlist + description: + 2nd: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/vertex_offset.md b/Resources/Documentation/Gem/vertex_offset.md new file mode 100644 index 0000000000..1ab3a1a0e9 --- /dev/null +++ b/Resources/Documentation/Gem/vertex_offset.md @@ -0,0 +1,24 @@ +--- +title: vertex_offset +description: offset vertices +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: v, c, t, or n + - type: symbol + description: v, c, t, or n +inlets: + 1st: + - type: gemlist + description: vertex in + 2nd: + - type: gemlist + description: param in +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/vertex_program.md b/Resources/Documentation/Gem/vertex_program.md new file mode 100644 index 0000000000..8fe1f52cd1 --- /dev/null +++ b/Resources/Documentation/Gem/vertex_program.md @@ -0,0 +1,18 @@ +--- +title: vertex_program +description: loads and applies an ARB (or NV) vertex shader +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +methods: + - type: open + description: vertex shader program to load +draft: false +--- diff --git a/Resources/Documentation/Gem/vertex_quad.md b/Resources/Documentation/Gem/vertex_quad.md new file mode 100644 index 0000000000..f0685dded3 --- /dev/null +++ b/Resources/Documentation/Gem/vertex_quad.md @@ -0,0 +1,18 @@ + +--- +title: vertex_quad +description: draw a quad with texture coordinates +categories: + - object +pdcategory: Graphics +arguments: +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/vertex_scale.md b/Resources/Documentation/Gem/vertex_scale.md new file mode 100644 index 0000000000..8cc827d194 --- /dev/null +++ b/Resources/Documentation/Gem/vertex_scale.md @@ -0,0 +1,24 @@ +--- +title: vertex_scale +description: scale vertices +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: v, c, t, or n + - type: symbol + description: v, c, t, or n +inlets: + 1st: + - type: gemlist + description: vertex in + 2nd: + - type: gemlist + description: param in +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/vertex_set.md b/Resources/Documentation/Gem/vertex_set.md new file mode 100644 index 0000000000..5ac4fc7198 --- /dev/null +++ b/Resources/Documentation/Gem/vertex_set.md @@ -0,0 +1,24 @@ +--- +title: vertex_set +description: set vertices +categories: + - object +pdcategory: Graphics +arguments: + - type: symbol + description: v, c, t, or n + - type: symbol + description: v, c, t, or n +inlets: + 1st: + - type: gemlist + description: vertex in + 2nd: + - type: gemlist + description: param in +outlets: + 1st: + - type: gemlist +draft: false +--- + diff --git a/Resources/Documentation/Gem/vertex_tabread.md b/Resources/Documentation/Gem/vertex_tabread.md new file mode 100644 index 0000000000..53f1395967 --- /dev/null +++ b/Resources/Documentation/Gem/vertex_tabread.md @@ -0,0 +1,24 @@ +--- +title: vertex_tabread +description: read from tables to set vertices +categories: + - object +pdcategory: Graphics +arguments: + - type: table + description: table V + - type: table + description: table C + - type: table + description: table N + - type: table + description: table T +inlets: + 1st: + - type: gemlist + description: +outlets: + 1st: + - type: gemlist +draft: false +--- diff --git a/Resources/Documentation/Gem/world_light.md b/Resources/Documentation/Gem/world_light.md new file mode 100644 index 0000000000..25ea791bfb --- /dev/null +++ b/Resources/Documentation/Gem/world_light.md @@ -0,0 +1,22 @@ +--- +title: world_light +description: adds a point-light to the scene. +categories: + - object +pdcategory: Graphics +arguments: + - type: float + description: turn light on/off (default: 1) +inlets: + 1st: + - type: gemlist + description: debug 1|0: show debugging object (default: 0), f 66 + 2nd: + - type: list + description: 3(RGB) or 4(RGBA) float values +outlets: + 1st: + - type: gemlist + description: +draft: false +--- diff --git a/Resources/Documentation/Gem/yuv2rgb.md b/Resources/Documentation/Gem/yuv2rgb.md new file mode 100644 index 0000000000..5915956aeb --- /dev/null +++ b/Resources/Documentation/Gem/yuv2rgb.md @@ -0,0 +1,16 @@ +--- +title: yuv2rgb +description: convert YUV color values to RGB +categories: + - object +pdcategory: Graphics +inlets: + 1st: + - type: list + description: YUV values +outlets: + 1st: + - type: list + description: RGB values +draft: false +--- From 6ba32ab98b214937e9dfc5d8c7ad3882a97b650f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Jan 2024 16:20:38 +0100 Subject: [PATCH 0112/1030] Improved GUI support for Gem --- Source/Connection.cpp | 16 +++++++++++++++- Source/Connection.h | 3 ++- Source/Constants.h | 1 + Source/Iolet.cpp | 6 +++++- Source/Iolet.h | 1 + Source/LookAndFeel.cpp | 14 +++++++------- Source/LookAndFeel.h | 1 + Source/Object.cpp | 15 +++++++++++---- Source/Pd/Library.cpp | 2 +- Source/Pd/Library.h | 1 - 10 files changed, 44 insertions(+), 16 deletions(-) diff --git a/Source/Connection.cpp b/Source/Connection.cpp index 7c4b408e22..c9fb69a288 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -274,6 +274,7 @@ void Connection::renderConnectionPath(Graphics& g, Canvas* cnv, Path const& connectionPath, bool isSignal, + bool isGemState, bool isMouseOver, bool showDirection, bool showConnectionOrder, @@ -287,12 +288,24 @@ void Connection::renderConnectionPath(Graphics& g, auto baseColour = cnv->findColour(PlugDataColour::connectionColourId); auto dataColour = cnv->findColour(PlugDataColour::dataColourId); auto signalColour = cnv->findColour(PlugDataColour::signalColourId); + auto gemColour = cnv->findColour(PlugDataColour::gemColourId); auto handleColour = isSignal ? dataColour : signalColour; auto connectionLength = connectionPath.getLength(); if (isSelected) { - baseColour = isSignal ? signalColour : dataColour; + if(isSignal) + { + baseColour = signalColour; + } + else if(isGemState) + { + baseColour = gemColour; + } + else { + baseColour = dataColour; + } + } else if (isMouseOver) { baseColour = isSignal ? signalColour : dataColour; baseColour = baseColour.brighter(0.6f); @@ -416,6 +429,7 @@ void Connection::paint(Graphics& g) cnv, toDrawLocalSpace, outlet != nullptr && outlet->isSignal, + outlet != nullptr && outlet->isGemState, isMouseOver(), showDirection, showConnectionOrder, diff --git a/Source/Connection.h b/Source/Connection.h index 44bd0f2545..3bbcaca66b 100644 --- a/Source/Connection.h +++ b/Source/Connection.h @@ -49,6 +49,7 @@ class Connection : public Component Canvas* cnv, Path const& connectionPath, bool isSignal, + bool isGemState, bool isMouseOver = false, bool showDirection = false, bool showConnectionOrder = false, @@ -226,7 +227,7 @@ class ConnectionBeingCreated : public Component { jassertfalse; // shouldn't happen return; } - Connection::renderConnectionPath(g, (Canvas*)cnv, connectionPath, iolet->isSignal, true); + Connection::renderConnectionPath(g, (Canvas*)cnv, connectionPath, iolet->isSignal, iolet->isGemState, true); } Iolet* getIolet() diff --git a/Source/Constants.h b/Source/Constants.h index 59bd9eb1f9..9355c3b946 100644 --- a/Source/Constants.h +++ b/Source/Constants.h @@ -270,6 +270,7 @@ enum PlugDataColour { dataColourId, connectionColourId, signalColourId, + gemColourId, dialogBackgroundColourId, diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index bc2f034475..e530018439 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -86,7 +86,11 @@ void Iolet::paint(Graphics& g) } auto backgroundColour = isSignal ? findColour(PlugDataColour::signalColourId) : findColour(PlugDataColour::dataColourId); - + if(isGemState) + { + backgroundColour = findColour(PlugDataColour::gemColourId); + } + if ((down || over) && !isLocked) backgroundColour = backgroundColour.contrasting(down ? 0.2f : 0.05f); diff --git a/Source/Iolet.h b/Source/Iolet.h index 6a44cb43cd..84464fae72 100644 --- a/Source/Iolet.h +++ b/Source/Iolet.h @@ -44,6 +44,7 @@ class Iolet : public Component int ioletIdx; bool isInlet; bool isSignal; + bool isGemState; bool isTargeted = false; diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index 5643f8e3c1..6fb9987f8e 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -1481,7 +1481,7 @@ const String PlugDataLook::defaultThemesXml = " " " default_object_background=\"ff333333\" object_outline_colour=\"ff696969\"\n" " selected_object_outline_colour=\"ff72aedf\" gui_internal_outline_colour=\"ff696969\"\n" " toolbar_outline_colour=\"ff393939\" outline_colour=\"ff393939\" data_colour=\"ff72aedf\"\n" -" connection_colour=\"ffb3b3b3\" signal_colour=\"ffe1ef00\" dialog_background=\"ff3b3b3b\"\n" +" connection_colour=\"ffb3b3b3\" signal_colour=\"ffe1ef00\" gem_colour=\"ff02f503\" dialog_background=\"ff3b3b3b\"\n" " sidebar_colour=\"ff3e3e3e\" sidebar_text=\"ffe4e4e4\" sidebar_background_active=\"ff4f4f4f\"\n" " sidebar_active_text=\"ffe4e4e4\" levelmeter_active=\"ff72aedf\" levelmeter_background=\"ff494949\"\n" " levelmeter_thumb=\"ffe4e4e4\" panel_background=\"ff4f4f4f\" panel_foreground=\"ff3f3f3f\"\n" @@ -1501,7 +1501,7 @@ const String PlugDataLook::defaultThemesXml = " " " object_outline_colour=\"ff000000\" selected_object_outline_colour=\"ff000000\"\n" " gui_internal_outline_colour=\"ff000000\" toolbar_outline_colour=\"ff000000\"\n" " outline_colour=\"ff000000\" iolet_area_colour=\"ffffffff\" iolet_outline_colour=\"ff000000\"\n" -" data_colour=\"ff000000\" connection_colour=\"ff000000\" signal_colour=\"ff000000\"\n" +" data_colour=\"ff000000\" connection_colour=\"ff000000\" signal_colour=\"ff000000\" gem_colour=\"ff02f503\"\n" " dialog_background=\"ffffffff\" sidebar_colour=\"ffefefef\" sidebar_text=\"ff000000\"\n" " sidebar_background_active=\"ffa0a0a0\" sidebar_active_text=\"ff000000\"\n" " levelmeter_active=\"ff000000\" levelmeter_background=\"ffededed\"\n" @@ -1520,7 +1520,7 @@ const String PlugDataLook::defaultThemesXml = " " " default_object_background=\"ff000000\" object_outline_colour=\"ffffffff\"\n" " selected_object_outline_colour=\"ffffffff\" gui_internal_outline_colour=\"ffffffff\"\n" " toolbar_outline_colour=\"ffffffff\" outline_colour=\"ffffffff\" data_colour=\"ffffffff\"\n" -" connection_colour=\"ffffffff\" signal_colour=\"ffffffff\" dialog_background=\"ff000000\"\n" +" connection_colour=\"ffffffff\" signal_colour=\"ffffffff\" gem_colour=\"ff02f503\" dialog_background=\"ff000000\"\n" " sidebar_colour=\"ff000000\" sidebar_text=\"ffffffff\" sidebar_background_active=\"ffa0a0a0\"\n" " sidebar_active_text=\"ffffffff\" levelmeter_active=\"ffffffff\" levelmeter_background=\"ff808080\"\n" " levelmeter_thumb=\"ffffffff\" panel_background=\"ff0e0e0e\" panel_foreground=\"ff000000\"\n" @@ -1539,7 +1539,7 @@ const String PlugDataLook::defaultThemesXml = " " " default_object_background=\"ff191919\" object_outline_colour=\"ff696969\"\n" " selected_object_outline_colour=\"ff42a2c8\" gui_internal_outline_colour=\"ff696969\"\n" " toolbar_outline_colour=\"ff2f2f2f\" outline_colour=\"ff393939\" data_colour=\"ff42a2c8\"\n" -" connection_colour=\"ffe1e1e1\" signal_colour=\"ffff8500\" dialog_background=\"ff191919\"\n" +" connection_colour=\"ffe1e1e1\" signal_colour=\"ffff8500\" gem_colour=\"ff02f503\" dialog_background=\"ff191919\"\n" " sidebar_colour=\"ff191919\" sidebar_text=\"ffe1e1e1\" sidebar_background_active=\"ff282828\"\n" " sidebar_active_text=\"ffe1e1e1\" levelmeter_active=\"ff42a2c8\" levelmeter_background=\"ff2e2e2e\"\n" " levelmeter_thumb=\"ffe3e3e3\" panel_background=\"ff2c2c2c\" panel_foreground=\"ff1f1f1f\"\n" @@ -1558,7 +1558,7 @@ const String PlugDataLook::defaultThemesXml = " " " default_object_background=\"ffe4e4e4\" object_outline_colour=\"ffb7b7b7\"\n" " selected_object_outline_colour=\"ff007aff\" gui_internal_outline_colour=\"ffb7b7b7\"\n" " toolbar_outline_colour=\"ffdfdfdf\" outline_colour=\"ffd0d0d0\" data_colour=\"ff007aff\"\n" -" connection_colour=\"ffb3b3b3\" signal_colour=\"ffff8500\" dialog_background=\"ffebebeb\"\n" +" connection_colour=\"ffb3b3b3\" signal_colour=\"ffff8500\" gem_colour=\"ff02f503\" dialog_background=\"ffebebeb\"\n" " sidebar_colour=\"ffefefef\" sidebar_text=\"ff373737\" sidebar_background_active=\"ffe4e4e4\"\n" " sidebar_active_text=\"ff373737\" levelmeter_active=\"ff007aff\" levelmeter_background=\"ffe1e1e1\"\n" " levelmeter_thumb=\"ff9a9a9a\" panel_background=\"fff7f7f7\" panel_foreground=\"fffdfdfd\"\n" @@ -1577,7 +1577,7 @@ const String PlugDataLook::defaultThemesXml = " " " default_object_background=\"ffe3dfd9\" object_outline_colour=\"ff968e82\"\n" " selected_object_outline_colour=\"ff5da0c4\" gui_internal_outline_colour=\"ff968e82\"\n" " toolbar_outline_colour=\"ffbdb3a4\" outline_colour=\"ff968e82\" data_colour=\"ff5da0c4\"\n" -" connection_colour=\"ffb3b3b3\" signal_colour=\"ffff8502\" dialog_background=\"ffd2cdc4\"\n" +" connection_colour=\"ffb3b3b3\" signal_colour=\"ffff8502\" gem_colour=\"ff02f503\" dialog_background=\"ffd2cdc4\"\n" " sidebar_colour=\"ffdedad3\" sidebar_text=\"ff5a5a5a\" sidebar_background_active=\"ffefefef\"\n" " sidebar_active_text=\"ff5a5a5a\" levelmeter_active=\"ff5da0c4\" levelmeter_background=\"ffc0bbb2\"\n" " levelmeter_thumb=\"ff7a7a7a\" panel_background=\"ffd2cdc4\" panel_foreground=\"ffe7e2d8\"\n" @@ -1596,7 +1596,7 @@ const String PlugDataLook::defaultThemesXml = " " " default_object_background=\"ff191919\" object_outline_colour=\"ff383838\"\n" " selected_object_outline_colour=\"ffffacab\" gui_internal_outline_colour=\"ff626262\"\n" " toolbar_outline_colour=\"ff343434\" outline_colour=\"ff383838\" data_colour=\"ff5bcefa\"\n" -" connection_colour=\"ffa0a0a0\" signal_colour=\"ffffacab\" dialog_background=\"ff191919\"\n" +" connection_colour=\"ffa0a0a0\" signal_colour=\"ffffacab\" gem_colour=\"ff02f503\" dialog_background=\"ff191919\"\n" " sidebar_colour=\"ff232323\" sidebar_text=\"ffffffff\" sidebar_background_active=\"ff383838\"\n" " sidebar_active_text=\"ffffffff\" levelmeter_active=\"ff5bcefa\" levelmeter_background=\"ff3a3a3a\"\n" " levelmeter_thumb=\"fff5f5f5\" panel_background=\"ff2c2c2c\" panel_foreground=\"ff1f1f1f\"\n" diff --git a/Source/LookAndFeel.h b/Source/LookAndFeel.h index bab26aa72d..7e5eab7e0c 100644 --- a/Source/LookAndFeel.h +++ b/Source/LookAndFeel.h @@ -40,6 +40,7 @@ inline std::map> const PlugDa { dataColourId, { "Data colour", "data_colour", "Canvas" } }, { connectionColourId, { "Connection", "connection_colour", "Canvas" } }, { signalColourId, { "Signal", "signal_colour", "Canvas" } }, + { gemColourId, { "Gem", "gem_colour", "Canvas" } }, { resizeableCornerColourId, { "Graph resizer", "graph_resizer", "Canvas" } }, { gridLineColourId, { "Grid line", "grid_colour", "Canvas" } }, diff --git a/Source/Object.cpp b/Source/Object.cpp index 147f3ffaf4..82eb401206 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -626,10 +626,16 @@ void Object::updateTooltips() auto* iolet = iolets[i]; auto& tooltip = ioletTooltips[!iolet->isInlet][iolet->isInlet ? i : i - numInputs]; - - // Don't overwrite custom documentation - if (tooltip.isNotEmpty()) { - iolet->setTooltip(tooltip); + if(tooltip.startsWith("(gemlist)")) + { + iolet->isGemState = true; + iolet->setTooltip("(gemlist)"); + } + else { + if (tooltip.isNotEmpty()) { // Don't overwrite custom documentation if there is no md documentation + iolet->setTooltip(tooltip); + } + iolet->isGemState = false; } } @@ -700,6 +706,7 @@ void Object::updateTooltips() auto& [x, message] = iolet->isInlet ? inletMessages[numIn++] : outletMessages[numOut++]; iolet->setTooltip(message); + iolet->isGemState = message.startsWith("(gemlist)"); } } diff --git a/Source/Pd/Library.cpp b/Source/Pd/Library.cpp index 050a8dafc8..caf0ff3b68 100644 --- a/Source/Pd/Library.cpp +++ b/Source/Pd/Library.cpp @@ -58,7 +58,7 @@ void Library::updateLibrary() continue; auto newName = String::fromUTF8(m->me_name->s_name); - if (!(newName.startsWith("else/") || newName.startsWith("cyclone/") || newName.endsWith("_aliased"))) { + if (!(newName.startsWith("else/") || newName.startsWith("cyclone/") || newName.startsWith("Gem/") || newName.endsWith("_aliased"))) { allObjects.add(newName); } } diff --git a/Source/Pd/Library.h b/Source/Pd/Library.h index 6b30dd08cf..ed0fa117aa 100644 --- a/Source/Pd/Library.h +++ b/Source/Pd/Library.h @@ -48,7 +48,6 @@ class Library : public FileSystemWatcher::Listener { ProjectInfo::appDataDir.getChildFile("Abstractions").getChildFile("else"), ProjectInfo::appDataDir.getChildFile("Abstractions").getChildFile("cyclone"), ProjectInfo::appDataDir.getChildFile("Abstractions").getChildFile("heavylib"), - ProjectInfo::appDataDir.getChildFile("Abstractions").getChildFile("Gem"), ProjectInfo::appDataDir.getChildFile("Abstractions"), ProjectInfo::appDataDir.getChildFile("Externals"), ProjectInfo::appDataDir.getChildFile("Extra").getChildFile("else"), From 0a30c343c0f3d107067bdef6a2f3e673780a3587 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Jan 2024 16:20:58 +0100 Subject: [PATCH 0113/1030] Fixed resource scripts --- Resources/Scripts/package_resources.py | 1 - Resources/Scripts/parse_documentation.py | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index 0a2851f539..eb2afc0a9e 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -133,7 +133,6 @@ def splitFile(file, num_files): globMove("Abstractions/Gem/*-help.pd", "Documentation/14.gem/") makeDir("Extra/Gem") # user can put plugins and resources in here -globCopy("../../Libraries/Gem/examples/data/*", "Extra/Gem/") # if we don't do this, helpfiles can't find resources for some reason # extract precompiled Gem plugins for our architecture system = platform.system().lower() diff --git a/Resources/Scripts/parse_documentation.py b/Resources/Scripts/parse_documentation.py index 5abedd8b3d..6d8f1bc519 100644 --- a/Resources/Scripts/parse_documentation.py +++ b/Resources/Scripts/parse_documentation.py @@ -201,6 +201,7 @@ def markdownToXml(root, md): arguments = ET.SubElement(object, "arguments") methods = ET.SubElement(object, "methods") flags = ET.SubElement(object, "flags") + #print(title) if "methods" in sections: for section in sectionsFromHyphens(sections["methods"]): @@ -232,7 +233,6 @@ def markdownToXml(root, md): ET.SubElement(flags, "flag", name=sectionMap["name"], description=desc) numbers = { "1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", "nth" }; - #print(description) if "inlets" in sections: inletSections = getSections(sections["inlets"], numbers) @@ -244,9 +244,11 @@ def markdownToXml(root, md): for argument in sectionsFromHyphens(inletSections[section]): typeAndDescription = getSections(argument, { "type", "description" }) tip += "(" + typeAndDescription["type"] + ") " + description = "" if "description" in typeAndDescription: tip += typeAndDescription["description"] + "\n" - ET.SubElement(inlet, "message", type=typeAndDescription["type"], description=typeAndDescription["description"]) + description = typeAndDescription["description"] + ET.SubElement(inlet, "message", type=typeAndDescription["type"], description=description) inlet.set("tooltip", tip.strip()) iolets.append(inlet) From 8e6d5858904615dfd2e53f3ebb134413b571e76d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Jan 2024 16:22:18 +0100 Subject: [PATCH 0114/1030] Don't load Gem in global namespace --- Libraries/Gem | 2 +- Libraries/pure-data | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index 1820974718..82abda8fb3 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 1820974718e5d99b5ffda4509fd559724925224a +Subproject commit 82abda8fb34ea6174fd20104326c2a5c99726106 diff --git a/Libraries/pure-data b/Libraries/pure-data index 5ff10320c2..b41e97c299 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit 5ff10320c278a791cd673b0bee0fa5d24c91e23a +Subproject commit b41e97c29903788034e65538b828e5d3d090a9cb From 66a67c6a33501116e2467ecd69fa403fa6b55133 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Jan 2024 16:27:00 +0100 Subject: [PATCH 0115/1030] Applied temporary fix --- Source/Utility/SettingsFile.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Source/Utility/SettingsFile.cpp b/Source/Utility/SettingsFile.cpp index 1cc5729b6a..da805dfaa7 100644 --- a/Source/Utility/SettingsFile.cpp +++ b/Source/Utility/SettingsFile.cpp @@ -163,6 +163,19 @@ void SettingsFile::initialisePathsTree() pathTree.appendChild(pathSubTree, nullptr); } } + + // TODO: remove this later. this is to fix a mistake during v0.8.4 development + for (auto child : pathTree) { + if(child.getProperty("Path").toString().contains("Abstractions/Gem")) + { + pathTree.removeChild(child, nullptr); + break; + } + } + + + + } void SettingsFile::addToRecentlyOpened(File const& path) From 9f52faf4691b743adddaa39e00f3ae10e984d7cc Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Jan 2024 16:29:13 +0100 Subject: [PATCH 0116/1030] Object library fix for Gem --- Source/Pd/Library.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Pd/Library.cpp b/Source/Pd/Library.cpp index caf0ff3b68..050a8dafc8 100644 --- a/Source/Pd/Library.cpp +++ b/Source/Pd/Library.cpp @@ -58,7 +58,7 @@ void Library::updateLibrary() continue; auto newName = String::fromUTF8(m->me_name->s_name); - if (!(newName.startsWith("else/") || newName.startsWith("cyclone/") || newName.startsWith("Gem/") || newName.endsWith("_aliased"))) { + if (!(newName.startsWith("else/") || newName.startsWith("cyclone/") || newName.endsWith("_aliased"))) { allObjects.add(newName); } } From 233ce179f1bb50fde581d9db1bb41b1e97b832e4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Jan 2024 16:29:33 +0100 Subject: [PATCH 0117/1030] Undo cyclone/scale alias --- Libraries/cyclone/cyclone_objects/binaries/control/scale.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Libraries/cyclone/cyclone_objects/binaries/control/scale.c b/Libraries/cyclone/cyclone_objects/binaries/control/scale.c index 7891191d75..b631921af8 100644 --- a/Libraries/cyclone/cyclone_objects/binaries/control/scale.c +++ b/Libraries/cyclone/cyclone_objects/binaries/control/scale.c @@ -150,8 +150,7 @@ CYCLONE_OBJ_API void cyclone_scale_setup(void) { t_class *c; - // Gem also has a [scale] object, and it needs to be in global namespace - scale_class = class_new(gensym("cyclone/scale"), (t_newmethod)scale_new, + scale_class = class_new(gensym("scale"), (t_newmethod)scale_new, (t_method)scale_free,sizeof(t_scale),0,A_GIMME,0); c = scale_class; class_addfloat(c,(t_method)scale_ft); From a47a410b6bd033c761413959516dcefe5f6ae286 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Jan 2024 16:39:53 +0100 Subject: [PATCH 0118/1030] Gem: fixes for object browser --- Source/Dialogs/ObjectBrowserDialog.h | 4 ++-- Source/Pd/Library.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Dialogs/ObjectBrowserDialog.h b/Source/Dialogs/ObjectBrowserDialog.h index 1b77dc5d4c..a135ba359a 100644 --- a/Source/Dialogs/ObjectBrowserDialog.h +++ b/Source/Dialogs/ObjectBrowserDialog.h @@ -225,13 +225,13 @@ class ObjectsListBox : public ListBox { if (existingComponentToUpdate == nullptr) { auto name = objects[rowNumber]; - auto description = descriptions[name]; + auto description = descriptions[name.fromLastOccurrenceOf("/", false, false)]; return new ObjectListBoxItem(this, name, description, isRowSelected, dismiss); } else { auto* itemComponent = dynamic_cast(existingComponentToUpdate); if (itemComponent != nullptr) { auto name = objects[rowNumber]; - auto description = descriptions[name]; + auto description = descriptions[name.fromLastOccurrenceOf("/", false, false)]; itemComponent->refresh(name, description, rowNumber, isRowSelected); } return itemComponent; diff --git a/Source/Pd/Library.cpp b/Source/Pd/Library.cpp index 050a8dafc8..93700427d8 100644 --- a/Source/Pd/Library.cpp +++ b/Source/Pd/Library.cpp @@ -212,7 +212,7 @@ void Library::getExtraSuggestions(int currentNumSuggestions, String const& query ValueTree Library::getObjectInfo(String const& name) { - return documentationTree.getChildWithProperty("name", name); + return documentationTree.getChildWithProperty("name", name.fromLastOccurrenceOf("/", false, false)); } std::array Library::parseIoletTooltips(ValueTree const& iolets, String const& name, int numIn, int numOut) From b8b3a4689b5f77cf7f509d1acb96293c4a087ff4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Jan 2024 17:29:02 +0100 Subject: [PATCH 0119/1030] Fixed small tree node bug, fixed gem connection hover colour --- Source/Connection.cpp | 6 +++--- Source/Utility/ValueTreeNodeBranchLine.h | 16 +++++++++------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Source/Connection.cpp b/Source/Connection.cpp index c9fb69a288..2ed08711da 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -293,7 +293,7 @@ void Connection::renderConnectionPath(Graphics& g, auto connectionLength = connectionPath.getLength(); - if (isSelected) { + if (isSelected || isMouseOver) { if(isSignal) { baseColour = signalColour; @@ -306,8 +306,8 @@ void Connection::renderConnectionPath(Graphics& g, baseColour = dataColour; } - } else if (isMouseOver) { - baseColour = isSignal ? signalColour : dataColour; + } + if (isMouseOver) { baseColour = baseColour.brighter(0.6f); } diff --git a/Source/Utility/ValueTreeNodeBranchLine.h b/Source/Utility/ValueTreeNodeBranchLine.h index 4fcf08b838..e2d10adc14 100644 --- a/Source/Utility/ValueTreeNodeBranchLine.h +++ b/Source/Utility/ValueTreeNodeBranchLine.h @@ -57,13 +57,15 @@ class ValueTreeNodeBranchLine : public Component, public SettableTooltipClient auto lineEnd = Point(4.0f, b.getHeight() - 3.0f); treeLine.startNewSubPath(4.0f, 0.0f); treeLine.lineTo(lineEnd); - - treeLineImage = Image(Image::PixelFormat::ARGB, b.getWidth(), b.getHeight(), true); - Graphics treeLineG(treeLineImage); - treeLineG.setColour(Colours::white); - treeLineG.strokePath(treeLine, PathStrokeType(1.0f)); - auto ballEnd = Rectangle(0, 0, 5, 5).withCentre(lineEnd); - treeLineG.fillEllipse(ballEnd); + + if(!b.isEmpty()) { + treeLineImage = Image(Image::PixelFormat::ARGB, b.getWidth(), b.getHeight(), true); + Graphics treeLineG(treeLineImage); + treeLineG.setColour(Colours::white); + treeLineG.strokePath(treeLine, PathStrokeType(1.0f)); + auto ballEnd = Rectangle(0, 0, 5, 5).withCentre(lineEnd); + treeLineG.fillEllipse(ballEnd); + } } } From e0452a0fc4e7b2870318b184c4fd1e281aba96ca Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Jan 2024 17:52:51 +0100 Subject: [PATCH 0120/1030] Gem: object browser fixes, Windows fixes --- Resources/Documentation/Gem/GEMglAccum.md | 2 +- .../Documentation/Gem/GEMglActiveTexture.md | 2 +- .../Documentation/Gem/GEMglActiveTextureARB.md | 2 +- Resources/Documentation/Gem/GEMglAlphaFunc.md | 2 +- .../Gem/GEMglAreTexturesResident.md | 2 +- .../Documentation/Gem/GEMglArrayElement.md | 2 +- Resources/Documentation/Gem/GEMglBegin.md | 2 +- .../Documentation/Gem/GEMglBindProgramARB.md | 2 +- .../Documentation/Gem/GEMglBindTexture.md | 2 +- Resources/Documentation/Gem/GEMglBitmap.md | 2 +- .../Documentation/Gem/GEMglBlendEquation.md | 2 +- Resources/Documentation/Gem/GEMglBlendFunc.md | 2 +- Resources/Documentation/Gem/GEMglCallList.md | 2 +- Resources/Documentation/Gem/GEMglClear.md | 2 +- Resources/Documentation/Gem/GEMglClearAccum.md | 2 +- Resources/Documentation/Gem/GEMglClearColor.md | 2 +- Resources/Documentation/Gem/GEMglClearDepth.md | 2 +- Resources/Documentation/Gem/GEMglClearIndex.md | 2 +- .../Documentation/Gem/GEMglClearStencil.md | 2 +- Resources/Documentation/Gem/GEMglClipPlane.md | 2 +- Resources/Documentation/Gem/GEMglColor3b.md | 2 +- Resources/Documentation/Gem/GEMglColor3bv.md | 2 +- Resources/Documentation/Gem/GEMglColor3d.md | 2 +- Resources/Documentation/Gem/GEMglColor3dv.md | 2 +- Resources/Documentation/Gem/GEMglColor3f.md | 2 +- Resources/Documentation/Gem/GEMglColor3fv.md | 2 +- Resources/Documentation/Gem/GEMglColor3i.md | 2 +- Resources/Documentation/Gem/GEMglColor3iv.md | 2 +- Resources/Documentation/Gem/GEMglColor3s.md | 2 +- Resources/Documentation/Gem/GEMglColor3sv.md | 2 +- Resources/Documentation/Gem/GEMglColor3ub.md | 2 +- Resources/Documentation/Gem/GEMglColor3ubv.md | 2 +- Resources/Documentation/Gem/GEMglColor3ui.md | 2 +- Resources/Documentation/Gem/GEMglColor3uiv.md | 2 +- Resources/Documentation/Gem/GEMglColor3us.md | 2 +- Resources/Documentation/Gem/GEMglColor3usv.md | 2 +- Resources/Documentation/Gem/GEMglColor4b.md | 2 +- Resources/Documentation/Gem/GEMglColor4bv.md | 2 +- Resources/Documentation/Gem/GEMglColor4d.md | 2 +- Resources/Documentation/Gem/GEMglColor4dv.md | 2 +- Resources/Documentation/Gem/GEMglColor4f.md | 2 +- Resources/Documentation/Gem/GEMglColor4fv.md | 2 +- Resources/Documentation/Gem/GEMglColor4i.md | 2 +- Resources/Documentation/Gem/GEMglColor4iv.md | 2 +- Resources/Documentation/Gem/GEMglColor4s.md | 2 +- Resources/Documentation/Gem/GEMglColor4sv.md | 2 +- Resources/Documentation/Gem/GEMglColor4ub.md | 2 +- Resources/Documentation/Gem/GEMglColor4ubv.md | 2 +- Resources/Documentation/Gem/GEMglColor4ui.md | 2 +- Resources/Documentation/Gem/GEMglColor4uiv.md | 2 +- Resources/Documentation/Gem/GEMglColor4us.md | 2 +- Resources/Documentation/Gem/GEMglColor4usv.md | 2 +- Resources/Documentation/Gem/GEMglColorMask.md | 2 +- .../Documentation/Gem/GEMglColorMaterial.md | 2 +- Resources/Documentation/Gem/GEMglCopyPixels.md | 2 +- .../Documentation/Gem/GEMglCopyTexImage1D.md | 2 +- .../Documentation/Gem/GEMglCopyTexImage2D.md | 2 +- .../Gem/GEMglCopyTexSubImage1D.md | 2 +- .../Gem/GEMglCopyTexSubImage2D.md | 2 +- Resources/Documentation/Gem/GEMglCullFace.md | 2 +- .../Documentation/Gem/GEMglDeleteTextures.md | 2 +- Resources/Documentation/Gem/GEMglDepthFunc.md | 2 +- Resources/Documentation/Gem/GEMglDepthMask.md | 2 +- Resources/Documentation/Gem/GEMglDepthRange.md | 2 +- Resources/Documentation/Gem/GEMglDisable.md | 2 +- .../Gem/GEMglDisableClientState.md | 2 +- Resources/Documentation/Gem/GEMglDrawArrays.md | 2 +- Resources/Documentation/Gem/GEMglDrawBuffer.md | 2 +- .../Documentation/Gem/GEMglDrawElements.md | 2 +- Resources/Documentation/Gem/GEMglEdgeFlag.md | 2 +- Resources/Documentation/Gem/GEMglEnable.md | 2 +- .../Gem/GEMglEnableClientState.md | 2 +- Resources/Documentation/Gem/GEMglEnd.md | 2 +- Resources/Documentation/Gem/GEMglEndList.md | 2 +- .../Documentation/Gem/GEMglEvalCoord1d.md | 2 +- .../Documentation/Gem/GEMglEvalCoord1dv.md | 2 +- .../Documentation/Gem/GEMglEvalCoord1f.md | 2 +- .../Documentation/Gem/GEMglEvalCoord1fv.md | 2 +- .../Documentation/Gem/GEMglEvalCoord2d.md | 2 +- .../Documentation/Gem/GEMglEvalCoord2dv.md | 2 +- .../Documentation/Gem/GEMglEvalCoord2f.md | 2 +- .../Documentation/Gem/GEMglEvalCoord2fv.md | 2 +- Resources/Documentation/Gem/GEMglEvalMesh1.md | 2 +- Resources/Documentation/Gem/GEMglEvalMesh2.md | 2 +- Resources/Documentation/Gem/GEMglEvalPoint1.md | 2 +- Resources/Documentation/Gem/GEMglEvalPoint2.md | 2 +- .../Documentation/Gem/GEMglFeedbackBuffer.md | 2 +- Resources/Documentation/Gem/GEMglFinish.md | 2 +- Resources/Documentation/Gem/GEMglFlush.md | 2 +- Resources/Documentation/Gem/GEMglFogf.md | 2 +- Resources/Documentation/Gem/GEMglFogfv.md | 2 +- Resources/Documentation/Gem/GEMglFogi.md | 2 +- Resources/Documentation/Gem/GEMglFogiv.md | 2 +- Resources/Documentation/Gem/GEMglFrontFace.md | 2 +- Resources/Documentation/Gem/GEMglFrustum.md | 2 +- Resources/Documentation/Gem/GEMglGenLists.md | 2 +- .../Documentation/Gem/GEMglGenProgramsARB.md | 2 +- .../Documentation/Gem/GEMglGenTextures.md | 2 +- .../Documentation/Gem/GEMglGenerateMipmap.md | 2 +- Resources/Documentation/Gem/GEMglGetError.md | 2 +- Resources/Documentation/Gem/GEMglGetFloatv.md | 2 +- .../Documentation/Gem/GEMglGetIntegerv.md | 2 +- Resources/Documentation/Gem/GEMglGetMapdv.md | 2 +- Resources/Documentation/Gem/GEMglGetMapfv.md | 2 +- Resources/Documentation/Gem/GEMglGetMapiv.md | 2 +- .../Documentation/Gem/GEMglGetPointerv.md | 2 +- Resources/Documentation/Gem/GEMglGetString.md | 2 +- Resources/Documentation/Gem/GEMglHint.md | 2 +- Resources/Documentation/Gem/GEMglIndexMask.md | 2 +- Resources/Documentation/Gem/GEMglIndexd.md | 2 +- Resources/Documentation/Gem/GEMglIndexdv.md | 2 +- Resources/Documentation/Gem/GEMglIndexf.md | 2 +- Resources/Documentation/Gem/GEMglIndexfv.md | 2 +- Resources/Documentation/Gem/GEMglIndexi.md | 2 +- Resources/Documentation/Gem/GEMglIndexiv.md | 2 +- Resources/Documentation/Gem/GEMglIndexs.md | 2 +- Resources/Documentation/Gem/GEMglIndexsv.md | 2 +- Resources/Documentation/Gem/GEMglIndexub.md | 2 +- Resources/Documentation/Gem/GEMglIndexubv.md | 2 +- Resources/Documentation/Gem/GEMglInitNames.md | 2 +- Resources/Documentation/Gem/GEMglIsEnabled.md | 2 +- Resources/Documentation/Gem/GEMglIsList.md | 2 +- Resources/Documentation/Gem/GEMglIsTexture.md | 2 +- .../Documentation/Gem/GEMglLightModelf.md | 2 +- .../Documentation/Gem/GEMglLightModeli.md | 2 +- Resources/Documentation/Gem/GEMglLightf.md | 2 +- Resources/Documentation/Gem/GEMglLighti.md | 2 +- .../Documentation/Gem/GEMglLineStipple.md | 2 +- Resources/Documentation/Gem/GEMglLineWidth.md | 2 +- .../Documentation/Gem/GEMglLoadIdentity.md | 2 +- .../Documentation/Gem/GEMglLoadMatrixd.md | 2 +- .../Documentation/Gem/GEMglLoadMatrixf.md | 2 +- Resources/Documentation/Gem/GEMglLoadName.md | 2 +- .../Gem/GEMglLoadTransposeMatrixd.md | 2 +- .../Gem/GEMglLoadTransposeMatrixf.md | 2 +- Resources/Documentation/Gem/GEMglLogicOp.md | 2 +- Resources/Documentation/Gem/GEMglMap1d.md | 2 +- Resources/Documentation/Gem/GEMglMap1f.md | 2 +- Resources/Documentation/Gem/GEMglMap2d.md | 2 +- Resources/Documentation/Gem/GEMglMap2f.md | 2 +- Resources/Documentation/Gem/GEMglMapGrid1d.md | 2 +- Resources/Documentation/Gem/GEMglMapGrid1f.md | 2 +- Resources/Documentation/Gem/GEMglMapGrid2d.md | 2 +- Resources/Documentation/Gem/GEMglMapGrid2f.md | 2 +- Resources/Documentation/Gem/GEMglMaterialf.md | 2 +- Resources/Documentation/Gem/GEMglMaterialfv.md | 2 +- Resources/Documentation/Gem/GEMglMateriali.md | 2 +- Resources/Documentation/Gem/GEMglMatrixMode.md | 2 +- .../Documentation/Gem/GEMglMultMatrixd.md | 2 +- .../Documentation/Gem/GEMglMultMatrixf.md | 2 +- .../Gem/GEMglMultTransposeMatrixf.md | 2 +- .../Documentation/Gem/GEMglMultiTexCoord2f.md | 2 +- .../Gem/GEMglMultiTexCoord2fARB.md | 2 +- Resources/Documentation/Gem/GEMglNewList.md | 2 +- Resources/Documentation/Gem/GEMglNormal3b.md | 2 +- Resources/Documentation/Gem/GEMglNormal3bv.md | 2 +- Resources/Documentation/Gem/GEMglNormal3d.md | 2 +- Resources/Documentation/Gem/GEMglNormal3dv.md | 2 +- Resources/Documentation/Gem/GEMglNormal3f.md | 2 +- Resources/Documentation/Gem/GEMglNormal3fv.md | 2 +- Resources/Documentation/Gem/GEMglNormal3i.md | 2 +- Resources/Documentation/Gem/GEMglNormal3iv.md | 2 +- Resources/Documentation/Gem/GEMglNormal3s.md | 2 +- Resources/Documentation/Gem/GEMglNormal3sv.md | 2 +- Resources/Documentation/Gem/GEMglOrtho.md | 2 +- .../Documentation/Gem/GEMglPassThrough.md | 2 +- .../Documentation/Gem/GEMglPixelStoref.md | 2 +- .../Documentation/Gem/GEMglPixelStorei.md | 2 +- .../Documentation/Gem/GEMglPixelTransferf.md | 2 +- .../Documentation/Gem/GEMglPixelTransferi.md | 2 +- Resources/Documentation/Gem/GEMglPixelZoom.md | 2 +- Resources/Documentation/Gem/GEMglPointSize.md | 2 +- .../Documentation/Gem/GEMglPolygonMode.md | 2 +- .../Documentation/Gem/GEMglPolygonOffset.md | 2 +- Resources/Documentation/Gem/GEMglPopAttrib.md | 2 +- .../Documentation/Gem/GEMglPopClientAttrib.md | 2 +- Resources/Documentation/Gem/GEMglPopMatrix.md | 2 +- Resources/Documentation/Gem/GEMglPopName.md | 2 +- .../Gem/GEMglPrioritizeTextures.md | 2 +- .../Gem/GEMglProgramEnvParameter4dARB.md | 2 +- .../Gem/GEMglProgramEnvParameter4fvARB.md | 2 +- .../Gem/GEMglProgramLocalParameter4fvARB.md | 2 +- .../Documentation/Gem/GEMglProgramStringARB.md | 2 +- Resources/Documentation/Gem/GEMglPushAttrib.md | 2 +- .../Documentation/Gem/GEMglPushClientAttrib.md | 2 +- Resources/Documentation/Gem/GEMglPushMatrix.md | 2 +- Resources/Documentation/Gem/GEMglPushName.md | 2 +- .../Documentation/Gem/GEMglRasterPos2d.md | 2 +- .../Documentation/Gem/GEMglRasterPos2dv.md | 2 +- .../Documentation/Gem/GEMglRasterPos2f.md | 2 +- .../Documentation/Gem/GEMglRasterPos2fv.md | 2 +- .../Documentation/Gem/GEMglRasterPos2i.md | 2 +- .../Documentation/Gem/GEMglRasterPos2iv.md | 2 +- .../Documentation/Gem/GEMglRasterPos2s.md | 2 +- .../Documentation/Gem/GEMglRasterPos2sv.md | 2 +- .../Documentation/Gem/GEMglRasterPos3d.md | 2 +- .../Documentation/Gem/GEMglRasterPos3dv.md | 2 +- .../Documentation/Gem/GEMglRasterPos3f.md | 2 +- .../Documentation/Gem/GEMglRasterPos3fv.md | 2 +- .../Documentation/Gem/GEMglRasterPos3i.md | 2 +- .../Documentation/Gem/GEMglRasterPos3iv.md | 2 +- .../Documentation/Gem/GEMglRasterPos3s.md | 2 +- .../Documentation/Gem/GEMglRasterPos3sv.md | 2 +- .../Documentation/Gem/GEMglRasterPos4d.md | 2 +- .../Documentation/Gem/GEMglRasterPos4dv.md | 2 +- .../Documentation/Gem/GEMglRasterPos4f.md | 2 +- .../Documentation/Gem/GEMglRasterPos4fv.md | 2 +- .../Documentation/Gem/GEMglRasterPos4i.md | 2 +- .../Documentation/Gem/GEMglRasterPos4iv.md | 2 +- .../Documentation/Gem/GEMglRasterPos4s.md | 2 +- .../Documentation/Gem/GEMglRasterPos4sv.md | 2 +- Resources/Documentation/Gem/GEMglRectd.md | 2 +- Resources/Documentation/Gem/GEMglRectf.md | 2 +- Resources/Documentation/Gem/GEMglRecti.md | 2 +- Resources/Documentation/Gem/GEMglRects.md | 2 +- Resources/Documentation/Gem/GEMglRenderMode.md | 2 +- .../Documentation/Gem/GEMglReportError.md | 2 +- Resources/Documentation/Gem/GEMglRotated.md | 2 +- Resources/Documentation/Gem/GEMglRotatef.md | 2 +- Resources/Documentation/Gem/GEMglScaled.md | 2 +- Resources/Documentation/Gem/GEMglScalef.md | 2 +- Resources/Documentation/Gem/GEMglScissor.md | 2 +- .../Documentation/Gem/GEMglSelectBuffer.md | 2 +- Resources/Documentation/Gem/GEMglShadeModel.md | 2 +- .../Documentation/Gem/GEMglStencilFunc.md | 2 +- .../Documentation/Gem/GEMglStencilMask.md | 2 +- Resources/Documentation/Gem/GEMglStencilOp.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord1d.md | 2 +- .../Documentation/Gem/GEMglTexCoord1dv.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord1f.md | 2 +- .../Documentation/Gem/GEMglTexCoord1fv.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord1i.md | 2 +- .../Documentation/Gem/GEMglTexCoord1iv.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord1s.md | 2 +- .../Documentation/Gem/GEMglTexCoord1sv.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord2d.md | 2 +- .../Documentation/Gem/GEMglTexCoord2dv.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord2f.md | 2 +- .../Documentation/Gem/GEMglTexCoord2fv.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord2i.md | 2 +- .../Documentation/Gem/GEMglTexCoord2iv.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord2s.md | 2 +- .../Documentation/Gem/GEMglTexCoord2sv.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord3d.md | 2 +- .../Documentation/Gem/GEMglTexCoord3dv.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord3f.md | 2 +- .../Documentation/Gem/GEMglTexCoord3fv.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord3i.md | 2 +- .../Documentation/Gem/GEMglTexCoord3iv.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord3s.md | 2 +- .../Documentation/Gem/GEMglTexCoord3sv.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord4d.md | 2 +- .../Documentation/Gem/GEMglTexCoord4dv.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord4f.md | 2 +- .../Documentation/Gem/GEMglTexCoord4fv.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord4i.md | 2 +- .../Documentation/Gem/GEMglTexCoord4iv.md | 2 +- Resources/Documentation/Gem/GEMglTexCoord4s.md | 2 +- .../Documentation/Gem/GEMglTexCoord4sv.md | 2 +- Resources/Documentation/Gem/GEMglTexEnvf.md | 2 +- Resources/Documentation/Gem/GEMglTexEnvi.md | 2 +- Resources/Documentation/Gem/GEMglTexGend.md | 2 +- Resources/Documentation/Gem/GEMglTexGenf.md | 2 +- Resources/Documentation/Gem/GEMglTexGenfv.md | 2 +- Resources/Documentation/Gem/GEMglTexGeni.md | 2 +- Resources/Documentation/Gem/GEMglTexImage2D.md | 2 +- .../Documentation/Gem/GEMglTexParameterf.md | 2 +- .../Documentation/Gem/GEMglTexParameteri.md | 2 +- .../Documentation/Gem/GEMglTexSubImage1D.md | 2 +- .../Documentation/Gem/GEMglTexSubImage2D.md | 2 +- Resources/Documentation/Gem/GEMglTranslated.md | 2 +- Resources/Documentation/Gem/GEMglTranslatef.md | 2 +- Resources/Documentation/Gem/GEMglUniform1f.md | 2 +- .../Documentation/Gem/GEMglUniform1fARB.md | 2 +- .../Gem/GEMglUseProgramObjectARB.md | 2 +- Resources/Documentation/Gem/GEMglVertex2d.md | 2 +- Resources/Documentation/Gem/GEMglVertex2dv.md | 2 +- Resources/Documentation/Gem/GEMglVertex2f.md | 2 +- Resources/Documentation/Gem/GEMglVertex2fv.md | 2 +- Resources/Documentation/Gem/GEMglVertex2i.md | 2 +- Resources/Documentation/Gem/GEMglVertex2iv.md | 2 +- Resources/Documentation/Gem/GEMglVertex2s.md | 2 +- Resources/Documentation/Gem/GEMglVertex2sv.md | 2 +- Resources/Documentation/Gem/GEMglVertex3d.md | 2 +- Resources/Documentation/Gem/GEMglVertex3dv.md | 2 +- Resources/Documentation/Gem/GEMglVertex3f.md | 2 +- Resources/Documentation/Gem/GEMglVertex3fv.md | 2 +- Resources/Documentation/Gem/GEMglVertex3i.md | 2 +- Resources/Documentation/Gem/GEMglVertex3iv.md | 2 +- Resources/Documentation/Gem/GEMglVertex3s.md | 2 +- Resources/Documentation/Gem/GEMglVertex3sv.md | 2 +- Resources/Documentation/Gem/GEMglVertex4d.md | 2 +- Resources/Documentation/Gem/GEMglVertex4dv.md | 2 +- Resources/Documentation/Gem/GEMglVertex4f.md | 2 +- Resources/Documentation/Gem/GEMglVertex4fv.md | 2 +- Resources/Documentation/Gem/GEMglVertex4i.md | 2 +- Resources/Documentation/Gem/GEMglVertex4iv.md | 2 +- Resources/Documentation/Gem/GEMglVertex4s.md | 2 +- Resources/Documentation/Gem/GEMglVertex4sv.md | 2 +- Resources/Documentation/Gem/GEMglViewport.md | 2 +- Resources/Documentation/Gem/GEMgluLookAt.md | 2 +- .../Documentation/Gem/GEMgluPerspective.md | 2 +- Resources/Documentation/Gem/GLdefine.md | 2 +- Resources/Documentation/Gem/accumrotate.md | 2 +- Resources/Documentation/Gem/alpha.md | 2 +- Resources/Documentation/Gem/ambient.md | 2 +- Resources/Documentation/Gem/ambientRGB.md | 2 +- Resources/Documentation/Gem/circle.md | 2 +- Resources/Documentation/Gem/color.md | 2 +- Resources/Documentation/Gem/colorRGB.md | 2 +- Resources/Documentation/Gem/colorSquare.md | 2 +- Resources/Documentation/Gem/cone.md | 2 +- Resources/Documentation/Gem/cube.md | 2 +- Resources/Documentation/Gem/cuboid.md | 2 +- Resources/Documentation/Gem/curve.md | 2 +- Resources/Documentation/Gem/curve3d.md | 2 +- Resources/Documentation/Gem/cylinder.md | 2 +- Resources/Documentation/Gem/depth.md | 2 +- Resources/Documentation/Gem/diffuse.md | 2 +- Resources/Documentation/Gem/diffuseRGB.md | 2 +- Resources/Documentation/Gem/disk.md | 2 +- Resources/Documentation/Gem/emission.md | 2 +- Resources/Documentation/Gem/emissionRGB.md | 2 +- .../Documentation/Gem/fragment_program.md | 2 +- Resources/Documentation/Gem/gemargs.md | 2 +- .../Documentation/Gem/gemcubeframebuffer.md | 2 +- Resources/Documentation/Gem/gemframebuffer.md | 2 +- Resources/Documentation/Gem/gemhead.md | 2 +- Resources/Documentation/Gem/gemkeyboard.md | 2 +- Resources/Documentation/Gem/gemkeyname.md | 2 +- Resources/Documentation/Gem/gemlist.md | 2 +- Resources/Documentation/Gem/gemlist_info.md | 2 +- Resources/Documentation/Gem/gemlist_matrix.md | 2 +- Resources/Documentation/Gem/gemmanager.md | 2 +- Resources/Documentation/Gem/gemmouse.md | 2 +- Resources/Documentation/Gem/gemreceive.md | 2 +- Resources/Documentation/Gem/gemrepeat.md | 2 +- Resources/Documentation/Gem/gemvertexbuffer.md | 2 +- Resources/Documentation/Gem/gemwin.md | 2 +- Resources/Documentation/Gem/glsl_fragment.md | 2 +- Resources/Documentation/Gem/glsl_geometry.md | 2 +- Resources/Documentation/Gem/glsl_program.md | 2 +- Resources/Documentation/Gem/glsl_vertex.md | 2 +- Resources/Documentation/Gem/hsv2rgb.md | 2 +- Resources/Documentation/Gem/imageVert.md | 2 +- Resources/Documentation/Gem/light.md | 2 +- Resources/Documentation/Gem/linear_path.md | 2 +- Resources/Documentation/Gem/mesh_line.md | 2 +- Resources/Documentation/Gem/mesh_square.md | 2 +- Resources/Documentation/Gem/model.md | 2 +- Resources/Documentation/Gem/multimodel.md | 2 +- Resources/Documentation/Gem/newWave.md | 2 +- Resources/Documentation/Gem/ortho.md | 2 +- Resources/Documentation/Gem/part_color.md | 2 +- Resources/Documentation/Gem/part_damp.md | 2 +- Resources/Documentation/Gem/part_draw.md | 2 +- Resources/Documentation/Gem/part_follow.md | 2 +- Resources/Documentation/Gem/part_gravity.md | 2 +- Resources/Documentation/Gem/part_head.md | 2 +- Resources/Documentation/Gem/part_info.md | 2 +- Resources/Documentation/Gem/part_killold.md | 2 +- Resources/Documentation/Gem/part_killslow.md | 2 +- Resources/Documentation/Gem/part_orbitpoint.md | 2 +- Resources/Documentation/Gem/part_render.md | 2 +- Resources/Documentation/Gem/part_sink.md | 2 +- Resources/Documentation/Gem/part_size.md | 2 +- Resources/Documentation/Gem/part_source.md | 2 +- .../Documentation/Gem/part_targetcolor.md | 2 +- Resources/Documentation/Gem/part_targetsize.md | 2 +- Resources/Documentation/Gem/part_velcone.md | 2 +- Resources/Documentation/Gem/part_velocity.md | 2 +- Resources/Documentation/Gem/part_velsphere.md | 2 +- Resources/Documentation/Gem/part_vertex.md | 2 +- Resources/Documentation/Gem/pix_2grey.md | 2 +- Resources/Documentation/Gem/pix_a_2grey.md | 2 +- Resources/Documentation/Gem/pix_add.md | 2 +- Resources/Documentation/Gem/pix_aging.md | 2 +- Resources/Documentation/Gem/pix_alpha.md | 2 +- Resources/Documentation/Gem/pix_background.md | 2 +- Resources/Documentation/Gem/pix_backlight.md | 2 +- Resources/Documentation/Gem/pix_biquad.md | 2 +- Resources/Documentation/Gem/pix_bitmask.md | 2 +- Resources/Documentation/Gem/pix_blob.md | 2 +- Resources/Documentation/Gem/pix_blobtracker.md | 2 +- Resources/Documentation/Gem/pix_blur.md | 2 +- Resources/Documentation/Gem/pix_buf.md | 2 +- Resources/Documentation/Gem/pix_buffer.md | 2 +- .../Documentation/Gem/pix_buffer_filmopen.md | 2 +- Resources/Documentation/Gem/pix_buffer_read.md | 2 +- .../Documentation/Gem/pix_buffer_write.md | 2 +- Resources/Documentation/Gem/pix_chroma_key.md | 2 +- Resources/Documentation/Gem/pix_clearblock.md | 2 +- Resources/Documentation/Gem/pix_color.md | 2 +- Resources/Documentation/Gem/pix_coloralpha.md | 2 +- .../Documentation/Gem/pix_colorclassify.md | 2 +- Resources/Documentation/Gem/pix_colormatrix.md | 2 +- Resources/Documentation/Gem/pix_colorreduce.md | 2 +- Resources/Documentation/Gem/pix_compare.md | 2 +- Resources/Documentation/Gem/pix_composite.md | 2 +- Resources/Documentation/Gem/pix_contrast.md | 2 +- Resources/Documentation/Gem/pix_convert.md | 2 +- Resources/Documentation/Gem/pix_convolve.md | 2 +- Resources/Documentation/Gem/pix_coordinate.md | 2 +- Resources/Documentation/Gem/pix_crop.md | 2 +- Resources/Documentation/Gem/pix_curve.md | 2 +- Resources/Documentation/Gem/pix_data.md | 2 +- Resources/Documentation/Gem/pix_deinterlace.md | 2 +- Resources/Documentation/Gem/pix_delay.md | 2 +- Resources/Documentation/Gem/pix_diff.md | 2 +- Resources/Documentation/Gem/pix_dot.md | 2 +- Resources/Documentation/Gem/pix_draw.md | 2 +- Resources/Documentation/Gem/pix_dump.md | 2 +- Resources/Documentation/Gem/pix_duotone.md | 2 +- Resources/Documentation/Gem/pix_equal.md | 2 +- Resources/Documentation/Gem/pix_film.md | 2 +- Resources/Documentation/Gem/pix_flip.md | 2 +- Resources/Documentation/Gem/pix_freeframe.md | 2 +- Resources/Documentation/Gem/pix_frei0r.md | 2 +- Resources/Documentation/Gem/pix_gain.md | 2 +- Resources/Documentation/Gem/pix_grey.md | 2 +- Resources/Documentation/Gem/pix_halftone.md | 2 +- Resources/Documentation/Gem/pix_histo.md | 2 +- Resources/Documentation/Gem/pix_hsv2rgb.md | 2 +- Resources/Documentation/Gem/pix_image.md | 2 +- .../Documentation/Gem/pix_imageInPlace.md | 2 +- Resources/Documentation/Gem/pix_info.md | 2 +- Resources/Documentation/Gem/pix_invert.md | 2 +- .../Documentation/Gem/pix_kaleidoscope.md | 2 +- Resources/Documentation/Gem/pix_levels.md | 2 +- Resources/Documentation/Gem/pix_lumaoffset.md | 2 +- Resources/Documentation/Gem/pix_mask.md | 2 +- Resources/Documentation/Gem/pix_mean_color.md | 2 +- Resources/Documentation/Gem/pix_metaimage.md | 2 +- Resources/Documentation/Gem/pix_mix.md | 2 +- Resources/Documentation/Gem/pix_motionblur.md | 2 +- Resources/Documentation/Gem/pix_movement.md | 2 +- Resources/Documentation/Gem/pix_movement2.md | 2 +- Resources/Documentation/Gem/pix_movie.md | 2 +- Resources/Documentation/Gem/pix_multiblob.md | 2 +- Resources/Documentation/Gem/pix_multiimage.md | 2 +- Resources/Documentation/Gem/pix_multiply.md | 2 +- .../Documentation/Gem/pix_multitexture.md | 2 +- Resources/Documentation/Gem/pix_noise.md | 2 +- Resources/Documentation/Gem/pix_normalize.md | 2 +- Resources/Documentation/Gem/pix_offset.md | 2 +- Resources/Documentation/Gem/pix_posterize.md | 2 +- Resources/Documentation/Gem/pix_puzzle.md | 2 +- Resources/Documentation/Gem/pix_rds.md | 2 +- Resources/Documentation/Gem/pix_record.md | 2 +- Resources/Documentation/Gem/pix_rectangle.md | 2 +- Resources/Documentation/Gem/pix_refraction.md | 2 +- Resources/Documentation/Gem/pix_resize.md | 2 +- Resources/Documentation/Gem/pix_rgb2hsv.md | 2 +- Resources/Documentation/Gem/pix_rgba.md | 2 +- Resources/Documentation/Gem/pix_roi.md | 2 +- Resources/Documentation/Gem/pix_roll.md | 2 +- Resources/Documentation/Gem/pix_rtx.md | 2 +- Resources/Documentation/Gem/pix_scanline.md | 2 +- Resources/Documentation/Gem/pix_set.md | 2 +- Resources/Documentation/Gem/pix_share_read.md | 2 +- Resources/Documentation/Gem/pix_share_write.md | 2 +- Resources/Documentation/Gem/pix_snap.md | 2 +- Resources/Documentation/Gem/pix_snap2tex.md | 2 +- Resources/Documentation/Gem/pix_subtract.md | 2 +- Resources/Documentation/Gem/pix_tIIR.md | 2 +- Resources/Documentation/Gem/pix_takealpha.md | 2 +- Resources/Documentation/Gem/pix_texture.md | 2 +- Resources/Documentation/Gem/pix_threshold.md | 2 +- .../Documentation/Gem/pix_threshold_bernsen.md | 2 +- Resources/Documentation/Gem/pix_video.md | 2 +- Resources/Documentation/Gem/pix_write.md | 2 +- Resources/Documentation/Gem/pix_writer.md | 2 +- Resources/Documentation/Gem/pix_yuv.md | 2 +- Resources/Documentation/Gem/pix_zoom.md | 2 +- Resources/Documentation/Gem/polygon.md | 2 +- Resources/Documentation/Gem/polygon_smooth.md | 2 +- Resources/Documentation/Gem/pqtorusknots.md | 2 +- Resources/Documentation/Gem/primTri.md | 2 +- Resources/Documentation/Gem/rectangle.md | 2 +- Resources/Documentation/Gem/render_trigger.md | 2 +- Resources/Documentation/Gem/rgb2hsv.md | 2 +- Resources/Documentation/Gem/rgb2yuv.md | 2 +- Resources/Documentation/Gem/ripple.md | 2 +- Resources/Documentation/Gem/rotate.md | 2 +- Resources/Documentation/Gem/rotateXYZ.md | 2 +- Resources/Documentation/Gem/rubber.md | 2 +- Resources/Documentation/Gem/scale.md | 2 +- Resources/Documentation/Gem/scaleXYZ.md | 2 +- Resources/Documentation/Gem/scopeXYZ~.md | 2 +- Resources/Documentation/Gem/separator.md | 2 +- Resources/Documentation/Gem/shearXY.md | 2 +- Resources/Documentation/Gem/shearXZ.md | 2 +- Resources/Documentation/Gem/shearYX.md | 2 +- Resources/Documentation/Gem/shearYZ.md | 2 +- Resources/Documentation/Gem/shearZX.md | 2 +- Resources/Documentation/Gem/shearZY.md | 2 +- Resources/Documentation/Gem/shininess.md | 2 +- Resources/Documentation/Gem/slideSquares.md | 2 +- Resources/Documentation/Gem/specular.md | 2 +- Resources/Documentation/Gem/specularRGB.md | 2 +- Resources/Documentation/Gem/sphere.md | 2 +- Resources/Documentation/Gem/sphere3d.md | 2 +- Resources/Documentation/Gem/spline_path.md | 2 +- Resources/Documentation/Gem/spot_light.md | 2 +- Resources/Documentation/Gem/square.md | 2 +- Resources/Documentation/Gem/surface3d.md | 2 +- Resources/Documentation/Gem/teapot.md | 2 +- Resources/Documentation/Gem/text2d.md | 2 +- Resources/Documentation/Gem/text3d.md | 2 +- Resources/Documentation/Gem/textoutline.md | 2 +- Resources/Documentation/Gem/torus.md | 2 +- Resources/Documentation/Gem/translate.md | 2 +- Resources/Documentation/Gem/translateXYZ.md | 2 +- Resources/Documentation/Gem/trapezoid.md | 2 +- Resources/Documentation/Gem/triangle.md | 2 +- Resources/Documentation/Gem/vertex_add.md | 2 +- Resources/Documentation/Gem/vertex_combine.md | 2 +- Resources/Documentation/Gem/vertex_draw.md | 2 +- Resources/Documentation/Gem/vertex_grid.md | 2 +- Resources/Documentation/Gem/vertex_mul.md | 2 +- Resources/Documentation/Gem/vertex_offset.md | 2 +- Resources/Documentation/Gem/vertex_program.md | 2 +- Resources/Documentation/Gem/vertex_quad.md | 2 +- Resources/Documentation/Gem/vertex_scale.md | 2 +- Resources/Documentation/Gem/vertex_set.md | 2 +- Resources/Documentation/Gem/vertex_tabread.md | 2 +- Resources/Documentation/Gem/world_light.md | 2 +- Resources/Documentation/Gem/yuv2rgb.md | 2 +- Source/Dialogs/ObjectBrowserDialog.h | 18 ++++++++++++++++++ Source/Objects/Gem.h | 2 +- Source/Pd/Library.h | 2 +- 531 files changed, 548 insertions(+), 530 deletions(-) diff --git a/Resources/Documentation/Gem/GEMglAccum.md b/Resources/Documentation/Gem/GEMglAccum.md index fec49e958f..c1926bd52a 100644 --- a/Resources/Documentation/Gem/GEMglAccum.md +++ b/Resources/Documentation/Gem/GEMglAccum.md @@ -4,7 +4,7 @@ title: GEMglAccum description: perform pixel arithmetic on the accumulation buffer categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the operation to be performed. diff --git a/Resources/Documentation/Gem/GEMglActiveTexture.md b/Resources/Documentation/Gem/GEMglActiveTexture.md index 3c408b2e98..230333470a 100644 --- a/Resources/Documentation/Gem/GEMglActiveTexture.md +++ b/Resources/Documentation/Gem/GEMglActiveTexture.md @@ -4,7 +4,7 @@ title: GEMglActiveTexture description: select active texture unit categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies which texture unit to make active. diff --git a/Resources/Documentation/Gem/GEMglActiveTextureARB.md b/Resources/Documentation/Gem/GEMglActiveTextureARB.md index 247d937bbb..0273f943b6 100644 --- a/Resources/Documentation/Gem/GEMglActiveTextureARB.md +++ b/Resources/Documentation/Gem/GEMglActiveTextureARB.md @@ -4,7 +4,7 @@ title: GEMglActiveTextureARB description: select active texture unit (ARB extension) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies which texture unit to make active. diff --git a/Resources/Documentation/Gem/GEMglAlphaFunc.md b/Resources/Documentation/Gem/GEMglAlphaFunc.md index b31baa7d12..23f9cfad21 100644 --- a/Resources/Documentation/Gem/GEMglAlphaFunc.md +++ b/Resources/Documentation/Gem/GEMglAlphaFunc.md @@ -4,7 +4,7 @@ title: GEMglAlphaFunc description: set the alpha test function categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the alpha comparison function. diff --git a/Resources/Documentation/Gem/GEMglAreTexturesResident.md b/Resources/Documentation/Gem/GEMglAreTexturesResident.md index ba2fb623eb..65e2aef165 100644 --- a/Resources/Documentation/Gem/GEMglAreTexturesResident.md +++ b/Resources/Documentation/Gem/GEMglAreTexturesResident.md @@ -4,7 +4,7 @@ title: GEMglAreTexturesResident description: determine if textures are loaded in texture memory categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the number of texture names to be queried. diff --git a/Resources/Documentation/Gem/GEMglArrayElement.md b/Resources/Documentation/Gem/GEMglArrayElement.md index 58d1f5d4f3..f1ea5b0630 100644 --- a/Resources/Documentation/Gem/GEMglArrayElement.md +++ b/Resources/Documentation/Gem/GEMglArrayElement.md @@ -4,7 +4,7 @@ title: GEMglArrayElement description: render a vertex using vertex arrays categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the index of the vertex in the enabled arrays. diff --git a/Resources/Documentation/Gem/GEMglBegin.md b/Resources/Documentation/Gem/GEMglBegin.md index 7a0c74d378..813f3f568a 100644 --- a/Resources/Documentation/Gem/GEMglBegin.md +++ b/Resources/Documentation/Gem/GEMglBegin.md @@ -4,7 +4,7 @@ title: GEMglBegin description: delimit the vertices of a primitive or a group of like primitives categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the primitive or group of primitives. diff --git a/Resources/Documentation/Gem/GEMglBindProgramARB.md b/Resources/Documentation/Gem/GEMglBindProgramARB.md index 082897baec..6f69a0aa1d 100644 --- a/Resources/Documentation/Gem/GEMglBindProgramARB.md +++ b/Resources/Documentation/Gem/GEMglBindProgramARB.md @@ -4,7 +4,7 @@ title: GEMglBindProgramARB description: bind a program object (ARB extension) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the program target (e.g., GL_VERTEX_PROGRAM_ARB). diff --git a/Resources/Documentation/Gem/GEMglBindTexture.md b/Resources/Documentation/Gem/GEMglBindTexture.md index edf2903a6b..dbbdddd3df 100644 --- a/Resources/Documentation/Gem/GEMglBindTexture.md +++ b/Resources/Documentation/Gem/GEMglBindTexture.md @@ -4,7 +4,7 @@ title: GEMglBindTexture description: bind a named texture to a texturing target categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target to which the texture is bound. diff --git a/Resources/Documentation/Gem/GEMglBitmap.md b/Resources/Documentation/Gem/GEMglBitmap.md index e3d7b19b50..a8a994804b 100644 --- a/Resources/Documentation/Gem/GEMglBitmap.md +++ b/Resources/Documentation/Gem/GEMglBitmap.md @@ -4,7 +4,7 @@ title: GEMglBitmap description: draw a bitmap categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the width of the bitmap image. diff --git a/Resources/Documentation/Gem/GEMglBlendEquation.md b/Resources/Documentation/Gem/GEMglBlendEquation.md index d72ad2502c..5b0cad796d 100644 --- a/Resources/Documentation/Gem/GEMglBlendEquation.md +++ b/Resources/Documentation/Gem/GEMglBlendEquation.md @@ -4,7 +4,7 @@ title: GEMglBlendEquation description: specify the equation used for both the RGB and alpha blending categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the blending equation. diff --git a/Resources/Documentation/Gem/GEMglBlendFunc.md b/Resources/Documentation/Gem/GEMglBlendFunc.md index 634b86861f..3b6b552e4b 100644 --- a/Resources/Documentation/Gem/GEMglBlendFunc.md +++ b/Resources/Documentation/Gem/GEMglBlendFunc.md @@ -4,7 +4,7 @@ title: GEMglBlendFunc description: specify pixel arithmetic for RGB and alpha components categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies how the source blending factor is computed. diff --git a/Resources/Documentation/Gem/GEMglCallList.md b/Resources/Documentation/Gem/GEMglCallList.md index cd89612c84..fa96225c32 100644 --- a/Resources/Documentation/Gem/GEMglCallList.md +++ b/Resources/Documentation/Gem/GEMglCallList.md @@ -4,7 +4,7 @@ title: GEMglCallList description: execute a display list categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the name of the display list to be executed. diff --git a/Resources/Documentation/Gem/GEMglClear.md b/Resources/Documentation/Gem/GEMglClear.md index 0d0c93f2ba..3cea70cb36 100644 --- a/Resources/Documentation/Gem/GEMglClear.md +++ b/Resources/Documentation/Gem/GEMglClear.md @@ -4,7 +4,7 @@ title: GEMglClear description: clear buffers to preset values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies which buffers are to be cleared. diff --git a/Resources/Documentation/Gem/GEMglClearAccum.md b/Resources/Documentation/Gem/GEMglClearAccum.md index 685b5bd75e..cacfecc8f4 100644 --- a/Resources/Documentation/Gem/GEMglClearAccum.md +++ b/Resources/Documentation/Gem/GEMglClearAccum.md @@ -4,7 +4,7 @@ title: GEMglClearAccum description: clear the accumulation buffer categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red value used to clear the accumulation buffer. diff --git a/Resources/Documentation/Gem/GEMglClearColor.md b/Resources/Documentation/Gem/GEMglClearColor.md index 9a2599b7a5..8ea765e809 100644 --- a/Resources/Documentation/Gem/GEMglClearColor.md +++ b/Resources/Documentation/Gem/GEMglClearColor.md @@ -4,7 +4,7 @@ title: GEMglClearColor description: specify the clear values for the color buffers categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the clear color. diff --git a/Resources/Documentation/Gem/GEMglClearDepth.md b/Resources/Documentation/Gem/GEMglClearDepth.md index 1f1c2babc7..c7332cdad2 100644 --- a/Resources/Documentation/Gem/GEMglClearDepth.md +++ b/Resources/Documentation/Gem/GEMglClearDepth.md @@ -4,7 +4,7 @@ title: GEMglClearDepth description: specify the clear value for the depth buffer categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the depth value used when the depth buffer is cleared. diff --git a/Resources/Documentation/Gem/GEMglClearIndex.md b/Resources/Documentation/Gem/GEMglClearIndex.md index b56eb66323..55e1189f58 100644 --- a/Resources/Documentation/Gem/GEMglClearIndex.md +++ b/Resources/Documentation/Gem/GEMglClearIndex.md @@ -4,7 +4,7 @@ title: GEMglClearIndex description: specify the clear value for the color index buffers categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the index used when the color index buffers are cleared. diff --git a/Resources/Documentation/Gem/GEMglClearStencil.md b/Resources/Documentation/Gem/GEMglClearStencil.md index 3aabf35456..66be3b322b 100644 --- a/Resources/Documentation/Gem/GEMglClearStencil.md +++ b/Resources/Documentation/Gem/GEMglClearStencil.md @@ -4,7 +4,7 @@ title: GEMglClearStencil description: specify the clear value for the stencil buffer categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the index used when the stencil buffer is cleared. diff --git a/Resources/Documentation/Gem/GEMglClipPlane.md b/Resources/Documentation/Gem/GEMglClipPlane.md index 188489c731..b8a2b7b443 100644 --- a/Resources/Documentation/Gem/GEMglClipPlane.md +++ b/Resources/Documentation/Gem/GEMglClipPlane.md @@ -4,7 +4,7 @@ title: GEMglClipPlane description: specify a plane against which all geometry is clipped categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies which clipping plane is being specified. diff --git a/Resources/Documentation/Gem/GEMglColor3b.md b/Resources/Documentation/Gem/GEMglColor3b.md index 09beb622ad..79fde66c45 100644 --- a/Resources/Documentation/Gem/GEMglColor3b.md +++ b/Resources/Documentation/Gem/GEMglColor3b.md @@ -4,7 +4,7 @@ title: GEMglColor3b description: set the current RGB color using 8-bit integers categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor3bv.md b/Resources/Documentation/Gem/GEMglColor3bv.md index fdfdc3b19f..6a0b1e0f47 100644 --- a/Resources/Documentation/Gem/GEMglColor3bv.md +++ b/Resources/Documentation/Gem/GEMglColor3bv.md @@ -4,7 +4,7 @@ title: GEMglColor3bv description: set the current RGB color using 8-bit integers from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, and blue components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor3d.md b/Resources/Documentation/Gem/GEMglColor3d.md index 6e420ddc8e..0c0005d945 100644 --- a/Resources/Documentation/Gem/GEMglColor3d.md +++ b/Resources/Documentation/Gem/GEMglColor3d.md @@ -4,7 +4,7 @@ title: GEMglColor3d description: set the current RGB color using double-precision floating-point values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor3dv.md b/Resources/Documentation/Gem/GEMglColor3dv.md index f72434426a..d166f5b14d 100644 --- a/Resources/Documentation/Gem/GEMglColor3dv.md +++ b/Resources/Documentation/Gem/GEMglColor3dv.md @@ -4,7 +4,7 @@ title: GEMglColor3dv description: set the current RGB color using double-precision floating-point values from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, and blue components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor3f.md b/Resources/Documentation/Gem/GEMglColor3f.md index 5380ddb14b..262fd3347a 100644 --- a/Resources/Documentation/Gem/GEMglColor3f.md +++ b/Resources/Documentation/Gem/GEMglColor3f.md @@ -4,7 +4,7 @@ title: GEMglColor3f description: set the current RGB color using single-precision floating-point values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor3fv.md b/Resources/Documentation/Gem/GEMglColor3fv.md index f89de53ead..3bd6a9a377 100644 --- a/Resources/Documentation/Gem/GEMglColor3fv.md +++ b/Resources/Documentation/Gem/GEMglColor3fv.md @@ -4,7 +4,7 @@ title: GEMglColor3fv description: set the current RGB color using single-precision floating-point values from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, and blue components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor3i.md b/Resources/Documentation/Gem/GEMglColor3i.md index 297e6d40c9..f7fdcccec0 100644 --- a/Resources/Documentation/Gem/GEMglColor3i.md +++ b/Resources/Documentation/Gem/GEMglColor3i.md @@ -4,7 +4,7 @@ title: GEMglColor3i description: set the current RGB color using 32-bit integers categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor3iv.md b/Resources/Documentation/Gem/GEMglColor3iv.md index 8c1cd76106..a572a08e6a 100644 --- a/Resources/Documentation/Gem/GEMglColor3iv.md +++ b/Resources/Documentation/Gem/GEMglColor3iv.md @@ -4,7 +4,7 @@ title: GEMglColor3iv description: set the current RGB color using 32-bit integers from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, and blue components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor3s.md b/Resources/Documentation/Gem/GEMglColor3s.md index 5572288b09..785ca92c76 100644 --- a/Resources/Documentation/Gem/GEMglColor3s.md +++ b/Resources/Documentation/Gem/GEMglColor3s.md @@ -4,7 +4,7 @@ title: GEMglColor3s description: set the current RGB color using 16-bit integers categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor3sv.md b/Resources/Documentation/Gem/GEMglColor3sv.md index 14fd10ceb8..58f8d2a9e3 100644 --- a/Resources/Documentation/Gem/GEMglColor3sv.md +++ b/Resources/Documentation/Gem/GEMglColor3sv.md @@ -4,7 +4,7 @@ title: GEMglColor3sv description: set the current RGB color using 16-bit integers from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, and blue components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor3ub.md b/Resources/Documentation/Gem/GEMglColor3ub.md index f69de4d3b4..76d24aad8f 100644 --- a/Resources/Documentation/Gem/GEMglColor3ub.md +++ b/Resources/Documentation/Gem/GEMglColor3ub.md @@ -4,7 +4,7 @@ title: GEMglColor3ub description: set the current RGB color using 8-bit unsigned integers categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor3ubv.md b/Resources/Documentation/Gem/GEMglColor3ubv.md index 560b0a92fe..7036e7cfed 100644 --- a/Resources/Documentation/Gem/GEMglColor3ubv.md +++ b/Resources/Documentation/Gem/GEMglColor3ubv.md @@ -4,7 +4,7 @@ title: GEMglColor3ubv description: set the current RGB color using 8-bit unsigned integers from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, and blue components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor3ui.md b/Resources/Documentation/Gem/GEMglColor3ui.md index eb8de01b4c..466c873562 100644 --- a/Resources/Documentation/Gem/GEMglColor3ui.md +++ b/Resources/Documentation/Gem/GEMglColor3ui.md @@ -4,7 +4,7 @@ title: GEMglColor3ui description: set the current RGB color using 32-bit unsigned integers categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor3uiv.md b/Resources/Documentation/Gem/GEMglColor3uiv.md index a7793fa813..ec3920d770 100644 --- a/Resources/Documentation/Gem/GEMglColor3uiv.md +++ b/Resources/Documentation/Gem/GEMglColor3uiv.md @@ -4,7 +4,7 @@ title: GEMglColor3uiv description: set the current RGB color using 32-bit unsigned integers from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, and blue components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor3us.md b/Resources/Documentation/Gem/GEMglColor3us.md index a6a12b03f6..fb9948b367 100644 --- a/Resources/Documentation/Gem/GEMglColor3us.md +++ b/Resources/Documentation/Gem/GEMglColor3us.md @@ -4,7 +4,7 @@ title: GEMglColor3us description: set the current RGB color using 16-bit unsigned integers categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor3usv.md b/Resources/Documentation/Gem/GEMglColor3usv.md index c485799309..1939b2bb99 100644 --- a/Resources/Documentation/Gem/GEMglColor3usv.md +++ b/Resources/Documentation/Gem/GEMglColor3usv.md @@ -4,7 +4,7 @@ title: GEMglColor3usv description: set the current RGB color using 16-bit unsigned integers from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, and blue components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4b.md b/Resources/Documentation/Gem/GEMglColor4b.md index 7132e51a27..c8e13c9615 100644 --- a/Resources/Documentation/Gem/GEMglColor4b.md +++ b/Resources/Documentation/Gem/GEMglColor4b.md @@ -4,7 +4,7 @@ title: GEMglColor4b description: set the current RGBA color using 8-bit integers categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4bv.md b/Resources/Documentation/Gem/GEMglColor4bv.md index 5f9ff5565c..15ce626d67 100644 --- a/Resources/Documentation/Gem/GEMglColor4bv.md +++ b/Resources/Documentation/Gem/GEMglColor4bv.md @@ -4,7 +4,7 @@ title: GEMglColor4bv description: set the current RGBA color using 8-bit integers from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, blue, and alpha components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4d.md b/Resources/Documentation/Gem/GEMglColor4d.md index fddf3a32cf..610dcc97b0 100644 --- a/Resources/Documentation/Gem/GEMglColor4d.md +++ b/Resources/Documentation/Gem/GEMglColor4d.md @@ -4,7 +4,7 @@ title: GEMglColor4d description: set the current RGBA color using double-precision floating-point values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4dv.md b/Resources/Documentation/Gem/GEMglColor4dv.md index 7c73c353b4..2a93fa110d 100644 --- a/Resources/Documentation/Gem/GEMglColor4dv.md +++ b/Resources/Documentation/Gem/GEMglColor4dv.md @@ -4,7 +4,7 @@ title: GEMglColor4dv description: set the current RGBA color using double-precision floating-point values from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, blue, and alpha components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4f.md b/Resources/Documentation/Gem/GEMglColor4f.md index d9184e29fd..a9fa3c8d2f 100644 --- a/Resources/Documentation/Gem/GEMglColor4f.md +++ b/Resources/Documentation/Gem/GEMglColor4f.md @@ -4,7 +4,7 @@ title: GEMglColor4f description: set the current RGBA color using single-precision floating-point values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4fv.md b/Resources/Documentation/Gem/GEMglColor4fv.md index 700bb43273..4792fa2b21 100644 --- a/Resources/Documentation/Gem/GEMglColor4fv.md +++ b/Resources/Documentation/Gem/GEMglColor4fv.md @@ -4,7 +4,7 @@ title: GEMglColor4fv description: set the current RGBA color using single-precision floating-point values from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, blue, and alpha components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4i.md b/Resources/Documentation/Gem/GEMglColor4i.md index aa39f43d1d..b17407c347 100644 --- a/Resources/Documentation/Gem/GEMglColor4i.md +++ b/Resources/Documentation/Gem/GEMglColor4i.md @@ -4,7 +4,7 @@ title: GEMglColor4i description: set the current RGBA color using 32-bit integers categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4iv.md b/Resources/Documentation/Gem/GEMglColor4iv.md index 7d36d22f98..c2f590fae9 100644 --- a/Resources/Documentation/Gem/GEMglColor4iv.md +++ b/Resources/Documentation/Gem/GEMglColor4iv.md @@ -4,7 +4,7 @@ title: GEMglColor4iv description: set the current RGBA color using 32-bit integers from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, blue, and alpha components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4s.md b/Resources/Documentation/Gem/GEMglColor4s.md index 2899725ee9..0400ff8791 100644 --- a/Resources/Documentation/Gem/GEMglColor4s.md +++ b/Resources/Documentation/Gem/GEMglColor4s.md @@ -4,7 +4,7 @@ title: GEMglColor4s description: set the current RGBA color using 16-bit integers categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4sv.md b/Resources/Documentation/Gem/GEMglColor4sv.md index f67f432e5f..4634202170 100644 --- a/Resources/Documentation/Gem/GEMglColor4sv.md +++ b/Resources/Documentation/Gem/GEMglColor4sv.md @@ -4,7 +4,7 @@ title: GEMglColor4sv description: set the current RGBA color using 16-bit integers from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, blue, and alpha components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4ub.md b/Resources/Documentation/Gem/GEMglColor4ub.md index 0f74bfadb6..258f804229 100644 --- a/Resources/Documentation/Gem/GEMglColor4ub.md +++ b/Resources/Documentation/Gem/GEMglColor4ub.md @@ -4,7 +4,7 @@ title: GEMglColor4ub description: set the current RGBA color using 8-bit unsigned integers categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4ubv.md b/Resources/Documentation/Gem/GEMglColor4ubv.md index 0e92dd9fa8..2e834deb7a 100644 --- a/Resources/Documentation/Gem/GEMglColor4ubv.md +++ b/Resources/Documentation/Gem/GEMglColor4ubv.md @@ -4,7 +4,7 @@ title: GEMglColor4ubv description: set the current RGBA color using 8-bit unsigned integers from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, blue, and alpha components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4ui.md b/Resources/Documentation/Gem/GEMglColor4ui.md index 317926036c..1f8bdb27d6 100644 --- a/Resources/Documentation/Gem/GEMglColor4ui.md +++ b/Resources/Documentation/Gem/GEMglColor4ui.md @@ -4,7 +4,7 @@ title: GEMglColor4ui description: set the current RGBA color using 32-bit unsigned integers categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4uiv.md b/Resources/Documentation/Gem/GEMglColor4uiv.md index 383c7f1368..2211811772 100644 --- a/Resources/Documentation/Gem/GEMglColor4uiv.md +++ b/Resources/Documentation/Gem/GEMglColor4uiv.md @@ -4,7 +4,7 @@ title: GEMglColor4uiv description: set the current RGBA color using 32-bit unsigned integers from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, blue, and alpha components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4us.md b/Resources/Documentation/Gem/GEMglColor4us.md index ca83a1ab1f..ecf4145bb6 100644 --- a/Resources/Documentation/Gem/GEMglColor4us.md +++ b/Resources/Documentation/Gem/GEMglColor4us.md @@ -4,7 +4,7 @@ title: GEMglColor4us description: set the current RGBA color using 16-bit unsigned integers categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the red component of the current color. diff --git a/Resources/Documentation/Gem/GEMglColor4usv.md b/Resources/Documentation/Gem/GEMglColor4usv.md index fcbd57315c..837bcdc734 100644 --- a/Resources/Documentation/Gem/GEMglColor4usv.md +++ b/Resources/Documentation/Gem/GEMglColor4usv.md @@ -4,7 +4,7 @@ title: GEMglColor4usv description: set the current RGBA color using 16-bit unsigned integers from an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the red, green, blue, and alpha components of the current color. diff --git a/Resources/Documentation/Gem/GEMglColorMask.md b/Resources/Documentation/Gem/GEMglColorMask.md index 8a6987bf9f..8b0f492401 100644 --- a/Resources/Documentation/Gem/GEMglColorMask.md +++ b/Resources/Documentation/Gem/GEMglColorMask.md @@ -4,7 +4,7 @@ title: GEMglColorMask description: enable and disable writing of frame buffer color components categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies whether red, green, blue, and alpha can be written into the frame buffer. diff --git a/Resources/Documentation/Gem/GEMglColorMaterial.md b/Resources/Documentation/Gem/GEMglColorMaterial.md index 3dc722122b..5b2739a953 100644 --- a/Resources/Documentation/Gem/GEMglColorMaterial.md +++ b/Resources/Documentation/Gem/GEMglColorMaterial.md @@ -4,7 +4,7 @@ title: GEMglColorMaterial description: specify the material parameters for the lighting model categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies which material face or faces are being updated. diff --git a/Resources/Documentation/Gem/GEMglCopyPixels.md b/Resources/Documentation/Gem/GEMglCopyPixels.md index f9768807c4..1a043e4903 100644 --- a/Resources/Documentation/Gem/GEMglCopyPixels.md +++ b/Resources/Documentation/Gem/GEMglCopyPixels.md @@ -4,7 +4,7 @@ title: GEMglCopyPixels description: copy pixels into a 1D or 2D texture image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the left x-coordinate, in pixels, of the source rectangle. diff --git a/Resources/Documentation/Gem/GEMglCopyTexImage1D.md b/Resources/Documentation/Gem/GEMglCopyTexImage1D.md index 85df4eec8a..bc5414791b 100644 --- a/Resources/Documentation/Gem/GEMglCopyTexImage1D.md +++ b/Resources/Documentation/Gem/GEMglCopyTexImage1D.md @@ -4,7 +4,7 @@ title: GEMglCopyTexImage1D description: copy pixels into a 1D texture image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target texture. diff --git a/Resources/Documentation/Gem/GEMglCopyTexImage2D.md b/Resources/Documentation/Gem/GEMglCopyTexImage2D.md index 93e291bc7b..b0357050c0 100644 --- a/Resources/Documentation/Gem/GEMglCopyTexImage2D.md +++ b/Resources/Documentation/Gem/GEMglCopyTexImage2D.md @@ -4,7 +4,7 @@ title: GEMglCopyTexImage2D description: copy pixels into a 2D texture image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target texture. diff --git a/Resources/Documentation/Gem/GEMglCopyTexSubImage1D.md b/Resources/Documentation/Gem/GEMglCopyTexSubImage1D.md index 9ff5444ce2..616cb3b67c 100644 --- a/Resources/Documentation/Gem/GEMglCopyTexSubImage1D.md +++ b/Resources/Documentation/Gem/GEMglCopyTexSubImage1D.md @@ -4,7 +4,7 @@ title: GEMglCopyTexSubImage1D description: copy a one-dimensional texture subimage categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target texture. diff --git a/Resources/Documentation/Gem/GEMglCopyTexSubImage2D.md b/Resources/Documentation/Gem/GEMglCopyTexSubImage2D.md index 7e7781d845..b620b2c796 100644 --- a/Resources/Documentation/Gem/GEMglCopyTexSubImage2D.md +++ b/Resources/Documentation/Gem/GEMglCopyTexSubImage2D.md @@ -4,7 +4,7 @@ title: GEMglCopyTexSubImage2D description: copy a two-dimensional texture subimage categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target texture. diff --git a/Resources/Documentation/Gem/GEMglCullFace.md b/Resources/Documentation/Gem/GEMglCullFace.md index a34d613eca..9541a77f9e 100644 --- a/Resources/Documentation/Gem/GEMglCullFace.md +++ b/Resources/Documentation/Gem/GEMglCullFace.md @@ -4,7 +4,7 @@ title: GEMglCullFace description: specify whether front- or back-facing facets can be culled categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the culling mode. diff --git a/Resources/Documentation/Gem/GEMglDeleteTextures.md b/Resources/Documentation/Gem/GEMglDeleteTextures.md index d8d07ebeb0..4eb91118f6 100644 --- a/Resources/Documentation/Gem/GEMglDeleteTextures.md +++ b/Resources/Documentation/Gem/GEMglDeleteTextures.md @@ -4,7 +4,7 @@ title: GEMglDeleteTextures description: delete named textures categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the number of textures to be deleted. diff --git a/Resources/Documentation/Gem/GEMglDepthFunc.md b/Resources/Documentation/Gem/GEMglDepthFunc.md index d4e9df13d5..db5e7549d7 100644 --- a/Resources/Documentation/Gem/GEMglDepthFunc.md +++ b/Resources/Documentation/Gem/GEMglDepthFunc.md @@ -4,7 +4,7 @@ title: GEMglDepthFunc description: set the function used for depth buffer comparisons categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the depth comparison function. diff --git a/Resources/Documentation/Gem/GEMglDepthMask.md b/Resources/Documentation/Gem/GEMglDepthMask.md index c4764abaaa..2058fda130 100644 --- a/Resources/Documentation/Gem/GEMglDepthMask.md +++ b/Resources/Documentation/Gem/GEMglDepthMask.md @@ -4,7 +4,7 @@ title: GEMglDepthMask description: enable or disable writing into the depth buffer categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies whether the depth buffer is enabled for writing. diff --git a/Resources/Documentation/Gem/GEMglDepthRange.md b/Resources/Documentation/Gem/GEMglDepthRange.md index f75bd4b92d..e8c11f61bc 100644 --- a/Resources/Documentation/Gem/GEMglDepthRange.md +++ b/Resources/Documentation/Gem/GEMglDepthRange.md @@ -4,7 +4,7 @@ title: GEMglDepthRange description: specify mapping of depth values from normalized device coordinates to window coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the mapping of the near clipping plane to window coordinates. diff --git a/Resources/Documentation/Gem/GEMglDisable.md b/Resources/Documentation/Gem/GEMglDisable.md index 214d192085..b2e6fecffd 100644 --- a/Resources/Documentation/Gem/GEMglDisable.md +++ b/Resources/Documentation/Gem/GEMglDisable.md @@ -4,7 +4,7 @@ title: GEMglDisable description: disable server-side GL capabilities categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the GL capability to disable. diff --git a/Resources/Documentation/Gem/GEMglDisableClientState.md b/Resources/Documentation/Gem/GEMglDisableClientState.md index d59844a638..5313ac181d 100644 --- a/Resources/Documentation/Gem/GEMglDisableClientState.md +++ b/Resources/Documentation/Gem/GEMglDisableClientState.md @@ -4,7 +4,7 @@ title: GEMglDisableClientState description: disable client-side capability categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the array to disable. diff --git a/Resources/Documentation/Gem/GEMglDrawArrays.md b/Resources/Documentation/Gem/GEMglDrawArrays.md index 3d68b523a9..0f3f2095cb 100644 --- a/Resources/Documentation/Gem/GEMglDrawArrays.md +++ b/Resources/Documentation/Gem/GEMglDrawArrays.md @@ -4,7 +4,7 @@ title: GEMglDrawArrays description: render primitives from array data categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies what kind of primitives to render. diff --git a/Resources/Documentation/Gem/GEMglDrawBuffer.md b/Resources/Documentation/Gem/GEMglDrawBuffer.md index 2050de3c8a..16568fc493 100644 --- a/Resources/Documentation/Gem/GEMglDrawBuffer.md +++ b/Resources/Documentation/Gem/GEMglDrawBuffer.md @@ -4,7 +4,7 @@ title: GEMglDrawBuffer description: specify color buffers to draw into categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the color buffer. diff --git a/Resources/Documentation/Gem/GEMglDrawElements.md b/Resources/Documentation/Gem/GEMglDrawElements.md index f838cdb2bb..d66a1b3ef6 100644 --- a/Resources/Documentation/Gem/GEMglDrawElements.md +++ b/Resources/Documentation/Gem/GEMglDrawElements.md @@ -4,7 +4,7 @@ title: GEMglDrawElements description: render primitives from array data categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies what kind of primitives to render. diff --git a/Resources/Documentation/Gem/GEMglEdgeFlag.md b/Resources/Documentation/Gem/GEMglEdgeFlag.md index cca6139d32..7c4a712710 100644 --- a/Resources/Documentation/Gem/GEMglEdgeFlag.md +++ b/Resources/Documentation/Gem/GEMglEdgeFlag.md @@ -4,7 +4,7 @@ title: GEMglEdgeFlag description: set the current edge flag categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the current edge flag. diff --git a/Resources/Documentation/Gem/GEMglEnable.md b/Resources/Documentation/Gem/GEMglEnable.md index c1e01991c6..ff7fde1316 100644 --- a/Resources/Documentation/Gem/GEMglEnable.md +++ b/Resources/Documentation/Gem/GEMglEnable.md @@ -4,7 +4,7 @@ title: GEMglEnable description: enable server-side GL capabilities categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the GL capability to enable. diff --git a/Resources/Documentation/Gem/GEMglEnableClientState.md b/Resources/Documentation/Gem/GEMglEnableClientState.md index 27b58b5818..4784789ee1 100644 --- a/Resources/Documentation/Gem/GEMglEnableClientState.md +++ b/Resources/Documentation/Gem/GEMglEnableClientState.md @@ -4,7 +4,7 @@ title: GEMglEnableClientState description: enable client-side capability categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the array to enable. diff --git a/Resources/Documentation/Gem/GEMglEnd.md b/Resources/Documentation/Gem/GEMglEnd.md index 3158e26044..9e50a42309 100644 --- a/Resources/Documentation/Gem/GEMglEnd.md +++ b/Resources/Documentation/Gem/GEMglEnd.md @@ -4,7 +4,7 @@ title: GEMglEnd description: end the definition of a vertex, primitive, or pixel group categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/GEMglEndList.md b/Resources/Documentation/Gem/GEMglEndList.md index 3a59c64520..c5048e7ebd 100644 --- a/Resources/Documentation/Gem/GEMglEndList.md +++ b/Resources/Documentation/Gem/GEMglEndList.md @@ -4,7 +4,7 @@ title: GEMglEndList description: end the definition of a display list categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/GEMglEvalCoord1d.md b/Resources/Documentation/Gem/GEMglEvalCoord1d.md index cc29b91cf9..5aafbcfc76 100644 --- a/Resources/Documentation/Gem/GEMglEvalCoord1d.md +++ b/Resources/Documentation/Gem/GEMglEvalCoord1d.md @@ -4,7 +4,7 @@ title: GEMglEvalCoord1d description: evaluate enabled one-dimensional maps at the specified parametric coordinate categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the parametric coordinate at which to evaluate the maps. diff --git a/Resources/Documentation/Gem/GEMglEvalCoord1dv.md b/Resources/Documentation/Gem/GEMglEvalCoord1dv.md index ff75a99d8a..21d455f2b7 100644 --- a/Resources/Documentation/Gem/GEMglEvalCoord1dv.md +++ b/Resources/Documentation/Gem/GEMglEvalCoord1dv.md @@ -4,7 +4,7 @@ title: GEMglEvalCoord1dv description: evaluate enabled one-dimensional maps at the specified parametric coordinate categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the parametric coordinate at which to evaluate the maps. diff --git a/Resources/Documentation/Gem/GEMglEvalCoord1f.md b/Resources/Documentation/Gem/GEMglEvalCoord1f.md index ec91473a7d..c677b4b95c 100644 --- a/Resources/Documentation/Gem/GEMglEvalCoord1f.md +++ b/Resources/Documentation/Gem/GEMglEvalCoord1f.md @@ -4,7 +4,7 @@ title: GEMglEvalCoord1f description: evaluate enabled one-dimensional maps at the specified parametric coordinate categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the parametric coordinate at which to evaluate the maps. diff --git a/Resources/Documentation/Gem/GEMglEvalCoord1fv.md b/Resources/Documentation/Gem/GEMglEvalCoord1fv.md index b1e5a14d15..d370b95ceb 100644 --- a/Resources/Documentation/Gem/GEMglEvalCoord1fv.md +++ b/Resources/Documentation/Gem/GEMglEvalCoord1fv.md @@ -4,7 +4,7 @@ title: GEMglEvalCoord1fv description: evaluate enabled one-dimensional maps at the specified parametric coordinate categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the parametric coordinate at which to evaluate the maps. diff --git a/Resources/Documentation/Gem/GEMglEvalCoord2d.md b/Resources/Documentation/Gem/GEMglEvalCoord2d.md index 3e12215456..1790ca17a0 100644 --- a/Resources/Documentation/Gem/GEMglEvalCoord2d.md +++ b/Resources/Documentation/Gem/GEMglEvalCoord2d.md @@ -4,7 +4,7 @@ title: GEMglEvalCoord2d description: evaluate enabled two-dimensional maps at the specified parametric coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the parametric coordinates at which to evaluate the maps. diff --git a/Resources/Documentation/Gem/GEMglEvalCoord2dv.md b/Resources/Documentation/Gem/GEMglEvalCoord2dv.md index 10f24df1d4..f2cde20606 100644 --- a/Resources/Documentation/Gem/GEMglEvalCoord2dv.md +++ b/Resources/Documentation/Gem/GEMglEvalCoord2dv.md @@ -4,7 +4,7 @@ title: GEMglEvalCoord2dv description: evaluate enabled two-dimensional maps at the specified parametric coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the parametric coordinates at which to evaluate the maps. diff --git a/Resources/Documentation/Gem/GEMglEvalCoord2f.md b/Resources/Documentation/Gem/GEMglEvalCoord2f.md index f2776a24c3..639e555c1b 100644 --- a/Resources/Documentation/Gem/GEMglEvalCoord2f.md +++ b/Resources/Documentation/Gem/GEMglEvalCoord2f.md @@ -4,7 +4,7 @@ title: GEMglEvalCoord2f description: evaluate enabled two-dimensional maps at the specified parametric coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the parametric coordinates at which to evaluate the maps. diff --git a/Resources/Documentation/Gem/GEMglEvalCoord2fv.md b/Resources/Documentation/Gem/GEMglEvalCoord2fv.md index 2c2900842b..1eda41925a 100644 --- a/Resources/Documentation/Gem/GEMglEvalCoord2fv.md +++ b/Resources/Documentation/Gem/GEMglEvalCoord2fv.md @@ -4,7 +4,7 @@ title: GEMglEvalCoord2fv description: evaluate enabled two-dimensional maps at the specified parametric coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the parametric coordinates at which to evaluate the maps. diff --git a/Resources/Documentation/Gem/GEMglEvalMesh1.md b/Resources/Documentation/Gem/GEMglEvalMesh1.md index c6606d67c2..9904a62a40 100644 --- a/Resources/Documentation/Gem/GEMglEvalMesh1.md +++ b/Resources/Documentation/Gem/GEMglEvalMesh1.md @@ -4,7 +4,7 @@ title: GEMglEvalMesh1 description: compute a one-dimensional mesh categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the mesh type. diff --git a/Resources/Documentation/Gem/GEMglEvalMesh2.md b/Resources/Documentation/Gem/GEMglEvalMesh2.md index 5d70bd950d..60eb0ae559 100644 --- a/Resources/Documentation/Gem/GEMglEvalMesh2.md +++ b/Resources/Documentation/Gem/GEMglEvalMesh2.md @@ -4,7 +4,7 @@ title: GEMglEvalMesh2 description: compute a two-dimensional mesh categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the mesh type. diff --git a/Resources/Documentation/Gem/GEMglEvalPoint1.md b/Resources/Documentation/Gem/GEMglEvalPoint1.md index 531ae1e54d..a833bb22d3 100644 --- a/Resources/Documentation/Gem/GEMglEvalPoint1.md +++ b/Resources/Documentation/Gem/GEMglEvalPoint1.md @@ -4,7 +4,7 @@ title: GEMglEvalPoint1 description: compute a one-dimensional grid of points categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the starting grid coordinate. diff --git a/Resources/Documentation/Gem/GEMglEvalPoint2.md b/Resources/Documentation/Gem/GEMglEvalPoint2.md index 9b81991b96..d9a63fe5e6 100644 --- a/Resources/Documentation/Gem/GEMglEvalPoint2.md +++ b/Resources/Documentation/Gem/GEMglEvalPoint2.md @@ -4,7 +4,7 @@ title: GEMglEvalPoint2 description: compute a two-dimensional grid of points categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the starting grid coordinates. diff --git a/Resources/Documentation/Gem/GEMglFeedbackBuffer.md b/Resources/Documentation/Gem/GEMglFeedbackBuffer.md index 58779b86f6..ae5ed1a107 100644 --- a/Resources/Documentation/Gem/GEMglFeedbackBuffer.md +++ b/Resources/Documentation/Gem/GEMglFeedbackBuffer.md @@ -4,7 +4,7 @@ title: GEMglFeedbackBuffer description: returns information about primitive groups in the feedback buffer categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the size of the buffer. diff --git a/Resources/Documentation/Gem/GEMglFinish.md b/Resources/Documentation/Gem/GEMglFinish.md index 34ed6e5778..48405d0ecb 100644 --- a/Resources/Documentation/Gem/GEMglFinish.md +++ b/Resources/Documentation/Gem/GEMglFinish.md @@ -4,7 +4,7 @@ title: GEMglFinish description: block until all GL execution is complete categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/GEMglFlush.md b/Resources/Documentation/Gem/GEMglFlush.md index a12703b57d..525fc0f222 100644 --- a/Resources/Documentation/Gem/GEMglFlush.md +++ b/Resources/Documentation/Gem/GEMglFlush.md @@ -4,7 +4,7 @@ title: GEMglFlush description: force execution of GL commands in finite time categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/GEMglFogf.md b/Resources/Documentation/Gem/GEMglFogf.md index 4e2a0647ab..2be6ba0061 100644 --- a/Resources/Documentation/Gem/GEMglFogf.md +++ b/Resources/Documentation/Gem/GEMglFogf.md @@ -4,7 +4,7 @@ title: GEMglFogf description: specify fog parameters categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the fog parameter to be set. diff --git a/Resources/Documentation/Gem/GEMglFogfv.md b/Resources/Documentation/Gem/GEMglFogfv.md index e8a93102cb..0f08a4f741 100644 --- a/Resources/Documentation/Gem/GEMglFogfv.md +++ b/Resources/Documentation/Gem/GEMglFogfv.md @@ -4,7 +4,7 @@ title: GEMglFogfv description: specify fog parameters categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the fog parameter to be set. diff --git a/Resources/Documentation/Gem/GEMglFogi.md b/Resources/Documentation/Gem/GEMglFogi.md index f290f870a5..c63168d917 100644 --- a/Resources/Documentation/Gem/GEMglFogi.md +++ b/Resources/Documentation/Gem/GEMglFogi.md @@ -4,7 +4,7 @@ title: GEMglFogi description: specify fog parameters categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the fog parameter to be set. diff --git a/Resources/Documentation/Gem/GEMglFogiv.md b/Resources/Documentation/Gem/GEMglFogiv.md index ab74ec78d0..0d2443b97c 100644 --- a/Resources/Documentation/Gem/GEMglFogiv.md +++ b/Resources/Documentation/Gem/GEMglFogiv.md @@ -4,7 +4,7 @@ title: GEMglFogiv description: specify fog parameters categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the fog parameter to be set. diff --git a/Resources/Documentation/Gem/GEMglFrontFace.md b/Resources/Documentation/Gem/GEMglFrontFace.md index f03c32f577..7ca59cb8de 100644 --- a/Resources/Documentation/Gem/GEMglFrontFace.md +++ b/Resources/Documentation/Gem/GEMglFrontFace.md @@ -4,7 +4,7 @@ title: GEMglFrontFace description: define front- and back-facing polygons categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the orientation of front-facing polygons. diff --git a/Resources/Documentation/Gem/GEMglFrustum.md b/Resources/Documentation/Gem/GEMglFrustum.md index edf8f5178b..958952bda2 100644 --- a/Resources/Documentation/Gem/GEMglFrustum.md +++ b/Resources/Documentation/Gem/GEMglFrustum.md @@ -4,7 +4,7 @@ title: GEMglFrustum description: define a perspective matrix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the coordinates for the left and right vertical clipping planes. diff --git a/Resources/Documentation/Gem/GEMglGenLists.md b/Resources/Documentation/Gem/GEMglGenLists.md index 2afed81013..ed1b3fa916 100644 --- a/Resources/Documentation/Gem/GEMglGenLists.md +++ b/Resources/Documentation/Gem/GEMglGenLists.md @@ -4,7 +4,7 @@ title: GEMglGenLists description: generate a contiguous set of empty display lists categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the number of contiguous empty display lists to be generated. diff --git a/Resources/Documentation/Gem/GEMglGenProgramsARB.md b/Resources/Documentation/Gem/GEMglGenProgramsARB.md index cca3cf6007..116cc0400e 100644 --- a/Resources/Documentation/Gem/GEMglGenProgramsARB.md +++ b/Resources/Documentation/Gem/GEMglGenProgramsARB.md @@ -4,7 +4,7 @@ title: GEMglGenProgramsARB description: generate program object names categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the number of program object names to generate. diff --git a/Resources/Documentation/Gem/GEMglGenTextures.md b/Resources/Documentation/Gem/GEMglGenTextures.md index eac6ab4e4c..4b8eabe21e 100644 --- a/Resources/Documentation/Gem/GEMglGenTextures.md +++ b/Resources/Documentation/Gem/GEMglGenTextures.md @@ -4,7 +4,7 @@ title: GEMglGenTextures description: generate texture names categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the number of texture names to generate. diff --git a/Resources/Documentation/Gem/GEMglGenerateMipmap.md b/Resources/Documentation/Gem/GEMglGenerateMipmap.md index 2b95059965..599eebe01c 100644 --- a/Resources/Documentation/Gem/GEMglGenerateMipmap.md +++ b/Resources/Documentation/Gem/GEMglGenerateMipmap.md @@ -4,7 +4,7 @@ title: GEMglGenerateMipmap description: generate mipmaps for a specified texture target categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target texture. diff --git a/Resources/Documentation/Gem/GEMglGetError.md b/Resources/Documentation/Gem/GEMglGetError.md index a9a8420ff4..67b009992c 100644 --- a/Resources/Documentation/Gem/GEMglGetError.md +++ b/Resources/Documentation/Gem/GEMglGetError.md @@ -4,7 +4,7 @@ title: GEMglGetError description: return error information categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/GEMglGetFloatv.md b/Resources/Documentation/Gem/GEMglGetFloatv.md index 5c2a22c9f4..6395158c1b 100644 --- a/Resources/Documentation/Gem/GEMglGetFloatv.md +++ b/Resources/Documentation/Gem/GEMglGetFloatv.md @@ -4,7 +4,7 @@ title: GEMglGetFloatv description: return the value or values of a selected parameter categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the parameter value to retrieve. diff --git a/Resources/Documentation/Gem/GEMglGetIntegerv.md b/Resources/Documentation/Gem/GEMglGetIntegerv.md index b4e899e4ff..69c010c285 100644 --- a/Resources/Documentation/Gem/GEMglGetIntegerv.md +++ b/Resources/Documentation/Gem/GEMglGetIntegerv.md @@ -4,7 +4,7 @@ title: GEMglGetIntegerv description: return the value or values of a selected parameter categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the parameter value to retrieve. diff --git a/Resources/Documentation/Gem/GEMglGetMapdv.md b/Resources/Documentation/Gem/GEMglGetMapdv.md index 3b25d5eee9..73cc52193d 100644 --- a/Resources/Documentation/Gem/GEMglGetMapdv.md +++ b/Resources/Documentation/Gem/GEMglGetMapdv.md @@ -4,7 +4,7 @@ title: GEMglGetMapdv description: return the coefficients of the specified map categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the map to query. diff --git a/Resources/Documentation/Gem/GEMglGetMapfv.md b/Resources/Documentation/Gem/GEMglGetMapfv.md index 7f760ba3d2..dc238fa904 100644 --- a/Resources/Documentation/Gem/GEMglGetMapfv.md +++ b/Resources/Documentation/Gem/GEMglGetMapfv.md @@ -4,7 +4,7 @@ title: GEMglGetMapfv description: return the coefficients of the specified map categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the map to query. diff --git a/Resources/Documentation/Gem/GEMglGetMapiv.md b/Resources/Documentation/Gem/GEMglGetMapiv.md index bc96d12141..179cd2c104 100644 --- a/Resources/Documentation/Gem/GEMglGetMapiv.md +++ b/Resources/Documentation/Gem/GEMglGetMapiv.md @@ -4,7 +4,7 @@ title: GEMglGetMapiv description: return the coefficients of the specified map categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the map to query. diff --git a/Resources/Documentation/Gem/GEMglGetPointerv.md b/Resources/Documentation/Gem/GEMglGetPointerv.md index 12dbbad585..27c7e7c898 100644 --- a/Resources/Documentation/Gem/GEMglGetPointerv.md +++ b/Resources/Documentation/Gem/GEMglGetPointerv.md @@ -4,7 +4,7 @@ title: GEMglGetPointerv description: return the address of the specified pointer categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the pointer to be returned. diff --git a/Resources/Documentation/Gem/GEMglGetString.md b/Resources/Documentation/Gem/GEMglGetString.md index 0b45274ad8..3750405149 100644 --- a/Resources/Documentation/Gem/GEMglGetString.md +++ b/Resources/Documentation/Gem/GEMglGetString.md @@ -4,7 +4,7 @@ title: GEMglGetString description: return a string describing the current GL connection categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the information to return. diff --git a/Resources/Documentation/Gem/GEMglHint.md b/Resources/Documentation/Gem/GEMglHint.md index d133852201..a46126dcbc 100644 --- a/Resources/Documentation/Gem/GEMglHint.md +++ b/Resources/Documentation/Gem/GEMglHint.md @@ -4,7 +4,7 @@ title: GEMglHint description: specify implementation-specific hints categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target. diff --git a/Resources/Documentation/Gem/GEMglIndexMask.md b/Resources/Documentation/Gem/GEMglIndexMask.md index 2dd5510ac0..397e044e06 100644 --- a/Resources/Documentation/Gem/GEMglIndexMask.md +++ b/Resources/Documentation/Gem/GEMglIndexMask.md @@ -4,7 +4,7 @@ title: GEMglIndexMask description: set the write enable mask for the color index buffers categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies a bit mask to enable and disable writing of individual bits in the color index buffers. diff --git a/Resources/Documentation/Gem/GEMglIndexd.md b/Resources/Documentation/Gem/GEMglIndexd.md index ead42eda3b..bbaabe5e6e 100644 --- a/Resources/Documentation/Gem/GEMglIndexd.md +++ b/Resources/Documentation/Gem/GEMglIndexd.md @@ -4,7 +4,7 @@ title: GEMglIndexd description: set the current color index categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the color index. diff --git a/Resources/Documentation/Gem/GEMglIndexdv.md b/Resources/Documentation/Gem/GEMglIndexdv.md index 3a1fe821d4..7526a5c2b4 100644 --- a/Resources/Documentation/Gem/GEMglIndexdv.md +++ b/Resources/Documentation/Gem/GEMglIndexdv.md @@ -4,7 +4,7 @@ title: GEMglIndexdv description: set the current color index categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the color index. diff --git a/Resources/Documentation/Gem/GEMglIndexf.md b/Resources/Documentation/Gem/GEMglIndexf.md index b13cde343e..e9d386543e 100644 --- a/Resources/Documentation/Gem/GEMglIndexf.md +++ b/Resources/Documentation/Gem/GEMglIndexf.md @@ -4,7 +4,7 @@ title: GEMglIndexf description: set the current color index categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the color index. diff --git a/Resources/Documentation/Gem/GEMglIndexfv.md b/Resources/Documentation/Gem/GEMglIndexfv.md index 0f9df2d74d..c4a3d2525b 100644 --- a/Resources/Documentation/Gem/GEMglIndexfv.md +++ b/Resources/Documentation/Gem/GEMglIndexfv.md @@ -4,7 +4,7 @@ title: GEMglIndexfv description: set the current color index categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the color index. diff --git a/Resources/Documentation/Gem/GEMglIndexi.md b/Resources/Documentation/Gem/GEMglIndexi.md index f9af4e40d3..3157cceb31 100644 --- a/Resources/Documentation/Gem/GEMglIndexi.md +++ b/Resources/Documentation/Gem/GEMglIndexi.md @@ -4,7 +4,7 @@ title: GEMglIndexi description: set the current color index categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the color index. diff --git a/Resources/Documentation/Gem/GEMglIndexiv.md b/Resources/Documentation/Gem/GEMglIndexiv.md index ba84920b17..70563ff49d 100644 --- a/Resources/Documentation/Gem/GEMglIndexiv.md +++ b/Resources/Documentation/Gem/GEMglIndexiv.md @@ -4,7 +4,7 @@ title: GEMglIndexiv description: set the current color index categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the color index. diff --git a/Resources/Documentation/Gem/GEMglIndexs.md b/Resources/Documentation/Gem/GEMglIndexs.md index d3da571e3e..42463a55a4 100644 --- a/Resources/Documentation/Gem/GEMglIndexs.md +++ b/Resources/Documentation/Gem/GEMglIndexs.md @@ -4,7 +4,7 @@ title: GEMglIndexs description: set the current color index categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the color index. diff --git a/Resources/Documentation/Gem/GEMglIndexsv.md b/Resources/Documentation/Gem/GEMglIndexsv.md index 25181a706c..e9fffd5bfe 100644 --- a/Resources/Documentation/Gem/GEMglIndexsv.md +++ b/Resources/Documentation/Gem/GEMglIndexsv.md @@ -4,7 +4,7 @@ title: GEMglIndexsv description: set the current color index categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the color index. diff --git a/Resources/Documentation/Gem/GEMglIndexub.md b/Resources/Documentation/Gem/GEMglIndexub.md index 3f10563500..0299bbaa55 100644 --- a/Resources/Documentation/Gem/GEMglIndexub.md +++ b/Resources/Documentation/Gem/GEMglIndexub.md @@ -4,7 +4,7 @@ title: GEMglIndexub description: set the current color index categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the color index. diff --git a/Resources/Documentation/Gem/GEMglIndexubv.md b/Resources/Documentation/Gem/GEMglIndexubv.md index c104c47078..1cf38cb222 100644 --- a/Resources/Documentation/Gem/GEMglIndexubv.md +++ b/Resources/Documentation/Gem/GEMglIndexubv.md @@ -4,7 +4,7 @@ title: GEMglIndexubv description: set the current color index categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the color index. diff --git a/Resources/Documentation/Gem/GEMglInitNames.md b/Resources/Documentation/Gem/GEMglInitNames.md index 21d870e9d2..c2c1fc4ad7 100644 --- a/Resources/Documentation/Gem/GEMglInitNames.md +++ b/Resources/Documentation/Gem/GEMglInitNames.md @@ -4,7 +4,7 @@ title: GEMglInitNames description: initialize the name stack categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/GEMglIsEnabled.md b/Resources/Documentation/Gem/GEMglIsEnabled.md index 4ecf1b05f7..0c0f1d3568 100644 --- a/Resources/Documentation/Gem/GEMglIsEnabled.md +++ b/Resources/Documentation/Gem/GEMglIsEnabled.md @@ -4,7 +4,7 @@ title: GEMglIsEnabled description: test whether a specific GL capability is enabled categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the capability to test. diff --git a/Resources/Documentation/Gem/GEMglIsList.md b/Resources/Documentation/Gem/GEMglIsList.md index e4f296a15e..78fa72b175 100644 --- a/Resources/Documentation/Gem/GEMglIsList.md +++ b/Resources/Documentation/Gem/GEMglIsList.md @@ -4,7 +4,7 @@ title: GEMglIsList description: test if a name corresponds to a display list categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the name of a display list. diff --git a/Resources/Documentation/Gem/GEMglIsTexture.md b/Resources/Documentation/Gem/GEMglIsTexture.md index 3bd459b9b8..9fb316247c 100644 --- a/Resources/Documentation/Gem/GEMglIsTexture.md +++ b/Resources/Documentation/Gem/GEMglIsTexture.md @@ -4,7 +4,7 @@ title: GEMglIsTexture description: determine if a name corresponds to a texture categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the name of a texture. diff --git a/Resources/Documentation/Gem/GEMglLightModelf.md b/Resources/Documentation/Gem/GEMglLightModelf.md index 8717981b54..191834eb7c 100644 --- a/Resources/Documentation/Gem/GEMglLightModelf.md +++ b/Resources/Documentation/Gem/GEMglLightModelf.md @@ -4,7 +4,7 @@ title: GEMglLightModelf description: set the value of a light model parameter categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the light model parameter. diff --git a/Resources/Documentation/Gem/GEMglLightModeli.md b/Resources/Documentation/Gem/GEMglLightModeli.md index de7660f95d..83939c2dd5 100644 --- a/Resources/Documentation/Gem/GEMglLightModeli.md +++ b/Resources/Documentation/Gem/GEMglLightModeli.md @@ -4,7 +4,7 @@ title: GEMglLightModeli description: set the value of a light model parameter categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the light model parameter. diff --git a/Resources/Documentation/Gem/GEMglLightf.md b/Resources/Documentation/Gem/GEMglLightf.md index 751fe2ec08..0e4a26f292 100644 --- a/Resources/Documentation/Gem/GEMglLightf.md +++ b/Resources/Documentation/Gem/GEMglLightf.md @@ -4,7 +4,7 @@ title: GEMglLightf description: set the value of a light source parameter categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the light source. diff --git a/Resources/Documentation/Gem/GEMglLighti.md b/Resources/Documentation/Gem/GEMglLighti.md index 94574700c2..d1482ca9f0 100644 --- a/Resources/Documentation/Gem/GEMglLighti.md +++ b/Resources/Documentation/Gem/GEMglLighti.md @@ -4,7 +4,7 @@ title: GEMglLighti description: set the value of a light source parameter categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the light source. diff --git a/Resources/Documentation/Gem/GEMglLineStipple.md b/Resources/Documentation/Gem/GEMglLineStipple.md index e32a75a489..7f43ab5b40 100644 --- a/Resources/Documentation/Gem/GEMglLineStipple.md +++ b/Resources/Documentation/Gem/GEMglLineStipple.md @@ -4,7 +4,7 @@ title: GEMglLineStipple description: specify the line stipple pattern categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the number of bits in the stipple pattern. diff --git a/Resources/Documentation/Gem/GEMglLineWidth.md b/Resources/Documentation/Gem/GEMglLineWidth.md index c9e62f9cfa..0921529295 100644 --- a/Resources/Documentation/Gem/GEMglLineWidth.md +++ b/Resources/Documentation/Gem/GEMglLineWidth.md @@ -4,7 +4,7 @@ title: GEMglLineWidth description: specify the width of rasterized lines categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the width of rasterized lines. diff --git a/Resources/Documentation/Gem/GEMglLoadIdentity.md b/Resources/Documentation/Gem/GEMglLoadIdentity.md index eeeab01588..4924b9eede 100644 --- a/Resources/Documentation/Gem/GEMglLoadIdentity.md +++ b/Resources/Documentation/Gem/GEMglLoadIdentity.md @@ -4,7 +4,7 @@ title: GEMglLoadIdentity description: replace the current matrix with the identity matrix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/GEMglLoadMatrixd.md b/Resources/Documentation/Gem/GEMglLoadMatrixd.md index b4b73ef5e4..e5edd9afa6 100644 --- a/Resources/Documentation/Gem/GEMglLoadMatrixd.md +++ b/Resources/Documentation/Gem/GEMglLoadMatrixd.md @@ -4,7 +4,7 @@ title: GEMglLoadMatrixd description: replace the current matrix with a double-precision matrix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 column-major matrix. diff --git a/Resources/Documentation/Gem/GEMglLoadMatrixf.md b/Resources/Documentation/Gem/GEMglLoadMatrixf.md index 8421f90a5a..0a27340012 100644 --- a/Resources/Documentation/Gem/GEMglLoadMatrixf.md +++ b/Resources/Documentation/Gem/GEMglLoadMatrixf.md @@ -4,7 +4,7 @@ title: GEMglLoadMatrixf description: replace the current matrix with a single-precision matrix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 column-major matrix. diff --git a/Resources/Documentation/Gem/GEMglLoadName.md b/Resources/Documentation/Gem/GEMglLoadName.md index fb804e00d5..5c6882c337 100644 --- a/Resources/Documentation/Gem/GEMglLoadName.md +++ b/Resources/Documentation/Gem/GEMglLoadName.md @@ -4,7 +4,7 @@ title: GEMglLoadName description: load a single unsigned integer value into the name stack categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the name to be loaded into the name stack. diff --git a/Resources/Documentation/Gem/GEMglLoadTransposeMatrixd.md b/Resources/Documentation/Gem/GEMglLoadTransposeMatrixd.md index b781fb7903..b797795e3f 100644 --- a/Resources/Documentation/Gem/GEMglLoadTransposeMatrixd.md +++ b/Resources/Documentation/Gem/GEMglLoadTransposeMatrixd.md @@ -4,7 +4,7 @@ title: GEMglLoadTransposeMatrixd description: replace the current matrix with the transpose of a double-precision matrix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 row-major matrix. diff --git a/Resources/Documentation/Gem/GEMglLoadTransposeMatrixf.md b/Resources/Documentation/Gem/GEMglLoadTransposeMatrixf.md index b29238ef65..dd681f7409 100644 --- a/Resources/Documentation/Gem/GEMglLoadTransposeMatrixf.md +++ b/Resources/Documentation/Gem/GEMglLoadTransposeMatrixf.md @@ -4,7 +4,7 @@ title: GEMglLoadTransposeMatrixf description: replace the current matrix with the transpose of a single-precision matrix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 row-major matrix. diff --git a/Resources/Documentation/Gem/GEMglLogicOp.md b/Resources/Documentation/Gem/GEMglLogicOp.md index 3afb08cf95..cc38932a03 100644 --- a/Resources/Documentation/Gem/GEMglLogicOp.md +++ b/Resources/Documentation/Gem/GEMglLogicOp.md @@ -4,7 +4,7 @@ title: GEMglLogicOp description: set the pixel transfer operation for rendering categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies a symbolic constant that selects a logical operation. diff --git a/Resources/Documentation/Gem/GEMglMap1d.md b/Resources/Documentation/Gem/GEMglMap1d.md index a061511cde..3e399c57b9 100644 --- a/Resources/Documentation/Gem/GEMglMap1d.md +++ b/Resources/Documentation/Gem/GEMglMap1d.md @@ -4,7 +4,7 @@ title: GEMglMap1d description: define a one-dimensional evaluator categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the kind of values being mapped. diff --git a/Resources/Documentation/Gem/GEMglMap1f.md b/Resources/Documentation/Gem/GEMglMap1f.md index 78368279a6..e6e00b4750 100644 --- a/Resources/Documentation/Gem/GEMglMap1f.md +++ b/Resources/Documentation/Gem/GEMglMap1f.md @@ -4,7 +4,7 @@ title: GEMglMap1f description: define a one-dimensional evaluator categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the kind of values being mapped. diff --git a/Resources/Documentation/Gem/GEMglMap2d.md b/Resources/Documentation/Gem/GEMglMap2d.md index c25983d700..4566940862 100644 --- a/Resources/Documentation/Gem/GEMglMap2d.md +++ b/Resources/Documentation/Gem/GEMglMap2d.md @@ -4,7 +4,7 @@ title: GEMglMap2d description: define a two-dimensional evaluator categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the kind of values being mapped. diff --git a/Resources/Documentation/Gem/GEMglMap2f.md b/Resources/Documentation/Gem/GEMglMap2f.md index 7feb928f8e..3af26195ad 100644 --- a/Resources/Documentation/Gem/GEMglMap2f.md +++ b/Resources/Documentation/Gem/GEMglMap2f.md @@ -4,7 +4,7 @@ title: GEMglMap2f description: define a two-dimensional evaluator categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the kind of values being mapped. diff --git a/Resources/Documentation/Gem/GEMglMapGrid1d.md b/Resources/Documentation/Gem/GEMglMapGrid1d.md index 02899519b3..eafdae98e4 100644 --- a/Resources/Documentation/Gem/GEMglMapGrid1d.md +++ b/Resources/Documentation/Gem/GEMglMapGrid1d.md @@ -4,7 +4,7 @@ title: GEMglMapGrid1d description: define a one-dimensional grid for evaluators categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the number of partitions in the grid. diff --git a/Resources/Documentation/Gem/GEMglMapGrid1f.md b/Resources/Documentation/Gem/GEMglMapGrid1f.md index 5d151493ea..927f7be92b 100644 --- a/Resources/Documentation/Gem/GEMglMapGrid1f.md +++ b/Resources/Documentation/Gem/GEMglMapGrid1f.md @@ -4,7 +4,7 @@ title: GEMglMapGrid1f description: define a one-dimensional grid for evaluators categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the number of partitions in the grid. diff --git a/Resources/Documentation/Gem/GEMglMapGrid2d.md b/Resources/Documentation/Gem/GEMglMapGrid2d.md index c7b182575a..83270cc49b 100644 --- a/Resources/Documentation/Gem/GEMglMapGrid2d.md +++ b/Resources/Documentation/Gem/GEMglMapGrid2d.md @@ -4,7 +4,7 @@ title: GEMglMapGrid2d description: define a two-dimensional grid for evaluators categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the number of partitions in the u direction of the grid. diff --git a/Resources/Documentation/Gem/GEMglMapGrid2f.md b/Resources/Documentation/Gem/GEMglMapGrid2f.md index 5b4ac50288..99c94cb046 100644 --- a/Resources/Documentation/Gem/GEMglMapGrid2f.md +++ b/Resources/Documentation/Gem/GEMglMapGrid2f.md @@ -4,7 +4,7 @@ title: GEMglMapGrid2f description: define a two-dimensional grid for evaluators categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the number of partitions in the u direction of the grid. diff --git a/Resources/Documentation/Gem/GEMglMaterialf.md b/Resources/Documentation/Gem/GEMglMaterialf.md index 34011a3ed2..e60195d5fe 100644 --- a/Resources/Documentation/Gem/GEMglMaterialf.md +++ b/Resources/Documentation/Gem/GEMglMaterialf.md @@ -4,7 +4,7 @@ title: GEMglMaterialf description: specify material parameters for lighting categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the face and the material parameter. diff --git a/Resources/Documentation/Gem/GEMglMaterialfv.md b/Resources/Documentation/Gem/GEMglMaterialfv.md index 632a9cb00b..d08068dcd7 100644 --- a/Resources/Documentation/Gem/GEMglMaterialfv.md +++ b/Resources/Documentation/Gem/GEMglMaterialfv.md @@ -4,7 +4,7 @@ title: GEMglMaterialfv description: specify material parameters for lighting with float values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the face and the material parameter. diff --git a/Resources/Documentation/Gem/GEMglMateriali.md b/Resources/Documentation/Gem/GEMglMateriali.md index 368c6b303f..35a1626c25 100644 --- a/Resources/Documentation/Gem/GEMglMateriali.md +++ b/Resources/Documentation/Gem/GEMglMateriali.md @@ -4,7 +4,7 @@ title: GEMglMateriali description: specify material parameters for lighting with integer values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the face and the material parameter. diff --git a/Resources/Documentation/Gem/GEMglMatrixMode.md b/Resources/Documentation/Gem/GEMglMatrixMode.md index 69d54c6698..e0508be70e 100644 --- a/Resources/Documentation/Gem/GEMglMatrixMode.md +++ b/Resources/Documentation/Gem/GEMglMatrixMode.md @@ -4,7 +4,7 @@ title: GEMglMatrixMode description: set the current matrix mode categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the matrix mode. diff --git a/Resources/Documentation/Gem/GEMglMultMatrixd.md b/Resources/Documentation/Gem/GEMglMultMatrixd.md index 65de835552..2f2ab9b4e4 100644 --- a/Resources/Documentation/Gem/GEMglMultMatrixd.md +++ b/Resources/Documentation/Gem/GEMglMultMatrixd.md @@ -4,7 +4,7 @@ title: GEMglMultMatrixd description: multiply the current matrix with a double-precision matrix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 column-major matrix. diff --git a/Resources/Documentation/Gem/GEMglMultMatrixf.md b/Resources/Documentation/Gem/GEMglMultMatrixf.md index 35638230cd..cb842814da 100644 --- a/Resources/Documentation/Gem/GEMglMultMatrixf.md +++ b/Resources/Documentation/Gem/GEMglMultMatrixf.md @@ -4,7 +4,7 @@ title: GEMglMultMatrixf description: multiply the current matrix with a single-precision matrix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 column-major matrix. diff --git a/Resources/Documentation/Gem/GEMglMultTransposeMatrixf.md b/Resources/Documentation/Gem/GEMglMultTransposeMatrixf.md index 7516d1b206..c2e31fe93e 100644 --- a/Resources/Documentation/Gem/GEMglMultTransposeMatrixf.md +++ b/Resources/Documentation/Gem/GEMglMultTransposeMatrixf.md @@ -4,7 +4,7 @@ title: GEMglMultTransposeMatrixf description: multiply the current matrix with the transpose of a single-precision matrix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies a pointer to sixteen consecutive values, which are used as the elements of a 4x4 column-major matrix. diff --git a/Resources/Documentation/Gem/GEMglMultiTexCoord2f.md b/Resources/Documentation/Gem/GEMglMultiTexCoord2f.md index 2011be45c0..59a5234afb 100644 --- a/Resources/Documentation/Gem/GEMglMultiTexCoord2f.md +++ b/Resources/Documentation/Gem/GEMglMultiTexCoord2f.md @@ -4,7 +4,7 @@ title: GEMglMultiTexCoord2f description: set the texture coordinates for a specified texture unit with two float values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the texture unit. diff --git a/Resources/Documentation/Gem/GEMglMultiTexCoord2fARB.md b/Resources/Documentation/Gem/GEMglMultiTexCoord2fARB.md index 9a2d2accc8..5d6db52d6c 100644 --- a/Resources/Documentation/Gem/GEMglMultiTexCoord2fARB.md +++ b/Resources/Documentation/Gem/GEMglMultiTexCoord2fARB.md @@ -4,7 +4,7 @@ title: GEMglMultiTexCoord2fARB description: set the texture coordinates for a specified texture unit with two float values (ARB extension) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the texture unit. diff --git a/Resources/Documentation/Gem/GEMglNewList.md b/Resources/Documentation/Gem/GEMglNewList.md index 8cba105978..3e59f28efe 100644 --- a/Resources/Documentation/Gem/GEMglNewList.md +++ b/Resources/Documentation/Gem/GEMglNewList.md @@ -4,7 +4,7 @@ title: GEMglNewList description: create or replace a display list categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies a unique integer that represents the display list. diff --git a/Resources/Documentation/Gem/GEMglNormal3b.md b/Resources/Documentation/Gem/GEMglNormal3b.md index f683a2e6e0..d1c2e2ad7a 100644 --- a/Resources/Documentation/Gem/GEMglNormal3b.md +++ b/Resources/Documentation/Gem/GEMglNormal3b.md @@ -4,7 +4,7 @@ title: GEMglNormal3b description: set the current normal vector with three signed byte values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z components of the current normal vector. diff --git a/Resources/Documentation/Gem/GEMglNormal3bv.md b/Resources/Documentation/Gem/GEMglNormal3bv.md index 32fe888567..6b87b64f2f 100644 --- a/Resources/Documentation/Gem/GEMglNormal3bv.md +++ b/Resources/Documentation/Gem/GEMglNormal3bv.md @@ -4,7 +4,7 @@ title: GEMglNormal3bv description: set the current normal vector with a pointer to three signed byte values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: pointer description: Specifies a pointer to an array of three signed bytes. diff --git a/Resources/Documentation/Gem/GEMglNormal3d.md b/Resources/Documentation/Gem/GEMglNormal3d.md index 17fc3ff9b2..1dc21b768c 100644 --- a/Resources/Documentation/Gem/GEMglNormal3d.md +++ b/Resources/Documentation/Gem/GEMglNormal3d.md @@ -4,7 +4,7 @@ title: GEMglNormal3d description: set the current normal vector with three double-precision values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z components of the current normal vector. diff --git a/Resources/Documentation/Gem/GEMglNormal3dv.md b/Resources/Documentation/Gem/GEMglNormal3dv.md index c0434fe24d..8cd7e5cbd9 100644 --- a/Resources/Documentation/Gem/GEMglNormal3dv.md +++ b/Resources/Documentation/Gem/GEMglNormal3dv.md @@ -4,7 +4,7 @@ title: GEMglNormal3dv description: set the current normal vector with a pointer to three double-precision values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: pointer description: Specifies a pointer to an array of three double-precision values. diff --git a/Resources/Documentation/Gem/GEMglNormal3f.md b/Resources/Documentation/Gem/GEMglNormal3f.md index 858a1c95ef..02bbd95965 100644 --- a/Resources/Documentation/Gem/GEMglNormal3f.md +++ b/Resources/Documentation/Gem/GEMglNormal3f.md @@ -4,7 +4,7 @@ title: GEMglNormal3f description: set the current normal vector with three single-precision values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z components of the current normal vector. diff --git a/Resources/Documentation/Gem/GEMglNormal3fv.md b/Resources/Documentation/Gem/GEMglNormal3fv.md index dd0b9dd6b9..052229d7be 100644 --- a/Resources/Documentation/Gem/GEMglNormal3fv.md +++ b/Resources/Documentation/Gem/GEMglNormal3fv.md @@ -4,7 +4,7 @@ title: GEMglNormal3fv description: set the current normal vector with a pointer to three single-precision values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: pointer description: Specifies a pointer to an array of three single-precision values. diff --git a/Resources/Documentation/Gem/GEMglNormal3i.md b/Resources/Documentation/Gem/GEMglNormal3i.md index cacd97e093..cbec1a6964 100644 --- a/Resources/Documentation/Gem/GEMglNormal3i.md +++ b/Resources/Documentation/Gem/GEMglNormal3i.md @@ -4,7 +4,7 @@ title: GEMglNormal3i description: set the current normal vector with three integer values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z components of the current normal vector. diff --git a/Resources/Documentation/Gem/GEMglNormal3iv.md b/Resources/Documentation/Gem/GEMglNormal3iv.md index 71c6912ff7..8ff34dc662 100644 --- a/Resources/Documentation/Gem/GEMglNormal3iv.md +++ b/Resources/Documentation/Gem/GEMglNormal3iv.md @@ -4,7 +4,7 @@ title: GEMglNormal3iv description: set the current normal vector with a pointer to three integer values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: pointer description: Specifies a pointer to an array of three integers. diff --git a/Resources/Documentation/Gem/GEMglNormal3s.md b/Resources/Documentation/Gem/GEMglNormal3s.md index 450fa642c8..b443c5641d 100644 --- a/Resources/Documentation/Gem/GEMglNormal3s.md +++ b/Resources/Documentation/Gem/GEMglNormal3s.md @@ -4,7 +4,7 @@ title: GEMglNormal3s description: set the current normal vector with three short integer values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z components of the current normal vector. diff --git a/Resources/Documentation/Gem/GEMglNormal3sv.md b/Resources/Documentation/Gem/GEMglNormal3sv.md index 23c9c7de66..54e41dfbaa 100644 --- a/Resources/Documentation/Gem/GEMglNormal3sv.md +++ b/Resources/Documentation/Gem/GEMglNormal3sv.md @@ -4,7 +4,7 @@ title: GEMglNormal3sv description: set the current normal vector with a pointer to three short integer values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: pointer description: Specifies a pointer to an array of three short integers. diff --git a/Resources/Documentation/Gem/GEMglOrtho.md b/Resources/Documentation/Gem/GEMglOrtho.md index 0831f2f3d6..1676369dd4 100644 --- a/Resources/Documentation/Gem/GEMglOrtho.md +++ b/Resources/Documentation/Gem/GEMglOrtho.md @@ -4,7 +4,7 @@ title: GEMglOrtho description: multiply the current matrix with an orthographic matrix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the coordinates for the left and right vertical clipping planes. diff --git a/Resources/Documentation/Gem/GEMglPassThrough.md b/Resources/Documentation/Gem/GEMglPassThrough.md index f25a95321a..0a0df4b85e 100644 --- a/Resources/Documentation/Gem/GEMglPassThrough.md +++ b/Resources/Documentation/Gem/GEMglPassThrough.md @@ -4,7 +4,7 @@ title: GEMglPassThrough description: pass a token through the feedback pipeline categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies a token value to be passed through the feedback pipeline. diff --git a/Resources/Documentation/Gem/GEMglPixelStoref.md b/Resources/Documentation/Gem/GEMglPixelStoref.md index 849ca16f5a..1ea4e18ee9 100644 --- a/Resources/Documentation/Gem/GEMglPixelStoref.md +++ b/Resources/Documentation/Gem/GEMglPixelStoref.md @@ -4,7 +4,7 @@ title: GEMglPixelStoref description: set pixel storage modes for read pixels categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the symbolic name of the parameter to be set. diff --git a/Resources/Documentation/Gem/GEMglPixelStorei.md b/Resources/Documentation/Gem/GEMglPixelStorei.md index 72b5fa9a7a..bcaf28dc85 100644 --- a/Resources/Documentation/Gem/GEMglPixelStorei.md +++ b/Resources/Documentation/Gem/GEMglPixelStorei.md @@ -4,7 +4,7 @@ title: GEMglPixelStorei description: set pixel storage modes for read pixels (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the symbolic name of the parameter to be set. diff --git a/Resources/Documentation/Gem/GEMglPixelTransferf.md b/Resources/Documentation/Gem/GEMglPixelTransferf.md index 9db11dd5d0..684b464f89 100644 --- a/Resources/Documentation/Gem/GEMglPixelTransferf.md +++ b/Resources/Documentation/Gem/GEMglPixelTransferf.md @@ -4,7 +4,7 @@ title: GEMglPixelTransferf description: set pixel transfer modes for read pixels categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the symbolic name of the parameter to be set. diff --git a/Resources/Documentation/Gem/GEMglPixelTransferi.md b/Resources/Documentation/Gem/GEMglPixelTransferi.md index 86bbcdf405..7cb9eacdd6 100644 --- a/Resources/Documentation/Gem/GEMglPixelTransferi.md +++ b/Resources/Documentation/Gem/GEMglPixelTransferi.md @@ -4,7 +4,7 @@ title: GEMglPixelTransferi description: set pixel transfer modes for read pixels (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the symbolic name of the parameter to be set. diff --git a/Resources/Documentation/Gem/GEMglPixelZoom.md b/Resources/Documentation/Gem/GEMglPixelZoom.md index 99eae86ea7..a46e2de700 100644 --- a/Resources/Documentation/Gem/GEMglPixelZoom.md +++ b/Resources/Documentation/Gem/GEMglPixelZoom.md @@ -4,7 +4,7 @@ title: GEMglPixelZoom description: set pixel zoom factors for read pixels categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x and y zoom factors. diff --git a/Resources/Documentation/Gem/GEMglPointSize.md b/Resources/Documentation/Gem/GEMglPointSize.md index 3c12058111..ad962cd603 100644 --- a/Resources/Documentation/Gem/GEMglPointSize.md +++ b/Resources/Documentation/Gem/GEMglPointSize.md @@ -4,7 +4,7 @@ title: GEMglPointSize description: specify the rasterized point size categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the size of rasterized points. diff --git a/Resources/Documentation/Gem/GEMglPolygonMode.md b/Resources/Documentation/Gem/GEMglPolygonMode.md index 95d5957a23..0054de5591 100644 --- a/Resources/Documentation/Gem/GEMglPolygonMode.md +++ b/Resources/Documentation/Gem/GEMglPolygonMode.md @@ -4,7 +4,7 @@ title: GEMglPolygonMode description: set the front and back polygon fill mode categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the face for which the polygon mode is set. diff --git a/Resources/Documentation/Gem/GEMglPolygonOffset.md b/Resources/Documentation/Gem/GEMglPolygonOffset.md index e9479888b3..ed6796c21e 100644 --- a/Resources/Documentation/Gem/GEMglPolygonOffset.md +++ b/Resources/Documentation/Gem/GEMglPolygonOffset.md @@ -4,7 +4,7 @@ title: GEMglPolygonOffset description: set the scale and units used to calculate depth values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies a scale factor that is used to create a variable depth offset for each polygon. diff --git a/Resources/Documentation/Gem/GEMglPopAttrib.md b/Resources/Documentation/Gem/GEMglPopAttrib.md index 31dc49da25..5f97d30649 100644 --- a/Resources/Documentation/Gem/GEMglPopAttrib.md +++ b/Resources/Documentation/Gem/GEMglPopAttrib.md @@ -4,7 +4,7 @@ title: GEMglPopAttrib description: restore the state of the attribute stack categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: null inlets: 1st: diff --git a/Resources/Documentation/Gem/GEMglPopClientAttrib.md b/Resources/Documentation/Gem/GEMglPopClientAttrib.md index 45dbf50dcc..9903c4c650 100644 --- a/Resources/Documentation/Gem/GEMglPopClientAttrib.md +++ b/Resources/Documentation/Gem/GEMglPopClientAttrib.md @@ -4,7 +4,7 @@ title: GEMglPopClientAttrib description: restore the state of the client attribute stack categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: null inlets: 1st: diff --git a/Resources/Documentation/Gem/GEMglPopMatrix.md b/Resources/Documentation/Gem/GEMglPopMatrix.md index 3a8bd43866..1c57986b88 100644 --- a/Resources/Documentation/Gem/GEMglPopMatrix.md +++ b/Resources/Documentation/Gem/GEMglPopMatrix.md @@ -4,7 +4,7 @@ title: GEMglPopMatrix description: pop the current matrix stack categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: null inlets: 1st: diff --git a/Resources/Documentation/Gem/GEMglPopName.md b/Resources/Documentation/Gem/GEMglPopName.md index dceed74418..e3a799cfdd 100644 --- a/Resources/Documentation/Gem/GEMglPopName.md +++ b/Resources/Documentation/Gem/GEMglPopName.md @@ -4,7 +4,7 @@ title: GEMglPopName description: pop the top name from the name stack categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: null inlets: 1st: diff --git a/Resources/Documentation/Gem/GEMglPrioritizeTextures.md b/Resources/Documentation/Gem/GEMglPrioritizeTextures.md index d3b50f9c80..fe9374b35f 100644 --- a/Resources/Documentation/Gem/GEMglPrioritizeTextures.md +++ b/Resources/Documentation/Gem/GEMglPrioritizeTextures.md @@ -4,7 +4,7 @@ title: GEMglPrioritizeTextures description: set texture object priorities categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the number of texture names in textures. diff --git a/Resources/Documentation/Gem/GEMglProgramEnvParameter4dARB.md b/Resources/Documentation/Gem/GEMglProgramEnvParameter4dARB.md index a067ac7da5..90b2c8f2e5 100644 --- a/Resources/Documentation/Gem/GEMglProgramEnvParameter4dARB.md +++ b/Resources/Documentation/Gem/GEMglProgramEnvParameter4dARB.md @@ -4,7 +4,7 @@ title: GEMglProgramEnvParameter4dARB description: specify a double-precision environment parameter for a program object categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target program object. diff --git a/Resources/Documentation/Gem/GEMglProgramEnvParameter4fvARB.md b/Resources/Documentation/Gem/GEMglProgramEnvParameter4fvARB.md index 61998fb56f..08dc1d6bed 100644 --- a/Resources/Documentation/Gem/GEMglProgramEnvParameter4fvARB.md +++ b/Resources/Documentation/Gem/GEMglProgramEnvParameter4fvARB.md @@ -4,7 +4,7 @@ title: GEMglProgramEnvParameter4fvARB description: specify a float-precision environment parameter for a program object categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target program object. diff --git a/Resources/Documentation/Gem/GEMglProgramLocalParameter4fvARB.md b/Resources/Documentation/Gem/GEMglProgramLocalParameter4fvARB.md index e7f4afc2a7..293b1e06ab 100644 --- a/Resources/Documentation/Gem/GEMglProgramLocalParameter4fvARB.md +++ b/Resources/Documentation/Gem/GEMglProgramLocalParameter4fvARB.md @@ -4,7 +4,7 @@ title: GEMglProgramLocalParameter4fvARB description: specify a float-precision local parameter for a program object categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target program object. diff --git a/Resources/Documentation/Gem/GEMglProgramStringARB.md b/Resources/Documentation/Gem/GEMglProgramStringARB.md index afbbb4726a..62be2526d1 100644 --- a/Resources/Documentation/Gem/GEMglProgramStringARB.md +++ b/Resources/Documentation/Gem/GEMglProgramStringARB.md @@ -4,7 +4,7 @@ title: GEMglProgramStringARB description: load a program object with a string categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target program object. diff --git a/Resources/Documentation/Gem/GEMglPushAttrib.md b/Resources/Documentation/Gem/GEMglPushAttrib.md index 8539aff50f..467c87cbae 100644 --- a/Resources/Documentation/Gem/GEMglPushAttrib.md +++ b/Resources/Documentation/Gem/GEMglPushAttrib.md @@ -4,7 +4,7 @@ title: GEMglPushAttrib description: push and save attribute state categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies which groups of attributes to save in the display list. diff --git a/Resources/Documentation/Gem/GEMglPushClientAttrib.md b/Resources/Documentation/Gem/GEMglPushClientAttrib.md index 8560c0284e..545fd2416d 100644 --- a/Resources/Documentation/Gem/GEMglPushClientAttrib.md +++ b/Resources/Documentation/Gem/GEMglPushClientAttrib.md @@ -4,7 +4,7 @@ title: GEMglPushClientAttrib description: push and save client attribute state categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies which groups of client attributes to save. diff --git a/Resources/Documentation/Gem/GEMglPushMatrix.md b/Resources/Documentation/Gem/GEMglPushMatrix.md index 19aa4779e5..63d141cc27 100644 --- a/Resources/Documentation/Gem/GEMglPushMatrix.md +++ b/Resources/Documentation/Gem/GEMglPushMatrix.md @@ -4,7 +4,7 @@ title: GEMglPushMatrix description: push the current matrix stack categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: null inlets: 1st: diff --git a/Resources/Documentation/Gem/GEMglPushName.md b/Resources/Documentation/Gem/GEMglPushName.md index a1f1966243..94ee32f843 100644 --- a/Resources/Documentation/Gem/GEMglPushName.md +++ b/Resources/Documentation/Gem/GEMglPushName.md @@ -4,7 +4,7 @@ title: GEMglPushName description: push a name onto the name stack categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies a name to be pushed onto the name stack. diff --git a/Resources/Documentation/Gem/GEMglRasterPos2d.md b/Resources/Documentation/Gem/GEMglRasterPos2d.md index eb94099a46..0773e55f23 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos2d.md +++ b/Resources/Documentation/Gem/GEMglRasterPos2d.md @@ -4,7 +4,7 @@ title: GEMglRasterPos2d description: set the current raster position categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos2dv.md b/Resources/Documentation/Gem/GEMglRasterPos2dv.md index 9e69759fa4..a7d0fcdadb 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos2dv.md +++ b/Resources/Documentation/Gem/GEMglRasterPos2dv.md @@ -4,7 +4,7 @@ title: GEMglRasterPos2dv description: set the current raster position (double-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos2f.md b/Resources/Documentation/Gem/GEMglRasterPos2f.md index 5930cdcfec..42c69a9865 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos2f.md +++ b/Resources/Documentation/Gem/GEMglRasterPos2f.md @@ -4,7 +4,7 @@ title: GEMglRasterPos2f description: set the current raster position categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos2fv.md b/Resources/Documentation/Gem/GEMglRasterPos2fv.md index 9a0f8bb396..51f134585a 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos2fv.md +++ b/Resources/Documentation/Gem/GEMglRasterPos2fv.md @@ -4,7 +4,7 @@ title: GEMglRasterPos2fv description: set the current raster position (single-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos2i.md b/Resources/Documentation/Gem/GEMglRasterPos2i.md index 294ecac6fd..018da1378f 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos2i.md +++ b/Resources/Documentation/Gem/GEMglRasterPos2i.md @@ -4,7 +4,7 @@ title: GEMglRasterPos2i description: set the current raster position categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos2iv.md b/Resources/Documentation/Gem/GEMglRasterPos2iv.md index 1e0a3f98e4..995fc69a81 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos2iv.md +++ b/Resources/Documentation/Gem/GEMglRasterPos2iv.md @@ -4,7 +4,7 @@ title: GEMglRasterPos2iv description: set the current raster position (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos2s.md b/Resources/Documentation/Gem/GEMglRasterPos2s.md index 356e25a5e1..0b846306d8 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos2s.md +++ b/Resources/Documentation/Gem/GEMglRasterPos2s.md @@ -4,7 +4,7 @@ title: GEMglRasterPos2s description: set the current raster position categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos2sv.md b/Resources/Documentation/Gem/GEMglRasterPos2sv.md index 2ea10328f4..9796d62d51 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos2sv.md +++ b/Resources/Documentation/Gem/GEMglRasterPos2sv.md @@ -4,7 +4,7 @@ title: GEMglRasterPos2sv description: set the current raster position (short-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos3d.md b/Resources/Documentation/Gem/GEMglRasterPos3d.md index 0195e053d4..75804230e2 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos3d.md +++ b/Resources/Documentation/Gem/GEMglRasterPos3d.md @@ -4,7 +4,7 @@ title: GEMglRasterPos3d description: set the current raster position categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos3dv.md b/Resources/Documentation/Gem/GEMglRasterPos3dv.md index e98f0ac935..811b099a77 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos3dv.md +++ b/Resources/Documentation/Gem/GEMglRasterPos3dv.md @@ -4,7 +4,7 @@ title: GEMglRasterPos3dv description: set the current raster position (double-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos3f.md b/Resources/Documentation/Gem/GEMglRasterPos3f.md index 6c32c37abc..3a0e9804e9 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos3f.md +++ b/Resources/Documentation/Gem/GEMglRasterPos3f.md @@ -4,7 +4,7 @@ title: GEMglRasterPos3f description: set the current raster position categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos3fv.md b/Resources/Documentation/Gem/GEMglRasterPos3fv.md index 9277b31087..1edd4af942 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos3fv.md +++ b/Resources/Documentation/Gem/GEMglRasterPos3fv.md @@ -4,7 +4,7 @@ title: GEMglRasterPos3fv description: set the current raster position (single-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos3i.md b/Resources/Documentation/Gem/GEMglRasterPos3i.md index b27b9612e8..8f73384247 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos3i.md +++ b/Resources/Documentation/Gem/GEMglRasterPos3i.md @@ -4,7 +4,7 @@ title: GEMglRasterPos3i description: set the current raster position categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos3iv.md b/Resources/Documentation/Gem/GEMglRasterPos3iv.md index 357341e8a0..b48f5983e7 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos3iv.md +++ b/Resources/Documentation/Gem/GEMglRasterPos3iv.md @@ -4,7 +4,7 @@ title: GEMglRasterPos3iv description: set the current raster position (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos3s.md b/Resources/Documentation/Gem/GEMglRasterPos3s.md index a8b87b8b05..b31aca8b97 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos3s.md +++ b/Resources/Documentation/Gem/GEMglRasterPos3s.md @@ -4,7 +4,7 @@ title: GEMglRasterPos3s description: set the current raster position categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos3sv.md b/Resources/Documentation/Gem/GEMglRasterPos3sv.md index 4bfe5ea243..9dee2d8d5f 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos3sv.md +++ b/Resources/Documentation/Gem/GEMglRasterPos3sv.md @@ -4,7 +4,7 @@ title: GEMglRasterPos3sv description: set the current raster position (short-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos4d.md b/Resources/Documentation/Gem/GEMglRasterPos4d.md index ff1a532faa..5fe87ea479 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos4d.md +++ b/Resources/Documentation/Gem/GEMglRasterPos4d.md @@ -4,7 +4,7 @@ title: GEMglRasterPos4d description: set the current raster position categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos4dv.md b/Resources/Documentation/Gem/GEMglRasterPos4dv.md index 3867314b98..d42a45345d 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos4dv.md +++ b/Resources/Documentation/Gem/GEMglRasterPos4dv.md @@ -4,7 +4,7 @@ title: GEMglRasterPos4dv description: set the current raster position (double-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos4f.md b/Resources/Documentation/Gem/GEMglRasterPos4f.md index d85cd04353..68e1e700fc 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos4f.md +++ b/Resources/Documentation/Gem/GEMglRasterPos4f.md @@ -4,7 +4,7 @@ title: GEMglRasterPos4f description: set the current raster position categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos4fv.md b/Resources/Documentation/Gem/GEMglRasterPos4fv.md index 40e4f015e1..4100a0abeb 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos4fv.md +++ b/Resources/Documentation/Gem/GEMglRasterPos4fv.md @@ -4,7 +4,7 @@ title: GEMglRasterPos4fv description: set the current raster position (single-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos4i.md b/Resources/Documentation/Gem/GEMglRasterPos4i.md index 6c318e80bb..1c216f37a1 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos4i.md +++ b/Resources/Documentation/Gem/GEMglRasterPos4i.md @@ -4,7 +4,7 @@ title: GEMglRasterPos4i description: set the current raster position categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos4iv.md b/Resources/Documentation/Gem/GEMglRasterPos4iv.md index ff2a4a09b8..77e13b39fb 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos4iv.md +++ b/Resources/Documentation/Gem/GEMglRasterPos4iv.md @@ -4,7 +4,7 @@ title: GEMglRasterPos4iv description: set the current raster position (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos4s.md b/Resources/Documentation/Gem/GEMglRasterPos4s.md index dde18f4186..2e9b1f510d 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos4s.md +++ b/Resources/Documentation/Gem/GEMglRasterPos4s.md @@ -4,7 +4,7 @@ title: GEMglRasterPos4s description: set the current raster position categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRasterPos4sv.md b/Resources/Documentation/Gem/GEMglRasterPos4sv.md index 7eb535f984..c1a42380d5 100644 --- a/Resources/Documentation/Gem/GEMglRasterPos4sv.md +++ b/Resources/Documentation/Gem/GEMglRasterPos4sv.md @@ -4,7 +4,7 @@ title: GEMglRasterPos4sv description: set the current raster position (short-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates. diff --git a/Resources/Documentation/Gem/GEMglRectd.md b/Resources/Documentation/Gem/GEMglRectd.md index 7049d35f6b..af32a57280 100644 --- a/Resources/Documentation/Gem/GEMglRectd.md +++ b/Resources/Documentation/Gem/GEMglRectd.md @@ -4,7 +4,7 @@ title: GEMglRectd description: draw a rectangle categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates of the first corner of the rectangle. diff --git a/Resources/Documentation/Gem/GEMglRectf.md b/Resources/Documentation/Gem/GEMglRectf.md index 87ccb47c18..a95c2b3cf8 100644 --- a/Resources/Documentation/Gem/GEMglRectf.md +++ b/Resources/Documentation/Gem/GEMglRectf.md @@ -4,7 +4,7 @@ title: GEMglRectf description: draw a rectangle categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates of the first corner of the rectangle. diff --git a/Resources/Documentation/Gem/GEMglRecti.md b/Resources/Documentation/Gem/GEMglRecti.md index d475387b03..41b85cb2a2 100644 --- a/Resources/Documentation/Gem/GEMglRecti.md +++ b/Resources/Documentation/Gem/GEMglRecti.md @@ -4,7 +4,7 @@ title: GEMglRecti description: draw a rectangle categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z window coordinates of the first corner of the rectangle. diff --git a/Resources/Documentation/Gem/GEMglRects.md b/Resources/Documentation/Gem/GEMglRects.md index 1969a776f0..265fc4aa74 100644 --- a/Resources/Documentation/Gem/GEMglRects.md +++ b/Resources/Documentation/Gem/GEMglRects.md @@ -4,7 +4,7 @@ title: GEMglRects description: draw a rectangle categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: GLshort description: Specifies the x, y, and z window coordinates of the first corner of the rectangle. diff --git a/Resources/Documentation/Gem/GEMglRenderMode.md b/Resources/Documentation/Gem/GEMglRenderMode.md index b851710527..4778d09cce 100644 --- a/Resources/Documentation/Gem/GEMglRenderMode.md +++ b/Resources/Documentation/Gem/GEMglRenderMode.md @@ -4,7 +4,7 @@ title: GEMglRenderMode description: set OpenGL rendering mode categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: GLenum description: Specifies the rendering mode. diff --git a/Resources/Documentation/Gem/GEMglReportError.md b/Resources/Documentation/Gem/GEMglReportError.md index b4a707dc57..dda26c7eda 100644 --- a/Resources/Documentation/Gem/GEMglReportError.md +++ b/Resources/Documentation/Gem/GEMglReportError.md @@ -4,7 +4,7 @@ title: GEMglReportError description: get and clear OpenGL error flag categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics outlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/GEMglRotated.md b/Resources/Documentation/Gem/GEMglRotated.md index 153f0110ca..88c760fff3 100644 --- a/Resources/Documentation/Gem/GEMglRotated.md +++ b/Resources/Documentation/Gem/GEMglRotated.md @@ -4,7 +4,7 @@ title: GEMglRotated description: multiply the current matrix by a rotation matrix (double-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: GLdouble description: Specifies the angle of rotation, in degrees. diff --git a/Resources/Documentation/Gem/GEMglRotatef.md b/Resources/Documentation/Gem/GEMglRotatef.md index 14417be53b..07f866f80d 100644 --- a/Resources/Documentation/Gem/GEMglRotatef.md +++ b/Resources/Documentation/Gem/GEMglRotatef.md @@ -4,7 +4,7 @@ title: GEMglRotatef description: multiply the current matrix by a rotation matrix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: GLfloat description: Specifies the angle of rotation, in degrees. diff --git a/Resources/Documentation/Gem/GEMglScaled.md b/Resources/Documentation/Gem/GEMglScaled.md index 1175dfc308..eb04664cab 100644 --- a/Resources/Documentation/Gem/GEMglScaled.md +++ b/Resources/Documentation/Gem/GEMglScaled.md @@ -4,7 +4,7 @@ title: GEMglScaled description: multiply the current matrix by a general scaling matrix (double-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the scale factors along the x, y, and z axes, respectively. diff --git a/Resources/Documentation/Gem/GEMglScalef.md b/Resources/Documentation/Gem/GEMglScalef.md index 4d2421c7e8..87b1444f01 100644 --- a/Resources/Documentation/Gem/GEMglScalef.md +++ b/Resources/Documentation/Gem/GEMglScalef.md @@ -4,7 +4,7 @@ title: GEMglScalef description: multiply the current matrix by a general scaling matrix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the scale factors along the x, y, and z axes, respectively. diff --git a/Resources/Documentation/Gem/GEMglScissor.md b/Resources/Documentation/Gem/GEMglScissor.md index 0e82f8de78..e499ee9267 100644 --- a/Resources/Documentation/Gem/GEMglScissor.md +++ b/Resources/Documentation/Gem/GEMglScissor.md @@ -4,7 +4,7 @@ title: GEMglScissor description: define the scissor box categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the lower left corner of the scissor box. diff --git a/Resources/Documentation/Gem/GEMglSelectBuffer.md b/Resources/Documentation/Gem/GEMglSelectBuffer.md index d2a8545a2d..12cf93d637 100644 --- a/Resources/Documentation/Gem/GEMglSelectBuffer.md +++ b/Resources/Documentation/Gem/GEMglSelectBuffer.md @@ -4,7 +4,7 @@ title: GEMglSelectBuffer description: establish a buffer for selection mode categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the size of the selection buffer. diff --git a/Resources/Documentation/Gem/GEMglShadeModel.md b/Resources/Documentation/Gem/GEMglShadeModel.md index 7ee42a8ac4..da3c3b98bb 100644 --- a/Resources/Documentation/Gem/GEMglShadeModel.md +++ b/Resources/Documentation/Gem/GEMglShadeModel.md @@ -4,7 +4,7 @@ title: GEMglShadeModel description: select flat or smooth shading categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the shading model. diff --git a/Resources/Documentation/Gem/GEMglStencilFunc.md b/Resources/Documentation/Gem/GEMglStencilFunc.md index a45fc8a085..4b6c2c7d40 100644 --- a/Resources/Documentation/Gem/GEMglStencilFunc.md +++ b/Resources/Documentation/Gem/GEMglStencilFunc.md @@ -4,7 +4,7 @@ title: GEMglStencilFunc description: set front and back function and reference value for stencil testing categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the stencil test function. diff --git a/Resources/Documentation/Gem/GEMglStencilMask.md b/Resources/Documentation/Gem/GEMglStencilMask.md index a9defddcf0..8bf67da4e8 100644 --- a/Resources/Documentation/Gem/GEMglStencilMask.md +++ b/Resources/Documentation/Gem/GEMglStencilMask.md @@ -4,7 +4,7 @@ title: GEMglStencilMask description: control the writing of individual bits in the stencil planes categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the mask that is ANDed with the stencil value when it is written. diff --git a/Resources/Documentation/Gem/GEMglStencilOp.md b/Resources/Documentation/Gem/GEMglStencilOp.md index d0aaec7a08..433f7a9978 100644 --- a/Resources/Documentation/Gem/GEMglStencilOp.md +++ b/Resources/Documentation/Gem/GEMglStencilOp.md @@ -4,7 +4,7 @@ title: GEMglStencilOp description: set front and back stencil test actions categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the action to take when the stencil test fails. diff --git a/Resources/Documentation/Gem/GEMglTexCoord1d.md b/Resources/Documentation/Gem/GEMglTexCoord1d.md index b4589e25c7..4d681a4758 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord1d.md +++ b/Resources/Documentation/Gem/GEMglTexCoord1d.md @@ -4,7 +4,7 @@ title: GEMglTexCoord1d description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s texture coordinate. diff --git a/Resources/Documentation/Gem/GEMglTexCoord1dv.md b/Resources/Documentation/Gem/GEMglTexCoord1dv.md index dedbecdf92..96f61ab28c 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord1dv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord1dv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord1dv description: set the current texture coordinates (double-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s texture coordinate. diff --git a/Resources/Documentation/Gem/GEMglTexCoord1f.md b/Resources/Documentation/Gem/GEMglTexCoord1f.md index 6df59ffa18..1bd80ea5d5 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord1f.md +++ b/Resources/Documentation/Gem/GEMglTexCoord1f.md @@ -4,7 +4,7 @@ title: GEMglTexCoord1f description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s texture coordinate. diff --git a/Resources/Documentation/Gem/GEMglTexCoord1fv.md b/Resources/Documentation/Gem/GEMglTexCoord1fv.md index 92e73c3419..4a892f0b50 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord1fv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord1fv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord1fv description: set the current texture coordinates (floating-point version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s texture coordinate. diff --git a/Resources/Documentation/Gem/GEMglTexCoord1i.md b/Resources/Documentation/Gem/GEMglTexCoord1i.md index 3d85344acb..36c524bb9e 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord1i.md +++ b/Resources/Documentation/Gem/GEMglTexCoord1i.md @@ -4,7 +4,7 @@ title: GEMglTexCoord1i description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s texture coordinate. diff --git a/Resources/Documentation/Gem/GEMglTexCoord1iv.md b/Resources/Documentation/Gem/GEMglTexCoord1iv.md index 690427c656..a694c31a19 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord1iv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord1iv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord1iv description: set the current texture coordinates (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s texture coordinate. diff --git a/Resources/Documentation/Gem/GEMglTexCoord1s.md b/Resources/Documentation/Gem/GEMglTexCoord1s.md index fa9ab09e2d..fdd7db6399 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord1s.md +++ b/Resources/Documentation/Gem/GEMglTexCoord1s.md @@ -4,7 +4,7 @@ title: GEMglTexCoord1s description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s texture coordinate. diff --git a/Resources/Documentation/Gem/GEMglTexCoord1sv.md b/Resources/Documentation/Gem/GEMglTexCoord1sv.md index 74787faf96..1dccce3b92 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord1sv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord1sv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord1sv description: set the current texture coordinates (short integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s and t texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord2d.md b/Resources/Documentation/Gem/GEMglTexCoord2d.md index 54f1a85ce4..95f92884a3 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord2d.md +++ b/Resources/Documentation/Gem/GEMglTexCoord2d.md @@ -4,7 +4,7 @@ title: GEMglTexCoord2d description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s and t texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord2dv.md b/Resources/Documentation/Gem/GEMglTexCoord2dv.md index 1287c92bf7..9300e89fa5 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord2dv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord2dv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord2dv description: set the current texture coordinates (double-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s and t texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord2f.md b/Resources/Documentation/Gem/GEMglTexCoord2f.md index 5ddb45b187..b757c63565 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord2f.md +++ b/Resources/Documentation/Gem/GEMglTexCoord2f.md @@ -4,7 +4,7 @@ title: GEMglTexCoord2f description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s and t texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord2fv.md b/Resources/Documentation/Gem/GEMglTexCoord2fv.md index 78fa600497..a932591ddb 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord2fv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord2fv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord2fv description: set the current texture coordinates (floating-point version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s and t texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord2i.md b/Resources/Documentation/Gem/GEMglTexCoord2i.md index 2b1549fc27..487c22a002 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord2i.md +++ b/Resources/Documentation/Gem/GEMglTexCoord2i.md @@ -4,7 +4,7 @@ title: GEMglTexCoord2i description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, and r texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord2iv.md b/Resources/Documentation/Gem/GEMglTexCoord2iv.md index 0032f02b66..d3dd40f587 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord2iv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord2iv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord2iv description: set the current texture coordinates (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, and r texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord2s.md b/Resources/Documentation/Gem/GEMglTexCoord2s.md index e1214b988e..0bf446dbc0 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord2s.md +++ b/Resources/Documentation/Gem/GEMglTexCoord2s.md @@ -4,7 +4,7 @@ title: GEMglTexCoord2s description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, and r texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord2sv.md b/Resources/Documentation/Gem/GEMglTexCoord2sv.md index df5acb91c7..edf4858960 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord2sv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord2sv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord2sv description: set the current texture coordinates (short integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, and r texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord3d.md b/Resources/Documentation/Gem/GEMglTexCoord3d.md index 1d949fe4ab..79f130364a 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord3d.md +++ b/Resources/Documentation/Gem/GEMglTexCoord3d.md @@ -4,7 +4,7 @@ title: GEMglTexCoord3d description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, and r texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord3dv.md b/Resources/Documentation/Gem/GEMglTexCoord3dv.md index 46f8092b81..ee808477e7 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord3dv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord3dv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord3dv description: set the current texture coordinates (double-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, and r texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord3f.md b/Resources/Documentation/Gem/GEMglTexCoord3f.md index c6788600d8..ffe54748c1 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord3f.md +++ b/Resources/Documentation/Gem/GEMglTexCoord3f.md @@ -4,7 +4,7 @@ title: GEMglTexCoord3f description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, and r texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord3fv.md b/Resources/Documentation/Gem/GEMglTexCoord3fv.md index ed8b9d947d..c4ec3f215f 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord3fv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord3fv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord3fv description: set the current texture coordinates (floating-point version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, and r texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord3i.md b/Resources/Documentation/Gem/GEMglTexCoord3i.md index 1e8fd429f7..0799828b2c 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord3i.md +++ b/Resources/Documentation/Gem/GEMglTexCoord3i.md @@ -4,7 +4,7 @@ title: GEMglTexCoord3i description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, and r texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord3iv.md b/Resources/Documentation/Gem/GEMglTexCoord3iv.md index 00fa61e114..547618bf4b 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord3iv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord3iv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord3iv description: set the current texture coordinates (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, and r texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord3s.md b/Resources/Documentation/Gem/GEMglTexCoord3s.md index 55bbf40039..f0090aa25f 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord3s.md +++ b/Resources/Documentation/Gem/GEMglTexCoord3s.md @@ -4,7 +4,7 @@ title: GEMglTexCoord3s description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, and r texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord3sv.md b/Resources/Documentation/Gem/GEMglTexCoord3sv.md index c2182363d8..171f5d0ed0 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord3sv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord3sv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord3sv description: set the current texture coordinates (short integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, and r texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord4d.md b/Resources/Documentation/Gem/GEMglTexCoord4d.md index bc9e8dbee8..1bc9657695 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord4d.md +++ b/Resources/Documentation/Gem/GEMglTexCoord4d.md @@ -4,7 +4,7 @@ title: GEMglTexCoord4d description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, r, and q texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord4dv.md b/Resources/Documentation/Gem/GEMglTexCoord4dv.md index 3d2db5c994..2e114dcecc 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord4dv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord4dv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord4dv description: set the current texture coordinates (double-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, r, and q texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord4f.md b/Resources/Documentation/Gem/GEMglTexCoord4f.md index 0fead22a89..1c83e4ffd0 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord4f.md +++ b/Resources/Documentation/Gem/GEMglTexCoord4f.md @@ -4,7 +4,7 @@ title: GEMglTexCoord4f description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, r, and q texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord4fv.md b/Resources/Documentation/Gem/GEMglTexCoord4fv.md index 3cdcf87e29..21f5152e57 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord4fv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord4fv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord4fv description: set the current texture coordinates (floating-point version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, r, and q texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord4i.md b/Resources/Documentation/Gem/GEMglTexCoord4i.md index db9f665f13..71dbf9a2a7 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord4i.md +++ b/Resources/Documentation/Gem/GEMglTexCoord4i.md @@ -4,7 +4,7 @@ title: GEMglTexCoord4i description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, r, and q texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord4iv.md b/Resources/Documentation/Gem/GEMglTexCoord4iv.md index 390eda3bfc..eb8e48c31d 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord4iv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord4iv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord4iv description: set the current texture coordinates (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, r, and q texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord4s.md b/Resources/Documentation/Gem/GEMglTexCoord4s.md index 4c884497e0..2a23d00e99 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord4s.md +++ b/Resources/Documentation/Gem/GEMglTexCoord4s.md @@ -4,7 +4,7 @@ title: GEMglTexCoord4s description: set the current texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, r, and q texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexCoord4sv.md b/Resources/Documentation/Gem/GEMglTexCoord4sv.md index 24df6b7311..2c5e3089b2 100644 --- a/Resources/Documentation/Gem/GEMglTexCoord4sv.md +++ b/Resources/Documentation/Gem/GEMglTexCoord4sv.md @@ -4,7 +4,7 @@ title: GEMglTexCoord4sv description: set the current texture coordinates (short integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the s, t, r, and q texture coordinates. diff --git a/Resources/Documentation/Gem/GEMglTexEnvf.md b/Resources/Documentation/Gem/GEMglTexEnvf.md index 7784e80931..20b56a3e23 100644 --- a/Resources/Documentation/Gem/GEMglTexEnvf.md +++ b/Resources/Documentation/Gem/GEMglTexEnvf.md @@ -4,7 +4,7 @@ title: GEMglTexEnvf description: set texture environment parameters (floating-point version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the texture environment target. diff --git a/Resources/Documentation/Gem/GEMglTexEnvi.md b/Resources/Documentation/Gem/GEMglTexEnvi.md index 02cf3fe31a..a58de15dcb 100644 --- a/Resources/Documentation/Gem/GEMglTexEnvi.md +++ b/Resources/Documentation/Gem/GEMglTexEnvi.md @@ -4,7 +4,7 @@ title: GEMglTexEnvi description: set texture environment parameters (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the texture environment target. diff --git a/Resources/Documentation/Gem/GEMglTexGend.md b/Resources/Documentation/Gem/GEMglTexGend.md index 49ee524b28..76837c578b 100644 --- a/Resources/Documentation/Gem/GEMglTexGend.md +++ b/Resources/Documentation/Gem/GEMglTexGend.md @@ -4,7 +4,7 @@ title: GEMglTexGend description: set texture coordinate generation parameters (double-precision version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the texture coordinate function. diff --git a/Resources/Documentation/Gem/GEMglTexGenf.md b/Resources/Documentation/Gem/GEMglTexGenf.md index f402f4f9ac..123f246353 100644 --- a/Resources/Documentation/Gem/GEMglTexGenf.md +++ b/Resources/Documentation/Gem/GEMglTexGenf.md @@ -4,7 +4,7 @@ title: GEMglTexGenf description: set texture coordinate generation parameters (floating-point version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the texture coordinate function. diff --git a/Resources/Documentation/Gem/GEMglTexGenfv.md b/Resources/Documentation/Gem/GEMglTexGenfv.md index 452238a0ef..f690cd70eb 100644 --- a/Resources/Documentation/Gem/GEMglTexGenfv.md +++ b/Resources/Documentation/Gem/GEMglTexGenfv.md @@ -4,7 +4,7 @@ title: GEMglTexGenfv description: set texture coordinate generation parameters (floating-point version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the texture coordinate function. diff --git a/Resources/Documentation/Gem/GEMglTexGeni.md b/Resources/Documentation/Gem/GEMglTexGeni.md index f2374f1215..13a06d4e51 100644 --- a/Resources/Documentation/Gem/GEMglTexGeni.md +++ b/Resources/Documentation/Gem/GEMglTexGeni.md @@ -4,7 +4,7 @@ title: GEMglTexGeni description: set texture coordinate generation parameters (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the texture coordinate function. diff --git a/Resources/Documentation/Gem/GEMglTexImage2D.md b/Resources/Documentation/Gem/GEMglTexImage2D.md index 7012305df3..52493974a3 100644 --- a/Resources/Documentation/Gem/GEMglTexImage2D.md +++ b/Resources/Documentation/Gem/GEMglTexImage2D.md @@ -4,7 +4,7 @@ title: GEMglTexImage2D description: specify a two-dimensional texture image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target texture. diff --git a/Resources/Documentation/Gem/GEMglTexParameterf.md b/Resources/Documentation/Gem/GEMglTexParameterf.md index e0382cd5d0..6fd2d2264e 100644 --- a/Resources/Documentation/Gem/GEMglTexParameterf.md +++ b/Resources/Documentation/Gem/GEMglTexParameterf.md @@ -4,7 +4,7 @@ title: GEMglTexParameterf description: set texture parameters (floating-point version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target texture. diff --git a/Resources/Documentation/Gem/GEMglTexParameteri.md b/Resources/Documentation/Gem/GEMglTexParameteri.md index 27ec662490..fa8944f6ed 100644 --- a/Resources/Documentation/Gem/GEMglTexParameteri.md +++ b/Resources/Documentation/Gem/GEMglTexParameteri.md @@ -4,7 +4,7 @@ title: GEMglTexParameteri description: set texture parameters (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target texture. diff --git a/Resources/Documentation/Gem/GEMglTexSubImage1D.md b/Resources/Documentation/Gem/GEMglTexSubImage1D.md index 7ad35b3563..bdd9d9ea1d 100644 --- a/Resources/Documentation/Gem/GEMglTexSubImage1D.md +++ b/Resources/Documentation/Gem/GEMglTexSubImage1D.md @@ -4,7 +4,7 @@ title: GEMglTexSubImage1D description: define a subregion of a one-dimensional texture image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target texture. diff --git a/Resources/Documentation/Gem/GEMglTexSubImage2D.md b/Resources/Documentation/Gem/GEMglTexSubImage2D.md index 26bae28b87..916db132d1 100644 --- a/Resources/Documentation/Gem/GEMglTexSubImage2D.md +++ b/Resources/Documentation/Gem/GEMglTexSubImage2D.md @@ -4,7 +4,7 @@ title: GEMglTexSubImage2D description: define a subregion of a two-dimensional texture image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the target texture. diff --git a/Resources/Documentation/Gem/GEMglTranslated.md b/Resources/Documentation/Gem/GEMglTranslated.md index 400e3d6df6..05739bc979 100644 --- a/Resources/Documentation/Gem/GEMglTranslated.md +++ b/Resources/Documentation/Gem/GEMglTranslated.md @@ -4,7 +4,7 @@ title: GEMglTranslated description: multiply the current matrix by a translation matrix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z offsets for the translation. diff --git a/Resources/Documentation/Gem/GEMglTranslatef.md b/Resources/Documentation/Gem/GEMglTranslatef.md index ea54b9d292..34779dab7a 100644 --- a/Resources/Documentation/Gem/GEMglTranslatef.md +++ b/Resources/Documentation/Gem/GEMglTranslatef.md @@ -4,7 +4,7 @@ title: GEMglTranslatef description: multiply the current matrix by a translation matrix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z offsets for the translation. diff --git a/Resources/Documentation/Gem/GEMglUniform1f.md b/Resources/Documentation/Gem/GEMglUniform1f.md index 65c36dbca7..5f7c42af1d 100644 --- a/Resources/Documentation/Gem/GEMglUniform1f.md +++ b/Resources/Documentation/Gem/GEMglUniform1f.md @@ -4,7 +4,7 @@ title: GEMglUniform1f description: load a floating-point uniform variable to a program object categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the location of the uniform variable. diff --git a/Resources/Documentation/Gem/GEMglUniform1fARB.md b/Resources/Documentation/Gem/GEMglUniform1fARB.md index 50b5e38633..25e72e173e 100644 --- a/Resources/Documentation/Gem/GEMglUniform1fARB.md +++ b/Resources/Documentation/Gem/GEMglUniform1fARB.md @@ -4,7 +4,7 @@ title: GEMglUniform1fARB description: load a floating-point uniform variable to a program object (ARB version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the location of the uniform variable. diff --git a/Resources/Documentation/Gem/GEMglUseProgramObjectARB.md b/Resources/Documentation/Gem/GEMglUseProgramObjectARB.md index 64a968f995..f08328f88f 100644 --- a/Resources/Documentation/Gem/GEMglUseProgramObjectARB.md +++ b/Resources/Documentation/Gem/GEMglUseProgramObjectARB.md @@ -4,7 +4,7 @@ title: GEMglUseProgramObjectARB description: install a program object as part of current rendering state (ARB version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the handle of the program object whose executables are to be used as part of rendering state. diff --git a/Resources/Documentation/Gem/GEMglVertex2d.md b/Resources/Documentation/Gem/GEMglVertex2d.md index 82f9004f3d..05a0efa8d1 100644 --- a/Resources/Documentation/Gem/GEMglVertex2d.md +++ b/Resources/Documentation/Gem/GEMglVertex2d.md @@ -4,7 +4,7 @@ title: GEMglVertex2d description: specify a two-dimensional vertex with double-precision coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: double description: Specifies the x-coordinate of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex2dv.md b/Resources/Documentation/Gem/GEMglVertex2dv.md index e50d60f8f8..cd46d3330d 100644 --- a/Resources/Documentation/Gem/GEMglVertex2dv.md +++ b/Resources/Documentation/Gem/GEMglVertex2dv.md @@ -4,7 +4,7 @@ title: GEMglVertex2dv description: specify a two-dimensional vertex with double-precision coordinates using an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: double description: Specifies an array containing the x and y coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex2f.md b/Resources/Documentation/Gem/GEMglVertex2f.md index 4c4d6585e7..2716c13bdb 100644 --- a/Resources/Documentation/Gem/GEMglVertex2f.md +++ b/Resources/Documentation/Gem/GEMglVertex2f.md @@ -4,7 +4,7 @@ title: GEMglVertex2f description: specify a two-dimensional vertex with single-precision coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x-coordinate of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex2fv.md b/Resources/Documentation/Gem/GEMglVertex2fv.md index 18826a3003..33160ca776 100644 --- a/Resources/Documentation/Gem/GEMglVertex2fv.md +++ b/Resources/Documentation/Gem/GEMglVertex2fv.md @@ -4,7 +4,7 @@ title: GEMglVertex2fv description: specify a two-dimensional vertex with single-precision coordinates using an array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies an array containing the x and y coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex2i.md b/Resources/Documentation/Gem/GEMglVertex2i.md index 6d7409b19a..2475ff5186 100644 --- a/Resources/Documentation/Gem/GEMglVertex2i.md +++ b/Resources/Documentation/Gem/GEMglVertex2i.md @@ -4,7 +4,7 @@ title: GEMglVertex2i description: specify a two-dimensional vertex categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x and y coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex2iv.md b/Resources/Documentation/Gem/GEMglVertex2iv.md index b8b145b1b8..ba3e88d7b1 100644 --- a/Resources/Documentation/Gem/GEMglVertex2iv.md +++ b/Resources/Documentation/Gem/GEMglVertex2iv.md @@ -4,7 +4,7 @@ title: GEMglVertex2iv description: specify a two-dimensional vertex (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: const float* description: Specifies a pointer to an array of two elements, which are the x and y coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex2s.md b/Resources/Documentation/Gem/GEMglVertex2s.md index 52a906c1ea..0a6b5c18ef 100644 --- a/Resources/Documentation/Gem/GEMglVertex2s.md +++ b/Resources/Documentation/Gem/GEMglVertex2s.md @@ -4,7 +4,7 @@ title: GEMglVertex2s description: specify a two-dimensional vertex (short version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x and y coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex2sv.md b/Resources/Documentation/Gem/GEMglVertex2sv.md index 0fcb3a20f0..4cf151e48a 100644 --- a/Resources/Documentation/Gem/GEMglVertex2sv.md +++ b/Resources/Documentation/Gem/GEMglVertex2sv.md @@ -4,7 +4,7 @@ title: GEMglVertex2sv description: specify a two-dimensional vertex (short version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: const float* description: Specifies a pointer to an array of two elements, which are the x and y coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex3d.md b/Resources/Documentation/Gem/GEMglVertex3d.md index b58fdab274..8b3d81a3cc 100644 --- a/Resources/Documentation/Gem/GEMglVertex3d.md +++ b/Resources/Documentation/Gem/GEMglVertex3d.md @@ -4,7 +4,7 @@ title: GEMglVertex3d description: specify a three-dimensional vertex (double version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex3dv.md b/Resources/Documentation/Gem/GEMglVertex3dv.md index 11d907c151..8fa3fd923e 100644 --- a/Resources/Documentation/Gem/GEMglVertex3dv.md +++ b/Resources/Documentation/Gem/GEMglVertex3dv.md @@ -4,7 +4,7 @@ title: GEMglVertex3dv description: specify a three-dimensional vertex (double version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: const float* description: Specifies a pointer to an array of three elements, which are the x, y, and z coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex3f.md b/Resources/Documentation/Gem/GEMglVertex3f.md index 4555e0ee5a..1cda8dfe26 100644 --- a/Resources/Documentation/Gem/GEMglVertex3f.md +++ b/Resources/Documentation/Gem/GEMglVertex3f.md @@ -4,7 +4,7 @@ title: GEMglVertex3f description: specify a three-dimensional vertex (float version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex3fv.md b/Resources/Documentation/Gem/GEMglVertex3fv.md index f3e41c31b3..7686553938 100644 --- a/Resources/Documentation/Gem/GEMglVertex3fv.md +++ b/Resources/Documentation/Gem/GEMglVertex3fv.md @@ -4,7 +4,7 @@ title: GEMglVertex3fv description: specify a three-dimensional vertex (float version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: const float* description: Specifies a pointer to an array of three elements, which are the x, y, and z coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex3i.md b/Resources/Documentation/Gem/GEMglVertex3i.md index 4862da9a28..7a8fd9046e 100644 --- a/Resources/Documentation/Gem/GEMglVertex3i.md +++ b/Resources/Documentation/Gem/GEMglVertex3i.md @@ -4,7 +4,7 @@ title: GEMglVertex3i description: specify a three-dimensional vertex (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex3iv.md b/Resources/Documentation/Gem/GEMglVertex3iv.md index b08d3070e1..c120521015 100644 --- a/Resources/Documentation/Gem/GEMglVertex3iv.md +++ b/Resources/Documentation/Gem/GEMglVertex3iv.md @@ -4,7 +4,7 @@ title: GEMglVertex3iv description: specify a three-dimensional vertex (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: const float* description: Specifies a pointer to an array of three elements, which are the x, y, and z coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex3s.md b/Resources/Documentation/Gem/GEMglVertex3s.md index 956827a497..7c7e94019c 100644 --- a/Resources/Documentation/Gem/GEMglVertex3s.md +++ b/Resources/Documentation/Gem/GEMglVertex3s.md @@ -4,7 +4,7 @@ title: GEMglVertex3s description: specify a three-dimensional vertex (short version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, and z coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex3sv.md b/Resources/Documentation/Gem/GEMglVertex3sv.md index 65bae56b49..0857d89daa 100644 --- a/Resources/Documentation/Gem/GEMglVertex3sv.md +++ b/Resources/Documentation/Gem/GEMglVertex3sv.md @@ -4,7 +4,7 @@ title: GEMglVertex3sv description: specify a three-dimensional vertex (short version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: const float* description: Specifies a pointer to an array of three elements, which are the x, y, and z coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex4d.md b/Resources/Documentation/Gem/GEMglVertex4d.md index 1b014a3c90..65d5a3c7f0 100644 --- a/Resources/Documentation/Gem/GEMglVertex4d.md +++ b/Resources/Documentation/Gem/GEMglVertex4d.md @@ -4,7 +4,7 @@ title: GEMglVertex4d description: specify a four-dimensional vertex (double version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, z, and w coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex4dv.md b/Resources/Documentation/Gem/GEMglVertex4dv.md index 67e14b8947..65f49793e9 100644 --- a/Resources/Documentation/Gem/GEMglVertex4dv.md +++ b/Resources/Documentation/Gem/GEMglVertex4dv.md @@ -4,7 +4,7 @@ title: GEMglVertex4dv description: specify a four-dimensional vertex (double version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: const float* description: Specifies a pointer to an array of four elements, which are the x, y, z, and w coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex4f.md b/Resources/Documentation/Gem/GEMglVertex4f.md index b06410ed1c..41c7a9a8a2 100644 --- a/Resources/Documentation/Gem/GEMglVertex4f.md +++ b/Resources/Documentation/Gem/GEMglVertex4f.md @@ -4,7 +4,7 @@ title: GEMglVertex4f description: specify a four-dimensional vertex (float version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, z, and w coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex4fv.md b/Resources/Documentation/Gem/GEMglVertex4fv.md index 5503b3e209..64da16f561 100644 --- a/Resources/Documentation/Gem/GEMglVertex4fv.md +++ b/Resources/Documentation/Gem/GEMglVertex4fv.md @@ -4,7 +4,7 @@ title: GEMglVertex4fv description: specify a four-dimensional vertex (float version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: const float* description: Specifies a pointer to an array of four elements, which are the x, y, z, and w coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex4i.md b/Resources/Documentation/Gem/GEMglVertex4i.md index a7a9591a9a..69ca454ff3 100644 --- a/Resources/Documentation/Gem/GEMglVertex4i.md +++ b/Resources/Documentation/Gem/GEMglVertex4i.md @@ -4,7 +4,7 @@ title: GEMglVertex4i description: specify a four-dimensional vertex (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, z, and w coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex4iv.md b/Resources/Documentation/Gem/GEMglVertex4iv.md index 8d91233805..eb6cbe1e46 100644 --- a/Resources/Documentation/Gem/GEMglVertex4iv.md +++ b/Resources/Documentation/Gem/GEMglVertex4iv.md @@ -4,7 +4,7 @@ title: GEMglVertex4iv description: specify a four-dimensional vertex (integer version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: const float* description: Specifies a pointer to an array of four elements, which are the x, y, z, and w coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex4s.md b/Resources/Documentation/Gem/GEMglVertex4s.md index b2ec97c7d2..fc1da5d062 100644 --- a/Resources/Documentation/Gem/GEMglVertex4s.md +++ b/Resources/Documentation/Gem/GEMglVertex4s.md @@ -4,7 +4,7 @@ title: GEMglVertex4s description: specify a four-dimensional vertex (short version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the x, y, z, and w coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglVertex4sv.md b/Resources/Documentation/Gem/GEMglVertex4sv.md index 337d4ad20a..f8647c2d7b 100644 --- a/Resources/Documentation/Gem/GEMglVertex4sv.md +++ b/Resources/Documentation/Gem/GEMglVertex4sv.md @@ -4,7 +4,7 @@ title: GEMglVertex4sv description: specify a four-dimensional vertex (short version) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: const float* description: Specifies a pointer to an array of four elements, which are the x, y, z, and w coordinates of the vertex. diff --git a/Resources/Documentation/Gem/GEMglViewport.md b/Resources/Documentation/Gem/GEMglViewport.md index 9481b86c24..0bd9c7c609 100644 --- a/Resources/Documentation/Gem/GEMglViewport.md +++ b/Resources/Documentation/Gem/GEMglViewport.md @@ -4,7 +4,7 @@ title: GEMglViewport description: set the viewport categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the lower left corner of the viewport rectangle, in pixels. The initial value is (0,0). diff --git a/Resources/Documentation/Gem/GEMgluLookAt.md b/Resources/Documentation/Gem/GEMgluLookAt.md index 6bd24a7517..4eca30b795 100644 --- a/Resources/Documentation/Gem/GEMgluLookAt.md +++ b/Resources/Documentation/Gem/GEMgluLookAt.md @@ -4,7 +4,7 @@ title: GEMgluLookAt description: define a viewing transformation categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the position of the eye point. diff --git a/Resources/Documentation/Gem/GEMgluPerspective.md b/Resources/Documentation/Gem/GEMgluPerspective.md index a439570536..871d62ad04 100644 --- a/Resources/Documentation/Gem/GEMgluPerspective.md +++ b/Resources/Documentation/Gem/GEMgluPerspective.md @@ -4,7 +4,7 @@ title: GEMgluPerspective description: set up a perspective projection matrix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Specifies the field of view angle, in degrees, in the y direction. diff --git a/Resources/Documentation/Gem/GLdefine.md b/Resources/Documentation/Gem/GLdefine.md index b8a26b618c..4203d890f2 100644 --- a/Resources/Documentation/Gem/GLdefine.md +++ b/Resources/Documentation/Gem/GLdefine.md @@ -3,7 +3,7 @@ title: GLdefine description: Gets the value of an OpenGL constant. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: bang diff --git a/Resources/Documentation/Gem/accumrotate.md b/Resources/Documentation/Gem/accumrotate.md index 802b47fc4a..399d734ebc 100644 --- a/Resources/Documentation/Gem/accumrotate.md +++ b/Resources/Documentation/Gem/accumrotate.md @@ -3,7 +3,7 @@ title: accumrotate description: Accumulated rotation. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: initial rotations around X, Y, Z-axes diff --git a/Resources/Documentation/Gem/alpha.md b/Resources/Documentation/Gem/alpha.md index edce745018..6c5f0b72eb 100644 --- a/Resources/Documentation/Gem/alpha.md +++ b/Resources/Documentation/Gem/alpha.md @@ -3,7 +3,7 @@ title: alpha description: enable alpha blending categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: turn alpha blending on/off diff --git a/Resources/Documentation/Gem/ambient.md b/Resources/Documentation/Gem/ambient.md index 07d93415c5..c8a2c6d74c 100644 --- a/Resources/Documentation/Gem/ambient.md +++ b/Resources/Documentation/Gem/ambient.md @@ -3,7 +3,7 @@ title: ambient description: ambient coloring categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: a list of 3 (RGB) or 4 (RGBA) float-values diff --git a/Resources/Documentation/Gem/ambientRGB.md b/Resources/Documentation/Gem/ambientRGB.md index f03bc58aca..2396c76db6 100644 --- a/Resources/Documentation/Gem/ambientRGB.md +++ b/Resources/Documentation/Gem/ambientRGB.md @@ -3,7 +3,7 @@ title: ambientRGB description: ambient coloring with RGB values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: red value diff --git a/Resources/Documentation/Gem/circle.md b/Resources/Documentation/Gem/circle.md index 34b3da54f8..02db88f695 100644 --- a/Resources/Documentation/Gem/circle.md +++ b/Resources/Documentation/Gem/circle.md @@ -3,7 +3,7 @@ title: circle description: renders a circle categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: size of the circle diff --git a/Resources/Documentation/Gem/color.md b/Resources/Documentation/Gem/color.md index c36e85fd5c..ddc7b4cede 100644 --- a/Resources/Documentation/Gem/color.md +++ b/Resources/Documentation/Gem/color.md @@ -3,7 +3,7 @@ title: color description: sets the color for subsequent shape and vertex operations categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: 3 (RGB) or 4 (RGBA) float-values diff --git a/Resources/Documentation/Gem/colorRGB.md b/Resources/Documentation/Gem/colorRGB.md index 0578b43814..cea23da30a 100644 --- a/Resources/Documentation/Gem/colorRGB.md +++ b/Resources/Documentation/Gem/colorRGB.md @@ -3,7 +3,7 @@ title: colorRGB description: sets the color for subsequent shape and vertex operations using RGB values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: red value diff --git a/Resources/Documentation/Gem/colorSquare.md b/Resources/Documentation/Gem/colorSquare.md index 95d50fb7bd..5cc4c429cd 100644 --- a/Resources/Documentation/Gem/colorSquare.md +++ b/Resources/Documentation/Gem/colorSquare.md @@ -3,7 +3,7 @@ title: colorSquare description: renders a square with several colors categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: size of the square diff --git a/Resources/Documentation/Gem/cone.md b/Resources/Documentation/Gem/cone.md index e823eaff7f..413f71b736 100644 --- a/Resources/Documentation/Gem/cone.md +++ b/Resources/Documentation/Gem/cone.md @@ -3,7 +3,7 @@ title: cone description: renders a cone categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: size of the cone diff --git a/Resources/Documentation/Gem/cube.md b/Resources/Documentation/Gem/cube.md index fbd0560640..befcfb82ea 100644 --- a/Resources/Documentation/Gem/cube.md +++ b/Resources/Documentation/Gem/cube.md @@ -3,7 +3,7 @@ title: cube description: renders a cube categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: size of the cube diff --git a/Resources/Documentation/Gem/cuboid.md b/Resources/Documentation/Gem/cuboid.md index f4c9366d3f..af837994b3 100644 --- a/Resources/Documentation/Gem/cuboid.md +++ b/Resources/Documentation/Gem/cuboid.md @@ -3,7 +3,7 @@ title: cuboid description: renders a cuboid box categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: length (dimX) diff --git a/Resources/Documentation/Gem/curve.md b/Resources/Documentation/Gem/curve.md index e5c73027e6..98a58cd7c6 100644 --- a/Resources/Documentation/Gem/curve.md +++ b/Resources/Documentation/Gem/curve.md @@ -3,7 +3,7 @@ title: curve description: renders a bezier-curve categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: description: number of control-points of the curve (mandatory) diff --git a/Resources/Documentation/Gem/curve3d.md b/Resources/Documentation/Gem/curve3d.md index 17daf89a40..5364b1316d 100644 --- a/Resources/Documentation/Gem/curve3d.md +++ b/Resources/Documentation/Gem/curve3d.md @@ -3,7 +3,7 @@ title: curve3d description: renders a 3D bezier curve categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/cylinder.md b/Resources/Documentation/Gem/cylinder.md index 5279b72706..e1fd9567a9 100644 --- a/Resources/Documentation/Gem/cylinder.md +++ b/Resources/Documentation/Gem/cylinder.md @@ -3,7 +3,7 @@ title: cylinder description: renders a cylinder categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: size diff --git a/Resources/Documentation/Gem/depth.md b/Resources/Documentation/Gem/depth.md index 2275d7af2e..e706ac709d 100644 --- a/Resources/Documentation/Gem/depth.md +++ b/Resources/Documentation/Gem/depth.md @@ -3,7 +3,7 @@ title: depth description: Activate / Deactivate depth test. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: null inlets: 1st: diff --git a/Resources/Documentation/Gem/diffuse.md b/Resources/Documentation/Gem/diffuse.md index effaab9ba4..71961f2fde 100644 --- a/Resources/Documentation/Gem/diffuse.md +++ b/Resources/Documentation/Gem/diffuse.md @@ -3,7 +3,7 @@ title: diffuse description: diffuse coloring categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: a list of 3 (RGB) or 4 (RGBA) float-values diff --git a/Resources/Documentation/Gem/diffuseRGB.md b/Resources/Documentation/Gem/diffuseRGB.md index 513b8821df..141761accc 100644 --- a/Resources/Documentation/Gem/diffuseRGB.md +++ b/Resources/Documentation/Gem/diffuseRGB.md @@ -3,7 +3,7 @@ title: diffuseRGB description: diffuse coloring with RGB values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: red value diff --git a/Resources/Documentation/Gem/disk.md b/Resources/Documentation/Gem/disk.md index f7b50ee35f..d685795c6d 100644 --- a/Resources/Documentation/Gem/disk.md +++ b/Resources/Documentation/Gem/disk.md @@ -3,7 +3,7 @@ title: disk description: renders a disk categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: size (= outer radius) diff --git a/Resources/Documentation/Gem/emission.md b/Resources/Documentation/Gem/emission.md index fd882d2a4e..b8f63b1095 100644 --- a/Resources/Documentation/Gem/emission.md +++ b/Resources/Documentation/Gem/emission.md @@ -3,7 +3,7 @@ title: emission description: emission coloring categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: a list of 3 (RGB) or 4 (RGBA) float-values diff --git a/Resources/Documentation/Gem/emissionRGB.md b/Resources/Documentation/Gem/emissionRGB.md index 198d86cd5d..ce2c771998 100644 --- a/Resources/Documentation/Gem/emissionRGB.md +++ b/Resources/Documentation/Gem/emissionRGB.md @@ -3,7 +3,7 @@ title: emissionRGB description: emission coloring with individual RGB values. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: a list of 3 (RGB) or 4 (RGBA) float-values. diff --git a/Resources/Documentation/Gem/fragment_program.md b/Resources/Documentation/Gem/fragment_program.md index 6211aba38d..dc2a8d1d59 100644 --- a/Resources/Documentation/Gem/fragment_program.md +++ b/Resources/Documentation/Gem/fragment_program.md @@ -3,7 +3,7 @@ title: fragment_program description: load and apply an ARB fragment shader categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: open description: fragment shader program to load diff --git a/Resources/Documentation/Gem/gemargs.md b/Resources/Documentation/Gem/gemargs.md index 9c0935ac22..632f2e0892 100644 --- a/Resources/Documentation/Gem/gemargs.md +++ b/Resources/Documentation/Gem/gemargs.md @@ -3,7 +3,7 @@ title: gemargs description: pass the initial message and arguments for the Gem subsystem categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: bang diff --git a/Resources/Documentation/Gem/gemcubeframebuffer.md b/Resources/Documentation/Gem/gemcubeframebuffer.md index db6b0c3615..a91f5c3ab0 100644 --- a/Resources/Documentation/Gem/gemcubeframebuffer.md +++ b/Resources/Documentation/Gem/gemcubeframebuffer.md @@ -3,7 +3,7 @@ title: gemcubeframebuffer description: renders a scene to faces of a GL cubemap texture categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: dimen description: set the dimensions of the framebuffer diff --git a/Resources/Documentation/Gem/gemframebuffer.md b/Resources/Documentation/Gem/gemframebuffer.md index 1579131067..323b058924 100644 --- a/Resources/Documentation/Gem/gemframebuffer.md +++ b/Resources/Documentation/Gem/gemframebuffer.md @@ -3,7 +3,7 @@ title: gemframebuffer description: renders a scene in a texture for later use categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: type description: type of the framebuffer data (BYTE, INT or FLOAT]) diff --git a/Resources/Documentation/Gem/gemhead.md b/Resources/Documentation/Gem/gemhead.md index 6df96c8241..ad41d00bd2 100644 --- a/Resources/Documentation/Gem/gemhead.md +++ b/Resources/Documentation/Gem/gemhead.md @@ -3,7 +3,7 @@ title: gemhead description: start a rendering chain categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: priority diff --git a/Resources/Documentation/Gem/gemkeyboard.md b/Resources/Documentation/Gem/gemkeyboard.md index ffb9f547d8..c480aad7d3 100644 --- a/Resources/Documentation/Gem/gemkeyboard.md +++ b/Resources/Documentation/Gem/gemkeyboard.md @@ -3,7 +3,7 @@ title: gemkeyboard description: output keyboard events in the GEM window categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: non-used diff --git a/Resources/Documentation/Gem/gemkeyname.md b/Resources/Documentation/Gem/gemkeyname.md index a355075df9..f82e028654 100644 --- a/Resources/Documentation/Gem/gemkeyname.md +++ b/Resources/Documentation/Gem/gemkeyname.md @@ -3,7 +3,7 @@ title: gemkeyname description: output keyboard events in the GEM window categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: non-used diff --git a/Resources/Documentation/Gem/gemlist.md b/Resources/Documentation/Gem/gemlist.md index f4bd1695c3..084987b249 100644 --- a/Resources/Documentation/Gem/gemlist.md +++ b/Resources/Documentation/Gem/gemlist.md @@ -3,7 +3,7 @@ title: gemlist description: Store a gemlist. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: diff --git a/Resources/Documentation/Gem/gemlist_info.md b/Resources/Documentation/Gem/gemlist_info.md index 552fec4c8f..83cfae4fd3 100644 --- a/Resources/Documentation/Gem/gemlist_info.md +++ b/Resources/Documentation/Gem/gemlist_info.md @@ -3,7 +3,7 @@ title: gemlist_info description: get current transformation of a gemlist categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/gemlist_matrix.md b/Resources/Documentation/Gem/gemlist_matrix.md index 7324f49a40..97d974641c 100644 --- a/Resources/Documentation/Gem/gemlist_matrix.md +++ b/Resources/Documentation/Gem/gemlist_matrix.md @@ -3,7 +3,7 @@ title: gemlist_matrix description: get the current transformation matrix of a gemlist categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: diff --git a/Resources/Documentation/Gem/gemmanager.md b/Resources/Documentation/Gem/gemmanager.md index 3ca7ac53c2..7099c57def 100644 --- a/Resources/Documentation/Gem/gemmanager.md +++ b/Resources/Documentation/Gem/gemmanager.md @@ -3,7 +3,7 @@ title: gemmanager description: interact with the global gemstate categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: dimen description: set global window dimensions diff --git a/Resources/Documentation/Gem/gemmouse.md b/Resources/Documentation/Gem/gemmouse.md index 492487b8f6..45599059da 100644 --- a/Resources/Documentation/Gem/gemmouse.md +++ b/Resources/Documentation/Gem/gemmouse.md @@ -3,7 +3,7 @@ title: gemmouse description: output mouse events in the GEM window categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: x-normalization diff --git a/Resources/Documentation/Gem/gemreceive.md b/Resources/Documentation/Gem/gemreceive.md index 7362d81b46..f04ebcc44e 100644 --- a/Resources/Documentation/Gem/gemreceive.md +++ b/Resources/Documentation/Gem/gemreceive.md @@ -3,7 +3,7 @@ title: gemreceive description: receive messages from Gem categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: symbol diff --git a/Resources/Documentation/Gem/gemrepeat.md b/Resources/Documentation/Gem/gemrepeat.md index 7db7159677..de54a96fbd 100644 --- a/Resources/Documentation/Gem/gemrepeat.md +++ b/Resources/Documentation/Gem/gemrepeat.md @@ -3,7 +3,7 @@ title: gemrepeat description: repeat a gemlist several times categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: number of repeats diff --git a/Resources/Documentation/Gem/gemvertexbuffer.md b/Resources/Documentation/Gem/gemvertexbuffer.md index 7c00bfc3a5..0cbd16c7fe 100644 --- a/Resources/Documentation/Gem/gemvertexbuffer.md +++ b/Resources/Documentation/Gem/gemvertexbuffer.md @@ -3,7 +3,7 @@ title: gemvertexbuffer description: renders a vertex buffer categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: null inlets: 1st: diff --git a/Resources/Documentation/Gem/gemwin.md b/Resources/Documentation/Gem/gemwin.md index e2778f2c48..245cf6aaa3 100644 --- a/Resources/Documentation/Gem/gemwin.md +++ b/Resources/Documentation/Gem/gemwin.md @@ -6,7 +6,7 @@ description: interact with Gem window categories: - graphics -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float diff --git a/Resources/Documentation/Gem/glsl_fragment.md b/Resources/Documentation/Gem/glsl_fragment.md index 2f4d6c367a..ff54749bd3 100644 --- a/Resources/Documentation/Gem/glsl_fragment.md +++ b/Resources/Documentation/Gem/glsl_fragment.md @@ -3,7 +3,7 @@ title: glsl_fragment description: loads and compiles a GLSL fragment shader into a module categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: open description: filename to load as GLSL fragment shader module diff --git a/Resources/Documentation/Gem/glsl_geometry.md b/Resources/Documentation/Gem/glsl_geometry.md index 03883c3c6a..41a4182f80 100644 --- a/Resources/Documentation/Gem/glsl_geometry.md +++ b/Resources/Documentation/Gem/glsl_geometry.md @@ -3,7 +3,7 @@ title: glsl_geometry description: loads and compiles a GLSL geometry shader into a module categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: open description: filename to load as GLSL geometry shader module diff --git a/Resources/Documentation/Gem/glsl_program.md b/Resources/Documentation/Gem/glsl_program.md index 6fa26548fe..a9d4a0de1f 100644 --- a/Resources/Documentation/Gem/glsl_program.md +++ b/Resources/Documentation/Gem/glsl_program.md @@ -3,7 +3,7 @@ title: glsl_program description: links GLSL-modules into a shader program categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: shader description: list of shader-module IDs as reported generated by [glsl_fragment] and [glsl_vertex] diff --git a/Resources/Documentation/Gem/glsl_vertex.md b/Resources/Documentation/Gem/glsl_vertex.md index fb4b529457..40b8c4374d 100644 --- a/Resources/Documentation/Gem/glsl_vertex.md +++ b/Resources/Documentation/Gem/glsl_vertex.md @@ -3,7 +3,7 @@ title: glsl_vertex description: loads and compiles a GLSL vertex shader into a module categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: open description: filename to load as GLSL vertex shader module diff --git a/Resources/Documentation/Gem/hsv2rgb.md b/Resources/Documentation/Gem/hsv2rgb.md index ac388f50c0..7939bf02ed 100644 --- a/Resources/Documentation/Gem/hsv2rgb.md +++ b/Resources/Documentation/Gem/hsv2rgb.md @@ -3,7 +3,7 @@ title: hsv2rgb description: convert RGB color values to HSV categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: list diff --git a/Resources/Documentation/Gem/imageVert.md b/Resources/Documentation/Gem/imageVert.md index 9bc58a915e..bf95f4e42f 100644 --- a/Resources/Documentation/Gem/imageVert.md +++ b/Resources/Documentation/Gem/imageVert.md @@ -3,7 +3,7 @@ title: imagevert description: map luminance to height categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/light.md b/Resources/Documentation/Gem/light.md index bd934f8b45..a9121691cc 100644 --- a/Resources/Documentation/Gem/light.md +++ b/Resources/Documentation/Gem/light.md @@ -3,7 +3,7 @@ title: light description: adds a point-light to the scene. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: gemlist description: diff --git a/Resources/Documentation/Gem/linear_path.md b/Resources/Documentation/Gem/linear_path.md index c25fdce4fb..199bbaa585 100644 --- a/Resources/Documentation/Gem/linear_path.md +++ b/Resources/Documentation/Gem/linear_path.md @@ -3,7 +3,7 @@ title: linear_path description: reads out a table categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: dimension of the table diff --git a/Resources/Documentation/Gem/mesh_line.md b/Resources/Documentation/Gem/mesh_line.md index 5744e49917..7c04326df5 100644 --- a/Resources/Documentation/Gem/mesh_line.md +++ b/Resources/Documentation/Gem/mesh_line.md @@ -3,7 +3,7 @@ title: mesh_line description: renders a mesh in a line categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: draw description: line, default or point diff --git a/Resources/Documentation/Gem/mesh_square.md b/Resources/Documentation/Gem/mesh_square.md index 82af99b2a6..54a823c47f 100644 --- a/Resources/Documentation/Gem/mesh_square.md +++ b/Resources/Documentation/Gem/mesh_square.md @@ -3,7 +3,7 @@ title: mesh_square description: renders a mesh in a square categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: resolution of the mesh diff --git a/Resources/Documentation/Gem/model.md b/Resources/Documentation/Gem/model.md index 98ecdf89bf..1fcdd69b71 100644 --- a/Resources/Documentation/Gem/model.md +++ b/Resources/Documentation/Gem/model.md @@ -3,7 +3,7 @@ title: model description: renders an Alias/Wavefront model categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: description: name of an OBJ-file to be loaded diff --git a/Resources/Documentation/Gem/multimodel.md b/Resources/Documentation/Gem/multimodel.md index 77a1f88857..0d668d024d 100644 --- a/Resources/Documentation/Gem/multimodel.md +++ b/Resources/Documentation/Gem/multimodel.md @@ -3,7 +3,7 @@ title: multimodel description: load multiple Alias/Wavefront models and render one of them categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: open description: load models with a specified file name pattern, base number, top number, and skip rate. diff --git a/Resources/Documentation/Gem/newWave.md b/Resources/Documentation/Gem/newWave.md index 820e183c65..1da2f2036c 100644 --- a/Resources/Documentation/Gem/newWave.md +++ b/Resources/Documentation/Gem/newWave.md @@ -3,7 +3,7 @@ title: newWaves description: renders a waving square (mass-spring-system) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: X grid resolution diff --git a/Resources/Documentation/Gem/ortho.md b/Resources/Documentation/Gem/ortho.md index 075186e4f2..cacef64633 100644 --- a/Resources/Documentation/Gem/ortho.md +++ b/Resources/Documentation/Gem/ortho.md @@ -3,7 +3,7 @@ title: ortho description: orthographic rendering categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: float description: turn orthographic rendering ON (default) or OFF diff --git a/Resources/Documentation/Gem/part_color.md b/Resources/Documentation/Gem/part_color.md index 0903629b45..4637c0ed4a 100644 --- a/Resources/Documentation/Gem/part_color.md +++ b/Resources/Documentation/Gem/part_color.md @@ -3,7 +3,7 @@ title: part_color description: defines color of particles categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/part_damp.md b/Resources/Documentation/Gem/part_damp.md index edaa7108c6..bc2af83ade 100644 --- a/Resources/Documentation/Gem/part_damp.md +++ b/Resources/Documentation/Gem/part_damp.md @@ -3,7 +3,7 @@ title: part_damp description: change velocity of particles categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: V1 diff --git a/Resources/Documentation/Gem/part_draw.md b/Resources/Documentation/Gem/part_draw.md index f9a2dd61c1..4c15892c51 100644 --- a/Resources/Documentation/Gem/part_draw.md +++ b/Resources/Documentation/Gem/part_draw.md @@ -3,7 +3,7 @@ title: part_draw description: draw a particle system categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/part_follow.md b/Resources/Documentation/Gem/part_follow.md index d8e75aec5d..6b9c69b261 100644 --- a/Resources/Documentation/Gem/part_follow.md +++ b/Resources/Documentation/Gem/part_follow.md @@ -3,7 +3,7 @@ title: part_follow description: make particles follow each other. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: particle follow diff --git a/Resources/Documentation/Gem/part_gravity.md b/Resources/Documentation/Gem/part_gravity.md index 74346a3519..3f5d5df139 100644 --- a/Resources/Documentation/Gem/part_gravity.md +++ b/Resources/Documentation/Gem/part_gravity.md @@ -3,7 +3,7 @@ title: part_gravity description: sets the gravity-vector of the particle-system categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: gravitation vector x diff --git a/Resources/Documentation/Gem/part_head.md b/Resources/Documentation/Gem/part_head.md index 57c4fda50d..e6b1c8c1a7 100644 --- a/Resources/Documentation/Gem/part_head.md +++ b/Resources/Documentation/Gem/part_head.md @@ -3,7 +3,7 @@ title: part_head description: starts a particle-system categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: the number of particles that can exist in one instance of time diff --git a/Resources/Documentation/Gem/part_info.md b/Resources/Documentation/Gem/part_info.md index a7a6c3962f..4529a2c5b9 100644 --- a/Resources/Documentation/Gem/part_info.md +++ b/Resources/Documentation/Gem/part_info.md @@ -3,7 +3,7 @@ title: part_info description: gives all available information of all the particles in the system categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/part_killold.md b/Resources/Documentation/Gem/part_killold.md index 96abb9548d..31d6730386 100644 --- a/Resources/Documentation/Gem/part_killold.md +++ b/Resources/Documentation/Gem/part_killold.md @@ -3,7 +3,7 @@ title: part_killold description: kill all particles which are older than the kill time categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: kill time diff --git a/Resources/Documentation/Gem/part_killslow.md b/Resources/Documentation/Gem/part_killslow.md index d2d30f1c2d..66e0d65bc1 100644 --- a/Resources/Documentation/Gem/part_killslow.md +++ b/Resources/Documentation/Gem/part_killslow.md @@ -3,7 +3,7 @@ title: part_killslow description: kill all particles which are slower than the kill speed categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: kill speed diff --git a/Resources/Documentation/Gem/part_orbitpoint.md b/Resources/Documentation/Gem/part_orbitpoint.md index 283525ce30..edf983b744 100644 --- a/Resources/Documentation/Gem/part_orbitpoint.md +++ b/Resources/Documentation/Gem/part_orbitpoint.md @@ -3,7 +3,7 @@ title: part_orbitpoint description: will make the particles orbit about a specified 3d coordinate categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: orbipoint x diff --git a/Resources/Documentation/Gem/part_render.md b/Resources/Documentation/Gem/part_render.md index 4c8e2a15b6..5797cb15bb 100644 --- a/Resources/Documentation/Gem/part_render.md +++ b/Resources/Documentation/Gem/part_render.md @@ -3,7 +3,7 @@ title: part_render description: draws a particle system that was set up with [part_head] and other categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/part_sink.md b/Resources/Documentation/Gem/part_sink.md index 57a0d45990..9055efc789 100644 --- a/Resources/Documentation/Gem/part_sink.md +++ b/Resources/Documentation/Gem/part_sink.md @@ -3,7 +3,7 @@ title: part_sink description: sets up a sink for the particles within the system categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: domain, one of "point", "line", "triangle", "plane", "box", "sphere", "cylinder", "cone", "blob", "disc", "rectangle" diff --git a/Resources/Documentation/Gem/part_size.md b/Resources/Documentation/Gem/part_size.md index c48f7fa47f..8aea0090b7 100644 --- a/Resources/Documentation/Gem/part_size.md +++ b/Resources/Documentation/Gem/part_size.md @@ -3,7 +3,7 @@ title: part_size description: changes the size of the particles of a particle-system categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: particle size diff --git a/Resources/Documentation/Gem/part_source.md b/Resources/Documentation/Gem/part_source.md index 798664c938..a96198e9f0 100644 --- a/Resources/Documentation/Gem/part_source.md +++ b/Resources/Documentation/Gem/part_source.md @@ -3,7 +3,7 @@ title: part_source description: adds a particle-source to a particle system categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: number of particles emitted at each rendering frame diff --git a/Resources/Documentation/Gem/part_targetcolor.md b/Resources/Documentation/Gem/part_targetcolor.md index cfdedcad52..081cac026c 100644 --- a/Resources/Documentation/Gem/part_targetcolor.md +++ b/Resources/Documentation/Gem/part_targetcolor.md @@ -3,7 +3,7 @@ title: part_targetcolor description: changes the color of particles by a scale factor every frame categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: RGB or RGBA vector representing the target color diff --git a/Resources/Documentation/Gem/part_targetsize.md b/Resources/Documentation/Gem/part_targetsize.md index 3441aecd62..6eb9770fc0 100644 --- a/Resources/Documentation/Gem/part_targetsize.md +++ b/Resources/Documentation/Gem/part_targetsize.md @@ -3,7 +3,7 @@ title: part_targetsize description: changes the size of particles by a scale factor every frame categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: size of particles diff --git a/Resources/Documentation/Gem/part_velcone.md b/Resources/Documentation/Gem/part_velcone.md index 3447fe4fd6..12ef9738be 100644 --- a/Resources/Documentation/Gem/part_velcone.md +++ b/Resources/Documentation/Gem/part_velcone.md @@ -3,7 +3,7 @@ title: part_velcone description: sets a cone with a specified height and center to be the velocity-domain of emitted particles categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: default dimensions (x y z height) diff --git a/Resources/Documentation/Gem/part_velocity.md b/Resources/Documentation/Gem/part_velocity.md index 9c6c74fe85..81393b77c2 100644 --- a/Resources/Documentation/Gem/part_velocity.md +++ b/Resources/Documentation/Gem/part_velocity.md @@ -3,7 +3,7 @@ title: part_velocity description: sets velocity of newly emitted particles within the system categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: domain, one of "point", "line", "triangle", "plane", "box", "sphere", "cylinder", "cone", "blob", "disc", "rectangle" diff --git a/Resources/Documentation/Gem/part_velsphere.md b/Resources/Documentation/Gem/part_velsphere.md index a7035f674c..7749a42f0e 100644 --- a/Resources/Documentation/Gem/part_velsphere.md +++ b/Resources/Documentation/Gem/part_velsphere.md @@ -3,7 +3,7 @@ title: part_velsphere description: sets a sphere with a specified radius and centre to be the velocity-domain of emitted particles categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: default dimensions (x y z radius) diff --git a/Resources/Documentation/Gem/part_vertex.md b/Resources/Documentation/Gem/part_vertex.md index 4ed69af1a3..fee3df0c2c 100644 --- a/Resources/Documentation/Gem/part_vertex.md +++ b/Resources/Documentation/Gem/part_vertex.md @@ -3,7 +3,7 @@ title: part_vertex description: adds a particle at the specified offset categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: default position (x y z) diff --git a/Resources/Documentation/Gem/pix_2grey.md b/Resources/Documentation/Gem/pix_2grey.md index f5c8e74ee9..79b85d0ff4 100644 --- a/Resources/Documentation/Gem/pix_2grey.md +++ b/Resources/Documentation/Gem/pix_2grey.md @@ -3,7 +3,7 @@ title: pix_2grey, pix_2gray description: convert a pix to greyscale categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_a_2grey.md b/Resources/Documentation/Gem/pix_a_2grey.md index 54c5eae390..35116179bb 100644 --- a/Resources/Documentation/Gem/pix_a_2grey.md +++ b/Resources/Documentation/Gem/pix_a_2grey.md @@ -3,7 +3,7 @@ title: pix_a_2grey, pix_a_2gray description: convert a pixel to greyscale based on alpha categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_add.md b/Resources/Documentation/Gem/pix_add.md index 385fb8623c..a20f02af4a 100644 --- a/Resources/Documentation/Gem/pix_add.md +++ b/Resources/Documentation/Gem/pix_add.md @@ -3,7 +3,7 @@ title: pix_add description: add 2 images categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_aging.md b/Resources/Documentation/Gem/pix_aging.md index be91896972..16d357de45 100644 --- a/Resources/Documentation/Gem/pix_aging.md +++ b/Resources/Documentation/Gem/pix_aging.md @@ -3,7 +3,7 @@ title: pix_aging description: apply a super8-like aging effect categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: scratch description: add a maximum of scratches diff --git a/Resources/Documentation/Gem/pix_alpha.md b/Resources/Documentation/Gem/pix_alpha.md index 06c9c2d23c..69c65d55b0 100644 --- a/Resources/Documentation/Gem/pix_alpha.md +++ b/Resources/Documentation/Gem/pix_alpha.md @@ -3,7 +3,7 @@ title: pix_alpha description: set the alpha values of an RGBA-pix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_background.md b/Resources/Documentation/Gem/pix_background.md index 31e1190beb..e3860a9bad 100644 --- a/Resources/Documentation/Gem/pix_background.md +++ b/Resources/Documentation/Gem/pix_background.md @@ -3,7 +3,7 @@ title: pix_background description: separate an object from the background categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: reset description: reset the background and capture a new image diff --git a/Resources/Documentation/Gem/pix_backlight.md b/Resources/Documentation/Gem/pix_backlight.md index 5a58b4b05a..5c9801e300 100644 --- a/Resources/Documentation/Gem/pix_backlight.md +++ b/Resources/Documentation/Gem/pix_backlight.md @@ -3,7 +3,7 @@ title: pix_backlight description: radially displace pixels depending on their luminance value, producing a backlighting effect categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: scale-factor diff --git a/Resources/Documentation/Gem/pix_biquad.md b/Resources/Documentation/Gem/pix_biquad.md index 9b02a52d7f..c1dc7ce478 100644 --- a/Resources/Documentation/Gem/pix_biquad.md +++ b/Resources/Documentation/Gem/pix_biquad.md @@ -3,7 +3,7 @@ title: pix_biquad description: time-based IIR filter categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: set description: overwrites the filter-buffers with the next incoming image diff --git a/Resources/Documentation/Gem/pix_bitmask.md b/Resources/Documentation/Gem/pix_bitmask.md index 860bc541aa..f76d6ee8b4 100644 --- a/Resources/Documentation/Gem/pix_bitmask.md +++ b/Resources/Documentation/Gem/pix_bitmask.md @@ -3,7 +3,7 @@ title: pix_bitmask description: mask out pixels categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_blob.md b/Resources/Documentation/Gem/pix_blob.md index f8a97bc3a8..fc927465bb 100644 --- a/Resources/Documentation/Gem/pix_blob.md +++ b/Resources/Documentation/Gem/pix_blob.md @@ -3,7 +3,7 @@ title: pix_blob description: calculate the "center-of-gravity" of a certain (combination of) channel(s) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_blobtracker.md b/Resources/Documentation/Gem/pix_blobtracker.md index 52486aeefd..07f0b75f14 100644 --- a/Resources/Documentation/Gem/pix_blobtracker.md +++ b/Resources/Documentation/Gem/pix_blobtracker.md @@ -3,7 +3,7 @@ title: pix_blobtracker description: blob detector and tracker categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: max number of blobs to detect diff --git a/Resources/Documentation/Gem/pix_blur.md b/Resources/Documentation/Gem/pix_blur.md index 57d39ac9b1..8df2e806c1 100644 --- a/Resources/Documentation/Gem/pix_blur.md +++ b/Resources/Documentation/Gem/pix_blur.md @@ -3,7 +3,7 @@ title: pix_blur description: apply a blur effect to an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_buf.md b/Resources/Documentation/Gem/pix_buf.md index dd4d81a5dd..f04b9f4265 100644 --- a/Resources/Documentation/Gem/pix_buf.md +++ b/Resources/Documentation/Gem/pix_buf.md @@ -3,7 +3,7 @@ title: pix_buf description: buffer a pix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: the default value to force processing diff --git a/Resources/Documentation/Gem/pix_buffer.md b/Resources/Documentation/Gem/pix_buffer.md index 2cd0bd5119..8c66f6e0b9 100644 --- a/Resources/Documentation/Gem/pix_buffer.md +++ b/Resources/Documentation/Gem/pix_buffer.md @@ -3,7 +3,7 @@ title: pix_buffer description: a storage place for a number of images categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: allocate description: preallocate memory for images diff --git a/Resources/Documentation/Gem/pix_buffer_filmopen.md b/Resources/Documentation/Gem/pix_buffer_filmopen.md index bbc8dd394f..fab07e25dd 100644 --- a/Resources/Documentation/Gem/pix_buffer_filmopen.md +++ b/Resources/Documentation/Gem/pix_buffer_filmopen.md @@ -3,7 +3,7 @@ title: pix_buffer_filmopen description: reads a movie into a named buffer in the pix_buffer object categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: buffer name diff --git a/Resources/Documentation/Gem/pix_buffer_read.md b/Resources/Documentation/Gem/pix_buffer_read.md index f6b22e789c..f869d2d509 100644 --- a/Resources/Documentation/Gem/pix_buffer_read.md +++ b/Resources/Documentation/Gem/pix_buffer_read.md @@ -3,7 +3,7 @@ title: pix_buffer_read description: reads an image from the named buffer provided by [pix_buffer] categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: diff --git a/Resources/Documentation/Gem/pix_buffer_write.md b/Resources/Documentation/Gem/pix_buffer_write.md index 60481100a2..391baf837a 100644 --- a/Resources/Documentation/Gem/pix_buffer_write.md +++ b/Resources/Documentation/Gem/pix_buffer_write.md @@ -3,7 +3,7 @@ title: pix_buffer_write description: writes an image into a named buffer created by the [pix_buffer] object categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: diff --git a/Resources/Documentation/Gem/pix_chroma_key.md b/Resources/Documentation/Gem/pix_chroma_key.md index ff34e1252b..02d621fc59 100644 --- a/Resources/Documentation/Gem/pix_chroma_key.md +++ b/Resources/Documentation/Gem/pix_chroma_key.md @@ -3,7 +3,7 @@ title: pix_chroma_key description: mix 2 images based on their color categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: float description: which stream is the key-source (0=left stream, 1=right stream) diff --git a/Resources/Documentation/Gem/pix_clearblock.md b/Resources/Documentation/Gem/pix_clearblock.md index 146c5871df..a64cc700dd 100644 --- a/Resources/Documentation/Gem/pix_clearblock.md +++ b/Resources/Documentation/Gem/pix_clearblock.md @@ -3,7 +3,7 @@ title: pix_clearblock description: clear a block in an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: diff --git a/Resources/Documentation/Gem/pix_color.md b/Resources/Documentation/Gem/pix_color.md index 26438aeb94..1229b8c583 100644 --- a/Resources/Documentation/Gem/pix_color.md +++ b/Resources/Documentation/Gem/pix_color.md @@ -3,7 +3,7 @@ title: pix_color, pix_colour description: set the colour-channels of an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_coloralpha.md b/Resources/Documentation/Gem/pix_coloralpha.md index fb649c60b2..b40304efbe 100644 --- a/Resources/Documentation/Gem/pix_coloralpha.md +++ b/Resources/Documentation/Gem/pix_coloralpha.md @@ -3,7 +3,7 @@ title: pix_coloralpha, pix_colouralpha description: calculate the Alpha-channel from the RGB-data. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: turn on/off diff --git a/Resources/Documentation/Gem/pix_colorclassify.md b/Resources/Documentation/Gem/pix_colorclassify.md index 64c7d73a33..0f0a5685d3 100644 --- a/Resources/Documentation/Gem/pix_colorclassify.md +++ b/Resources/Documentation/Gem/pix_colorclassify.md @@ -3,7 +3,7 @@ title: pix_colorclassify, pix_colourclassify description: detects color classes in an image. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_colormatrix.md b/Resources/Documentation/Gem/pix_colormatrix.md index d489ee12a0..7e401d807a 100644 --- a/Resources/Documentation/Gem/pix_colormatrix.md +++ b/Resources/Documentation/Gem/pix_colormatrix.md @@ -3,7 +3,7 @@ title: pix_colormatrix, pix_colourmatrix description: transform the pixel values by a matrix. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_colorreduce.md b/Resources/Documentation/Gem/pix_colorreduce.md index 42cbdcff29..8b0085de8f 100644 --- a/Resources/Documentation/Gem/pix_colorreduce.md +++ b/Resources/Documentation/Gem/pix_colorreduce.md @@ -3,7 +3,7 @@ title: pix_colorreduce, pix_colourreduce description: reduce the number of colours in the image. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: number of colours diff --git a/Resources/Documentation/Gem/pix_compare.md b/Resources/Documentation/Gem/pix_compare.md index 0400887855..96eb7eaf9d 100644 --- a/Resources/Documentation/Gem/pix_compare.md +++ b/Resources/Documentation/Gem/pix_compare.md @@ -3,7 +3,7 @@ title: pix_compare description: mix 2 images based on their luminance. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: take lower or higher valued pixel diff --git a/Resources/Documentation/Gem/pix_composite.md b/Resources/Documentation/Gem/pix_composite.md index f0601a35d6..79c0ae7f1c 100644 --- a/Resources/Documentation/Gem/pix_composite.md +++ b/Resources/Documentation/Gem/pix_composite.md @@ -3,7 +3,7 @@ title: pix_composite description: alpha-blend 2 images. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_contrast.md b/Resources/Documentation/Gem/pix_contrast.md index dd953e264d..86a4dff2c0 100644 --- a/Resources/Documentation/Gem/pix_contrast.md +++ b/Resources/Documentation/Gem/pix_contrast.md @@ -3,7 +3,7 @@ title: pix_contrast description: change contrast and saturation of an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: contrast (>=0, default: 1) diff --git a/Resources/Documentation/Gem/pix_convert.md b/Resources/Documentation/Gem/pix_convert.md index 0f49ec9f4b..22cefdc01c 100644 --- a/Resources/Documentation/Gem/pix_convert.md +++ b/Resources/Documentation/Gem/pix_convert.md @@ -3,7 +3,7 @@ title: pix_convert description: convert the colorspace of an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: colorspace to convert to diff --git a/Resources/Documentation/Gem/pix_convolve.md b/Resources/Documentation/Gem/pix_convolve.md index 9eef4ea65f..93c7c929ee 100644 --- a/Resources/Documentation/Gem/pix_convolve.md +++ b/Resources/Documentation/Gem/pix_convolve.md @@ -3,7 +3,7 @@ title: pix_convolve description: apply a convolution kernel to an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: matrix dimensions diff --git a/Resources/Documentation/Gem/pix_coordinate.md b/Resources/Documentation/Gem/pix_coordinate.md index 5e107570e9..b6faaa46f6 100644 --- a/Resources/Documentation/Gem/pix_coordinate.md +++ b/Resources/Documentation/Gem/pix_coordinate.md @@ -3,7 +3,7 @@ title: pix_coordinate description: set the texture-coordinates for an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: null inlets: 1st: diff --git a/Resources/Documentation/Gem/pix_crop.md b/Resources/Documentation/Gem/pix_crop.md index a072353520..3cf46435f5 100644 --- a/Resources/Documentation/Gem/pix_crop.md +++ b/Resources/Documentation/Gem/pix_crop.md @@ -3,7 +3,7 @@ title: pix_crop description: get a subimage of an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - name: offsetX type: float diff --git a/Resources/Documentation/Gem/pix_curve.md b/Resources/Documentation/Gem/pix_curve.md index 753d742c0c..97f80d1f28 100644 --- a/Resources/Documentation/Gem/pix_curve.md +++ b/Resources/Documentation/Gem/pix_curve.md @@ -3,7 +3,7 @@ title: pix_curve description: applies color-curves stored in arrays to an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: table name per channel diff --git a/Resources/Documentation/Gem/pix_data.md b/Resources/Documentation/Gem/pix_data.md index 1b21462b2f..8065c93a66 100644 --- a/Resources/Documentation/Gem/pix_data.md +++ b/Resources/Documentation/Gem/pix_data.md @@ -3,7 +3,7 @@ title: pix_data description: gets the color of a specified pixel within an image when triggered categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: xpos diff --git a/Resources/Documentation/Gem/pix_deinterlace.md b/Resources/Documentation/Gem/pix_deinterlace.md index 9cacadc586..04db5ec13a 100644 --- a/Resources/Documentation/Gem/pix_deinterlace.md +++ b/Resources/Documentation/Gem/pix_deinterlace.md @@ -3,7 +3,7 @@ title: pix_deinterlace description: deinterlaces an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: mode description: enforce (1) or not (0) diff --git a/Resources/Documentation/Gem/pix_delay.md b/Resources/Documentation/Gem/pix_delay.md index c1b790fccd..0917812d27 100644 --- a/Resources/Documentation/Gem/pix_delay.md +++ b/Resources/Documentation/Gem/pix_delay.md @@ -3,7 +3,7 @@ title: pix_delay description: delay a series of images categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: max number of delayed frames diff --git a/Resources/Documentation/Gem/pix_diff.md b/Resources/Documentation/Gem/pix_diff.md index d6b31acf99..fca171c5a5 100644 --- a/Resources/Documentation/Gem/pix_diff.md +++ b/Resources/Documentation/Gem/pix_diff.md @@ -3,7 +3,7 @@ title: pix_diff description: get the absolute value of the difference between 2 images categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: diff --git a/Resources/Documentation/Gem/pix_dot.md b/Resources/Documentation/Gem/pix_dot.md index 251158473a..3b4e3a13e3 100644 --- a/Resources/Documentation/Gem/pix_dot.md +++ b/Resources/Documentation/Gem/pix_dot.md @@ -3,7 +3,7 @@ title: pix_dot description: simplify an image by representing each segment with a white dot whose size is relative to the luminance of the original segment categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: size of the dots diff --git a/Resources/Documentation/Gem/pix_draw.md b/Resources/Documentation/Gem/pix_draw.md index 5a145669b0..f0c60c77e8 100644 --- a/Resources/Documentation/Gem/pix_draw.md +++ b/Resources/Documentation/Gem/pix_draw.md @@ -3,7 +3,7 @@ title: pix_draw description: draw pixels on the screen without doing any texture mapping. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: diff --git a/Resources/Documentation/Gem/pix_dump.md b/Resources/Documentation/Gem/pix_dump.md index 54e9f7e173..ac15f0e0fd 100644 --- a/Resources/Documentation/Gem/pix_dump.md +++ b/Resources/Documentation/Gem/pix_dump.md @@ -3,7 +3,7 @@ title: pix_dump description: dump all the pixel-data of an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: bytemode description: set normalization on or off diff --git a/Resources/Documentation/Gem/pix_duotone.md b/Resources/Documentation/Gem/pix_duotone.md index 9e4929560f..7018cb2bc3 100644 --- a/Resources/Documentation/Gem/pix_duotone.md +++ b/Resources/Documentation/Gem/pix_duotone.md @@ -3,7 +3,7 @@ title: pix_duotone description: reduce the number of colours by thresholding categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_equal.md b/Resources/Documentation/Gem/pix_equal.md index 82ad435295..82f0a39a5a 100644 --- a/Resources/Documentation/Gem/pix_equal.md +++ b/Resources/Documentation/Gem/pix_equal.md @@ -3,7 +3,7 @@ title: pix_equal description: marks the pixels nearly equal to a given color categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_film.md b/Resources/Documentation/Gem/pix_film.md index d49e1fd203..20db31aa8e 100644 --- a/Resources/Documentation/Gem/pix_film.md +++ b/Resources/Documentation/Gem/pix_film.md @@ -3,7 +3,7 @@ title: pix_film description: load in a movie-file categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: file to load initially diff --git a/Resources/Documentation/Gem/pix_flip.md b/Resources/Documentation/Gem/pix_flip.md index 8168c7a634..0268ccd344 100644 --- a/Resources/Documentation/Gem/pix_flip.md +++ b/Resources/Documentation/Gem/pix_flip.md @@ -3,7 +3,7 @@ title: pix_flip description: flips the image along an axis categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: none description: set flip mode to none diff --git a/Resources/Documentation/Gem/pix_freeframe.md b/Resources/Documentation/Gem/pix_freeframe.md index 91ac423306..1ff33cec99 100644 --- a/Resources/Documentation/Gem/pix_freeframe.md +++ b/Resources/Documentation/Gem/pix_freeframe.md @@ -3,7 +3,7 @@ title: pix_freeframe description: run a FreeFrame effect categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: the plugin to load diff --git a/Resources/Documentation/Gem/pix_frei0r.md b/Resources/Documentation/Gem/pix_frei0r.md index 15699a3034..24b80a353b 100644 --- a/Resources/Documentation/Gem/pix_frei0r.md +++ b/Resources/Documentation/Gem/pix_frei0r.md @@ -3,7 +3,7 @@ title: pix_frei0r description: run a Frei0r effect categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: the plugin to load diff --git a/Resources/Documentation/Gem/pix_gain.md b/Resources/Documentation/Gem/pix_gain.md index 24170ba2b9..e2a28e0be3 100644 --- a/Resources/Documentation/Gem/pix_gain.md +++ b/Resources/Documentation/Gem/pix_gain.md @@ -3,7 +3,7 @@ title: pix_gain description: multiply pixel-values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: description: gain vector diff --git a/Resources/Documentation/Gem/pix_grey.md b/Resources/Documentation/Gem/pix_grey.md index ac6319432f..a7b3cb0973 100644 --- a/Resources/Documentation/Gem/pix_grey.md +++ b/Resources/Documentation/Gem/pix_grey.md @@ -3,7 +3,7 @@ title: pix_grey, pix_gray description: convert the colorspace of an image to grey categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: turn conversion on/off diff --git a/Resources/Documentation/Gem/pix_halftone.md b/Resources/Documentation/Gem/pix_halftone.md index 734aceb436..48b504b3cc 100644 --- a/Resources/Documentation/Gem/pix_halftone.md +++ b/Resources/Documentation/Gem/pix_halftone.md @@ -3,7 +3,7 @@ title: pix_halftone description: draws the input using halftone patterns categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: style description: selects a style diff --git a/Resources/Documentation/Gem/pix_histo.md b/Resources/Documentation/Gem/pix_histo.md index a93bb93ddf..3a87a2b3e7 100644 --- a/Resources/Documentation/Gem/pix_histo.md +++ b/Resources/Documentation/Gem/pix_histo.md @@ -3,7 +3,7 @@ title: pix_histo description: gets the histogram (density function) of an image. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: description: tables to store histogram diff --git a/Resources/Documentation/Gem/pix_hsv2rgb.md b/Resources/Documentation/Gem/pix_hsv2rgb.md index fc481cdfaa..2ff4dd7d6f 100644 --- a/Resources/Documentation/Gem/pix_hsv2rgb.md +++ b/Resources/Documentation/Gem/pix_hsv2rgb.md @@ -3,7 +3,7 @@ title: pix_hsv2rgb description: convert HSV into RGB categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_image.md b/Resources/Documentation/Gem/pix_image.md index 65261c0037..9ce8b15731 100644 --- a/Resources/Documentation/Gem/pix_image.md +++ b/Resources/Documentation/Gem/pix_image.md @@ -3,7 +3,7 @@ title: pix_image description: loads in an image to be used as texture, bitblit, or something else categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: filename diff --git a/Resources/Documentation/Gem/pix_imageInPlace.md b/Resources/Documentation/Gem/pix_imageInPlace.md index 03f8d42f83..ec9d5d331e 100644 --- a/Resources/Documentation/Gem/pix_imageInPlace.md +++ b/Resources/Documentation/Gem/pix_imageInPlace.md @@ -3,7 +3,7 @@ title: pix_imageInPlace description: loads in multiple image files categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: filename (with wildcard *) for images to load diff --git a/Resources/Documentation/Gem/pix_info.md b/Resources/Documentation/Gem/pix_info.md index ecc8839d16..3a7db61e76 100644 --- a/Resources/Documentation/Gem/pix_info.md +++ b/Resources/Documentation/Gem/pix_info.md @@ -3,7 +3,7 @@ title: pix_info description: get information about the current image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics flags: - name: -m description: message mode diff --git a/Resources/Documentation/Gem/pix_invert.md b/Resources/Documentation/Gem/pix_invert.md index 5c84ccac16..39cc3c7c85 100644 --- a/Resources/Documentation/Gem/pix_invert.md +++ b/Resources/Documentation/Gem/pix_invert.md @@ -3,7 +3,7 @@ title: pix_invert description: invert an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_kaleidoscope.md b/Resources/Documentation/Gem/pix_kaleidoscope.md index e532bc84cc..681ead6e15 100644 --- a/Resources/Documentation/Gem/pix_kaleidoscope.md +++ b/Resources/Documentation/Gem/pix_kaleidoscope.md @@ -3,7 +3,7 @@ title: kaleidoscope description: kaleidoscope effect categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_levels.md b/Resources/Documentation/Gem/pix_levels.md index 579a834343..734603e136 100644 --- a/Resources/Documentation/Gem/pix_levels.md +++ b/Resources/Documentation/Gem/pix_levels.md @@ -3,7 +3,7 @@ title: pix_levels description: level adjustment categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: uni description: choose between uniform adjustment(1) or per-channel-adjustment (0) diff --git a/Resources/Documentation/Gem/pix_lumaoffset.md b/Resources/Documentation/Gem/pix_lumaoffset.md index cd4d0fd51d..7e85c226a0 100644 --- a/Resources/Documentation/Gem/pix_lumaoffset.md +++ b/Resources/Documentation/Gem/pix_lumaoffset.md @@ -3,7 +3,7 @@ title: pix_lumaoffset description: offset pixels depending on the luminance categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: fill description: draw filled lines diff --git a/Resources/Documentation/Gem/pix_mask.md b/Resources/Documentation/Gem/pix_mask.md index d176b1d02a..01804508f5 100644 --- a/Resources/Documentation/Gem/pix_mask.md +++ b/Resources/Documentation/Gem/pix_mask.md @@ -2,7 +2,7 @@ title: pix_mask description: mask out a pix - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_mean_color.md b/Resources/Documentation/Gem/pix_mean_color.md index ec0391a38c..7e9c152787 100644 --- a/Resources/Documentation/Gem/pix_mean_color.md +++ b/Resources/Documentation/Gem/pix_mean_color.md @@ -3,7 +3,7 @@ title: pix_mean_color description: get the mean color of the current image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_metaimage.md b/Resources/Documentation/Gem/pix_metaimage.md index 51fd639f18..6b20aa3424 100644 --- a/Resources/Documentation/Gem/pix_metaimage.md +++ b/Resources/Documentation/Gem/pix_metaimage.md @@ -3,7 +3,7 @@ title: pix_metaimage description: display a pix by itself categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: size diff --git a/Resources/Documentation/Gem/pix_mix.md b/Resources/Documentation/Gem/pix_mix.md index 2b18be779d..d334597cf9 100644 --- a/Resources/Documentation/Gem/pix_mix.md +++ b/Resources/Documentation/Gem/pix_mix.md @@ -3,7 +3,7 @@ title: pix_mix description: mix 2 images based on mixing factors categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: left gain diff --git a/Resources/Documentation/Gem/pix_motionblur.md b/Resources/Documentation/Gem/pix_motionblur.md index af88b2c1b7..c3ae36d9b7 100644 --- a/Resources/Documentation/Gem/pix_motionblur.md +++ b/Resources/Documentation/Gem/pix_motionblur.md @@ -3,7 +3,7 @@ title: pix_motionblur description: apply motion blurring on a series of images categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: blur-factor (0=no blurring, 1=only blurring) diff --git a/Resources/Documentation/Gem/pix_movement.md b/Resources/Documentation/Gem/pix_movement.md index 5b88895b54..fb9ce633ff 100644 --- a/Resources/Documentation/Gem/pix_movement.md +++ b/Resources/Documentation/Gem/pix_movement.md @@ -3,7 +3,7 @@ title: pix_movement description: time-based IIR filter for images categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: change threshold (0-1) diff --git a/Resources/Documentation/Gem/pix_movement2.md b/Resources/Documentation/Gem/pix_movement2.md index 049639f580..5b600dceb9 100644 --- a/Resources/Documentation/Gem/pix_movement2.md +++ b/Resources/Documentation/Gem/pix_movement2.md @@ -3,7 +3,7 @@ title: pix_movement2 description: time-based IIR-filter for motion detection categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: low threshold (0-1) diff --git a/Resources/Documentation/Gem/pix_movie.md b/Resources/Documentation/Gem/pix_movie.md index b0abbe54e2..090bdeaeab 100644 --- a/Resources/Documentation/Gem/pix_movie.md +++ b/Resources/Documentation/Gem/pix_movie.md @@ -3,7 +3,7 @@ title: pix_movie description: loads in a preproduced digital video to be used as a texture, bitblit, or something else categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: file to load initially diff --git a/Resources/Documentation/Gem/pix_multiblob.md b/Resources/Documentation/Gem/pix_multiblob.md index c18f8a8421..95e733c219 100644 --- a/Resources/Documentation/Gem/pix_multiblob.md +++ b/Resources/Documentation/Gem/pix_multiblob.md @@ -3,7 +3,7 @@ title: pix_multiblob description: blob detector (for multiple blobs) categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: max number N of blobs to detect diff --git a/Resources/Documentation/Gem/pix_multiimage.md b/Resources/Documentation/Gem/pix_multiimage.md index 9e43941ee2..47d25ac1bf 100644 --- a/Resources/Documentation/Gem/pix_multiimage.md +++ b/Resources/Documentation/Gem/pix_multiimage.md @@ -3,7 +3,7 @@ title: pix_multiimage description: loads in an image to be used as a texture, bitblit, or something else. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: filename (with wildcard *) for images to load diff --git a/Resources/Documentation/Gem/pix_multiply.md b/Resources/Documentation/Gem/pix_multiply.md index 7f36f86273..ae9067ff67 100644 --- a/Resources/Documentation/Gem/pix_multiply.md +++ b/Resources/Documentation/Gem/pix_multiply.md @@ -3,7 +3,7 @@ title: pix_multiply description: multiply 2 images categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_multitexture.md b/Resources/Documentation/Gem/pix_multitexture.md index cd3b2a4568..af1b384cc3 100644 --- a/Resources/Documentation/Gem/pix_multitexture.md +++ b/Resources/Documentation/Gem/pix_multitexture.md @@ -3,7 +3,7 @@ title: pix_multitexture description: apply multiple texture mappings to the current network categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: number of texture to use diff --git a/Resources/Documentation/Gem/pix_noise.md b/Resources/Documentation/Gem/pix_noise.md index 55e7ea9d56..c3b72e69a5 100644 --- a/Resources/Documentation/Gem/pix_noise.md +++ b/Resources/Documentation/Gem/pix_noise.md @@ -3,7 +3,7 @@ title: pix_noise description: generate a noise image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: width diff --git a/Resources/Documentation/Gem/pix_normalize.md b/Resources/Documentation/Gem/pix_normalize.md index 2d71f14173..af31765cbc 100644 --- a/Resources/Documentation/Gem/pix_normalize.md +++ b/Resources/Documentation/Gem/pix_normalize.md @@ -3,7 +3,7 @@ title: pix_normalize description: normalize an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: diff --git a/Resources/Documentation/Gem/pix_offset.md b/Resources/Documentation/Gem/pix_offset.md index 3b2a432808..6fa4d0f529 100644 --- a/Resources/Documentation/Gem/pix_offset.md +++ b/Resources/Documentation/Gem/pix_offset.md @@ -3,7 +3,7 @@ title: pix_offset description: add an offset to the color categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_posterize.md b/Resources/Documentation/Gem/pix_posterize.md index 175a3a875e..cc53d63879 100644 --- a/Resources/Documentation/Gem/pix_posterize.md +++ b/Resources/Documentation/Gem/pix_posterize.md @@ -3,7 +3,7 @@ title: pix_posterize description: posterization effect categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: factor diff --git a/Resources/Documentation/Gem/pix_puzzle.md b/Resources/Documentation/Gem/pix_puzzle.md index 0bd76b4a7a..e35c47002c 100644 --- a/Resources/Documentation/Gem/pix_puzzle.md +++ b/Resources/Documentation/Gem/pix_puzzle.md @@ -3,7 +3,7 @@ title: pix_puzzle description: shuffle an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: size description: number of elements in x/y diff --git a/Resources/Documentation/Gem/pix_rds.md b/Resources/Documentation/Gem/pix_rds.md index 2484988b26..4d19ecc803 100644 --- a/Resources/Documentation/Gem/pix_rds.md +++ b/Resources/Documentation/Gem/pix_rds.md @@ -3,7 +3,7 @@ title: pix_rds description: random dot stereogram for luminance categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: stride distance diff --git a/Resources/Documentation/Gem/pix_record.md b/Resources/Documentation/Gem/pix_record.md index bed64ebaf0..2cda060cf2 100644 --- a/Resources/Documentation/Gem/pix_record.md +++ b/Resources/Documentation/Gem/pix_record.md @@ -3,7 +3,7 @@ title: pix_record description: write a sequence of pixes to a movie file categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: file description: specify the file for writing diff --git a/Resources/Documentation/Gem/pix_rectangle.md b/Resources/Documentation/Gem/pix_rectangle.md index 45107673e1..4044528552 100644 --- a/Resources/Documentation/Gem/pix_rectangle.md +++ b/Resources/Documentation/Gem/pix_rectangle.md @@ -3,7 +3,7 @@ title: pix_rectangle description: draw a rectangle into a pix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_refraction.md b/Resources/Documentation/Gem/pix_refraction.md index 8942e9ddbe..eb667a1c94 100644 --- a/Resources/Documentation/Gem/pix_refraction.md +++ b/Resources/Documentation/Gem/pix_refraction.md @@ -3,7 +3,7 @@ title: pix_refraction description: display a pix through glass bricks categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: width description: width of each cell diff --git a/Resources/Documentation/Gem/pix_resize.md b/Resources/Documentation/Gem/pix_resize.md index 77fc8eeaf7..3134675cb7 100644 --- a/Resources/Documentation/Gem/pix_resize.md +++ b/Resources/Documentation/Gem/pix_resize.md @@ -3,7 +3,7 @@ title: pix_resize description: resize an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: xsize diff --git a/Resources/Documentation/Gem/pix_rgb2hsv.md b/Resources/Documentation/Gem/pix_rgb2hsv.md index 8a3ee09837..1e5e2222fe 100644 --- a/Resources/Documentation/Gem/pix_rgb2hsv.md +++ b/Resources/Documentation/Gem/pix_rgb2hsv.md @@ -3,7 +3,7 @@ title: pix_rgb2hsv description: convert RGB into HSV categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_rgba.md b/Resources/Documentation/Gem/pix_rgba.md index 1894dd2956..573209890a 100644 --- a/Resources/Documentation/Gem/pix_rgba.md +++ b/Resources/Documentation/Gem/pix_rgba.md @@ -3,7 +3,7 @@ title: pix_rgba description: convert the colorspace of an image to RGBA categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_roi.md b/Resources/Documentation/Gem/pix_roi.md index 565441eeb3..d7c5ab7693 100644 --- a/Resources/Documentation/Gem/pix_roi.md +++ b/Resources/Documentation/Gem/pix_roi.md @@ -3,7 +3,7 @@ title: pix_roi description: set the region-of-interest of an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: x1 diff --git a/Resources/Documentation/Gem/pix_roll.md b/Resources/Documentation/Gem/pix_roll.md index 92f1851583..15b74dce01 100644 --- a/Resources/Documentation/Gem/pix_roll.md +++ b/Resources/Documentation/Gem/pix_roll.md @@ -3,7 +3,7 @@ title: pix_roll description: (sc)roll through an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: axis description: scroll(0=default) or roll(1) diff --git a/Resources/Documentation/Gem/pix_rtx.md b/Resources/Documentation/Gem/pix_rtx.md index 20babdaf8c..fc051ac3e8 100644 --- a/Resources/Documentation/Gem/pix_rtx.md +++ b/Resources/Documentation/Gem/pix_rtx.md @@ -3,7 +3,7 @@ title: pix_rtx description: RealTime vs. X transformation categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: null methods: - type: set diff --git a/Resources/Documentation/Gem/pix_scanline.md b/Resources/Documentation/Gem/pix_scanline.md index 483e61cdfa..ef7f2e38f7 100644 --- a/Resources/Documentation/Gem/pix_scanline.md +++ b/Resources/Documentation/Gem/pix_scanline.md @@ -3,7 +3,7 @@ title: pix_scanline description: scan lines of an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: null methods: - type: mode diff --git a/Resources/Documentation/Gem/pix_set.md b/Resources/Documentation/Gem/pix_set.md index 6d619059f2..3be036fa11 100644 --- a/Resources/Documentation/Gem/pix_set.md +++ b/Resources/Documentation/Gem/pix_set.md @@ -3,7 +3,7 @@ title: pix_set description: set the pixel-data of an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: width diff --git a/Resources/Documentation/Gem/pix_share_read.md b/Resources/Documentation/Gem/pix_share_read.md index 81a41c118a..567595d6bf 100644 --- a/Resources/Documentation/Gem/pix_share_read.md +++ b/Resources/Documentation/Gem/pix_share_read.md @@ -3,7 +3,7 @@ title: pix_share_read description: read pixels from a shared memory region categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: ID diff --git a/Resources/Documentation/Gem/pix_share_write.md b/Resources/Documentation/Gem/pix_share_write.md index a81d2e940b..87c22f3203 100644 --- a/Resources/Documentation/Gem/pix_share_write.md +++ b/Resources/Documentation/Gem/pix_share_write.md @@ -3,7 +3,7 @@ title: pix_share_write description: write pixels to a shared memory region categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: ID diff --git a/Resources/Documentation/Gem/pix_snap.md b/Resources/Documentation/Gem/pix_snap.md index 8260aeab90..d47fd925b3 100644 --- a/Resources/Documentation/Gem/pix_snap.md +++ b/Resources/Documentation/Gem/pix_snap.md @@ -3,7 +3,7 @@ title: pix_snap description: take a screenshot and convert it to a pix categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: x offset, y offset, width, height diff --git a/Resources/Documentation/Gem/pix_snap2tex.md b/Resources/Documentation/Gem/pix_snap2tex.md index 1d3666ca39..212a36d5dd 100644 --- a/Resources/Documentation/Gem/pix_snap2tex.md +++ b/Resources/Documentation/Gem/pix_snap2tex.md @@ -3,7 +3,7 @@ title: pix_snap2tex description: take a screenshot and texture it categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: X offset diff --git a/Resources/Documentation/Gem/pix_subtract.md b/Resources/Documentation/Gem/pix_subtract.md index 06a1b28241..0ab53c176b 100644 --- a/Resources/Documentation/Gem/pix_subtract.md +++ b/Resources/Documentation/Gem/pix_subtract.md @@ -3,7 +3,7 @@ title: pix_subtract description: subtract 2 images categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_tIIR.md b/Resources/Documentation/Gem/pix_tIIR.md index ed79cabd44..689dcbdf3f 100644 --- a/Resources/Documentation/Gem/pix_tIIR.md +++ b/Resources/Documentation/Gem/pix_tIIR.md @@ -3,7 +3,7 @@ title: pix_tIIR description: timebased IIR-filter categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: IIR coefficients diff --git a/Resources/Documentation/Gem/pix_takealpha.md b/Resources/Documentation/Gem/pix_takealpha.md index f9eba8de84..d004d2c2da 100644 --- a/Resources/Documentation/Gem/pix_takealpha.md +++ b/Resources/Documentation/Gem/pix_takealpha.md @@ -3,7 +3,7 @@ title: pix_takealpha description: transfer the alpha-channel categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: null methods: null inlets: diff --git a/Resources/Documentation/Gem/pix_texture.md b/Resources/Documentation/Gem/pix_texture.md index e06a7cc403..162c49f9a9 100644 --- a/Resources/Documentation/Gem/pix_texture.md +++ b/Resources/Documentation/Gem/pix_texture.md @@ -3,7 +3,7 @@ title: pix_texture description: apply texture mapping categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: env description: texture environment mode (0=GL_REPLACE, 1=GL_DECAL, 2=GL_BLEND, 3=GL_ADD, 4=GL_COMBINE, >4=GL_MODULATE) diff --git a/Resources/Documentation/Gem/pix_threshold.md b/Resources/Documentation/Gem/pix_threshold.md index cc022436d4..ebe59baa7d 100644 --- a/Resources/Documentation/Gem/pix_threshold.md +++ b/Resources/Documentation/Gem/pix_threshold.md @@ -3,7 +3,7 @@ title: pix_threshold description: apply a threshold to pixels categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: threshold (scalar/vector) diff --git a/Resources/Documentation/Gem/pix_threshold_bernsen.md b/Resources/Documentation/Gem/pix_threshold_bernsen.md index e934f3d2f4..1f0bd0c8f1 100644 --- a/Resources/Documentation/Gem/pix_threshold_bernsen.md +++ b/Resources/Documentation/Gem/pix_threshold_bernsen.md @@ -3,7 +3,7 @@ title: pix_threshold_bernsen description: apply dynamic thresholds to pixels for binarization categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: number of tiles in x-direction diff --git a/Resources/Documentation/Gem/pix_video.md b/Resources/Documentation/Gem/pix_video.md index 821be3b457..239270f600 100644 --- a/Resources/Documentation/Gem/pix_video.md +++ b/Resources/Documentation/Gem/pix_video.md @@ -3,7 +3,7 @@ title: pix_video description: open a camera and get input categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - name: colorspace diff --git a/Resources/Documentation/Gem/pix_write.md b/Resources/Documentation/Gem/pix_write.md index 9363eb288f..91dd70029b 100644 --- a/Resources/Documentation/Gem/pix_write.md +++ b/Resources/Documentation/Gem/pix_write.md @@ -3,7 +3,7 @@ title: pix_write description: Make a snapshot of the frame-buffer and write it to a file categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - name: file diff --git a/Resources/Documentation/Gem/pix_writer.md b/Resources/Documentation/Gem/pix_writer.md index bd02609376..163935c4fc 100644 --- a/Resources/Documentation/Gem/pix_writer.md +++ b/Resources/Documentation/Gem/pix_writer.md @@ -3,7 +3,7 @@ title: pix_writer description: write the current texture to a file categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - name: file diff --git a/Resources/Documentation/Gem/pix_yuv.md b/Resources/Documentation/Gem/pix_yuv.md index 8050da118b..8d909a43ff 100644 --- a/Resources/Documentation/Gem/pix_yuv.md +++ b/Resources/Documentation/Gem/pix_yuv.md @@ -3,7 +3,7 @@ title: pix_yuv description: convert the colorspace of an image to YUV categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pix_zoom.md b/Resources/Documentation/Gem/pix_zoom.md index 129116a97a..7bd9d471e7 100644 --- a/Resources/Documentation/Gem/pix_zoom.md +++ b/Resources/Documentation/Gem/pix_zoom.md @@ -3,7 +3,7 @@ title: pix_zoom description: zooms in on an image categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: diff --git a/Resources/Documentation/Gem/polygon.md b/Resources/Documentation/Gem/polygon.md index d97871487e..b1ce448604 100644 --- a/Resources/Documentation/Gem/polygon.md +++ b/Resources/Documentation/Gem/polygon.md @@ -3,7 +3,7 @@ title: polygon description: Renders a polygon. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/polygon_smooth.md b/Resources/Documentation/Gem/polygon_smooth.md index 100f34d0fb..93bd5c29c7 100644 --- a/Resources/Documentation/Gem/polygon_smooth.md +++ b/Resources/Documentation/Gem/polygon_smooth.md @@ -3,7 +3,7 @@ title: polygon_smooth description: Turn on/off polygon smoothing (antialiasing). categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/pqtorusknots.md b/Resources/Documentation/Gem/pqtorusknots.md index 5b054f8e6a..1eb5c9b778 100644 --- a/Resources/Documentation/Gem/pqtorusknots.md +++ b/Resources/Documentation/Gem/pqtorusknots.md @@ -3,7 +3,7 @@ title: pqtorusknot description: renders a 3D knot categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: gemlist description: diff --git a/Resources/Documentation/Gem/primTri.md b/Resources/Documentation/Gem/primTri.md index 0272ab9cbb..ceb10f2a1e 100644 --- a/Resources/Documentation/Gem/primTri.md +++ b/Resources/Documentation/Gem/primTri.md @@ -3,7 +3,7 @@ title: primTri description: renders a triangle with gradient colors categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics methods: - type: symbol description: draw [line|fill|point] diff --git a/Resources/Documentation/Gem/rectangle.md b/Resources/Documentation/Gem/rectangle.md index defa06ba33..7454fa7134 100644 --- a/Resources/Documentation/Gem/rectangle.md +++ b/Resources/Documentation/Gem/rectangle.md @@ -3,7 +3,7 @@ title: rectangle description: renders a rectangle categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: draw [line|fill|point] diff --git a/Resources/Documentation/Gem/render_trigger.md b/Resources/Documentation/Gem/render_trigger.md index bf7c9fa140..198770b3d4 100644 --- a/Resources/Documentation/Gem/render_trigger.md +++ b/Resources/Documentation/Gem/render_trigger.md @@ -3,7 +3,7 @@ title: render_trigger description: triggers on rendering categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/rgb2hsv.md b/Resources/Documentation/Gem/rgb2hsv.md index 073d2298da..2231774ae0 100644 --- a/Resources/Documentation/Gem/rgb2hsv.md +++ b/Resources/Documentation/Gem/rgb2hsv.md @@ -3,7 +3,7 @@ title: rgb2hsv description: convert RGB color values to HSV categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: list diff --git a/Resources/Documentation/Gem/rgb2yuv.md b/Resources/Documentation/Gem/rgb2yuv.md index a600d72a1b..f1733c64ac 100644 --- a/Resources/Documentation/Gem/rgb2yuv.md +++ b/Resources/Documentation/Gem/rgb2yuv.md @@ -3,7 +3,7 @@ title: rgb2yuv description: convert RGB color values to YUV categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: list diff --git a/Resources/Documentation/Gem/ripple.md b/Resources/Documentation/Gem/ripple.md index d5a371086a..6459dbbb69 100644 --- a/Resources/Documentation/Gem/ripple.md +++ b/Resources/Documentation/Gem/ripple.md @@ -3,7 +3,7 @@ title: ripple description: renders and distorts a square categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: message description: draw [line|fill|point] diff --git a/Resources/Documentation/Gem/rotate.md b/Resources/Documentation/Gem/rotate.md index 05f7b4831a..f8230645eb 100644 --- a/Resources/Documentation/Gem/rotate.md +++ b/Resources/Documentation/Gem/rotate.md @@ -3,7 +3,7 @@ title: rotate description: rotation categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: rotation amount around axis (in deg) diff --git a/Resources/Documentation/Gem/rotateXYZ.md b/Resources/Documentation/Gem/rotateXYZ.md index e2079eb411..06bd81fc89 100644 --- a/Resources/Documentation/Gem/rotateXYZ.md +++ b/Resources/Documentation/Gem/rotateXYZ.md @@ -3,7 +3,7 @@ title: rotateXYZ description: rotates a gemList by the specified rotation around the X-, Y-, and Z-axes categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: rotation amount around X-axis (in deg) diff --git a/Resources/Documentation/Gem/rubber.md b/Resources/Documentation/Gem/rubber.md index 3291ea9470..e1ba1ee889 100644 --- a/Resources/Documentation/Gem/rubber.md +++ b/Resources/Documentation/Gem/rubber.md @@ -3,7 +3,7 @@ title: rubber description: renders and distorts a square categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: bang description: grab/release diff --git a/Resources/Documentation/Gem/scale.md b/Resources/Documentation/Gem/scale.md index 51b250b28c..a03d93af52 100644 --- a/Resources/Documentation/Gem/scale.md +++ b/Resources/Documentation/Gem/scale.md @@ -3,7 +3,7 @@ title: scale description: scales a gemList with specified scaling factors categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: initial scale amount diff --git a/Resources/Documentation/Gem/scaleXYZ.md b/Resources/Documentation/Gem/scaleXYZ.md index dabae7c74c..6fcae6b269 100644 --- a/Resources/Documentation/Gem/scaleXYZ.md +++ b/Resources/Documentation/Gem/scaleXYZ.md @@ -3,7 +3,7 @@ title: scaleXYZ description: scales a gemList along the X-, Y-, and Z-axes categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: scaling amount along the X-axis diff --git a/Resources/Documentation/Gem/scopeXYZ~.md b/Resources/Documentation/Gem/scopeXYZ~.md index dfed7e6b99..1732251bf8 100644 --- a/Resources/Documentation/Gem/scopeXYZ~.md +++ b/Resources/Documentation/Gem/scopeXYZ~.md @@ -3,7 +3,7 @@ title: scopeXYZ~ description: 3D oscilloscope categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: number of signal points to be stored diff --git a/Resources/Documentation/Gem/separator.md b/Resources/Documentation/Gem/separator.md index b6b641d3d1..2ac3ad26f5 100644 --- a/Resources/Documentation/Gem/separator.md +++ b/Resources/Documentation/Gem/separator.md @@ -3,7 +3,7 @@ title: separator description: separate render chains categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: list of matrices to work on diff --git a/Resources/Documentation/Gem/shearXY.md b/Resources/Documentation/Gem/shearXY.md index 05fe49639f..2823c9476e 100644 --- a/Resources/Documentation/Gem/shearXY.md +++ b/Resources/Documentation/Gem/shearXY.md @@ -3,7 +3,7 @@ title: shearXY description: shear. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: shear factor (XY) diff --git a/Resources/Documentation/Gem/shearXZ.md b/Resources/Documentation/Gem/shearXZ.md index 4e64e5507d..5ff2e09410 100644 --- a/Resources/Documentation/Gem/shearXZ.md +++ b/Resources/Documentation/Gem/shearXZ.md @@ -3,7 +3,7 @@ title: shearXZ description: shear. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: shear factor (XZ) diff --git a/Resources/Documentation/Gem/shearYX.md b/Resources/Documentation/Gem/shearYX.md index 2d288b8b38..960bd2587d 100644 --- a/Resources/Documentation/Gem/shearYX.md +++ b/Resources/Documentation/Gem/shearYX.md @@ -3,7 +3,7 @@ title: shearYX description: shear. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: shear factor (YX) diff --git a/Resources/Documentation/Gem/shearYZ.md b/Resources/Documentation/Gem/shearYZ.md index 79777eadbc..5547ec17c0 100644 --- a/Resources/Documentation/Gem/shearYZ.md +++ b/Resources/Documentation/Gem/shearYZ.md @@ -3,7 +3,7 @@ title: shearYZ description: shear. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: shear factor (YZ) diff --git a/Resources/Documentation/Gem/shearZX.md b/Resources/Documentation/Gem/shearZX.md index dd94dd9e40..3a2c50f318 100644 --- a/Resources/Documentation/Gem/shearZX.md +++ b/Resources/Documentation/Gem/shearZX.md @@ -3,7 +3,7 @@ title: shearZX description: shear. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: shear factor (ZX) diff --git a/Resources/Documentation/Gem/shearZY.md b/Resources/Documentation/Gem/shearZY.md index 6eb3063664..f841cdfd27 100644 --- a/Resources/Documentation/Gem/shearZY.md +++ b/Resources/Documentation/Gem/shearZY.md @@ -3,7 +3,7 @@ title: shearZY description: shear. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: shear factor (ZY) diff --git a/Resources/Documentation/Gem/shininess.md b/Resources/Documentation/Gem/shininess.md index 93583cbc0f..bc4a3dfaaa 100644 --- a/Resources/Documentation/Gem/shininess.md +++ b/Resources/Documentation/Gem/shininess.md @@ -3,7 +3,7 @@ title: shininess description: aets the shininess of the material categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Shininess value (0 - 128) diff --git a/Resources/Documentation/Gem/slideSquares.md b/Resources/Documentation/Gem/slideSquares.md index 50ab138644..2dbfebf354 100644 --- a/Resources/Documentation/Gem/slideSquares.md +++ b/Resources/Documentation/Gem/slideSquares.md @@ -3,7 +3,7 @@ title: slideSquares description: renders sliding rectangles categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: draw [line|fill|point] diff --git a/Resources/Documentation/Gem/specular.md b/Resources/Documentation/Gem/specular.md index ac6dce8906..d32b99b6a0 100644 --- a/Resources/Documentation/Gem/specular.md +++ b/Resources/Documentation/Gem/specular.md @@ -3,7 +3,7 @@ title: specular description: specular coloring categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: a list of 3 (RGB) or 4 (RGBA) float-values diff --git a/Resources/Documentation/Gem/specularRGB.md b/Resources/Documentation/Gem/specularRGB.md index ef776fd51c..b8cefc347c 100644 --- a/Resources/Documentation/Gem/specularRGB.md +++ b/Resources/Documentation/Gem/specularRGB.md @@ -3,7 +3,7 @@ title: specularRGB description: specular coloring with individual RGB values categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: a list of 3 (RGB) or 4 (RGBA) float-values diff --git a/Resources/Documentation/Gem/sphere.md b/Resources/Documentation/Gem/sphere.md index e151afee6b..05c6b5e309 100644 --- a/Resources/Documentation/Gem/sphere.md +++ b/Resources/Documentation/Gem/sphere.md @@ -3,7 +3,7 @@ title: sphere description: renders a segmented sphere. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: draw [line|fill|point] diff --git a/Resources/Documentation/Gem/sphere3d.md b/Resources/Documentation/Gem/sphere3d.md index e17e90df2c..87610eab27 100644 --- a/Resources/Documentation/Gem/sphere3d.md +++ b/Resources/Documentation/Gem/sphere3d.md @@ -3,7 +3,7 @@ title: sphere3d description: renders a segmented sphere3d. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: draw [line|fill|point] diff --git a/Resources/Documentation/Gem/spline_path.md b/Resources/Documentation/Gem/spline_path.md index c6af8e7fb0..34ff0ad771 100644 --- a/Resources/Documentation/Gem/spline_path.md +++ b/Resources/Documentation/Gem/spline_path.md @@ -3,7 +3,7 @@ title: spline_path description: reads out a table categories: - object -pdcategory: Graphics, Data Structures +pdcategory: Gem, Graphics, Data Structures arguments: - type: float description: reading point (0.0 to 1.0) diff --git a/Resources/Documentation/Gem/spot_light.md b/Resources/Documentation/Gem/spot_light.md index ccc30df026..c2f4bf28a4 100644 --- a/Resources/Documentation/Gem/spot_light.md +++ b/Resources/Documentation/Gem/spot_light.md @@ -3,7 +3,7 @@ title: spot_light description: Adds a spot-light to the scene categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: Turn light on/off diff --git a/Resources/Documentation/Gem/square.md b/Resources/Documentation/Gem/square.md index 63a80648dc..9ebe56eda6 100644 --- a/Resources/Documentation/Gem/square.md +++ b/Resources/Documentation/Gem/square.md @@ -3,7 +3,7 @@ title: square description: renders a square. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: draw [line|fill|point] diff --git a/Resources/Documentation/Gem/surface3d.md b/Resources/Documentation/Gem/surface3d.md index 628ef3f10f..6fc2c611e4 100644 --- a/Resources/Documentation/Gem/surface3d.md +++ b/Resources/Documentation/Gem/surface3d.md @@ -3,7 +3,7 @@ title: surface3d description: Renders a 3d bicubic curve. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: list description: size of the control matrix diff --git a/Resources/Documentation/Gem/teapot.md b/Resources/Documentation/Gem/teapot.md index 4c26ecf522..00072521d2 100644 --- a/Resources/Documentation/Gem/teapot.md +++ b/Resources/Documentation/Gem/teapot.md @@ -6,7 +6,7 @@ description: renders a teapot categories: - graphics -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float diff --git a/Resources/Documentation/Gem/text2d.md b/Resources/Documentation/Gem/text2d.md index 7a3e684041..f1735b0b1c 100644 --- a/Resources/Documentation/Gem/text2d.md +++ b/Resources/Documentation/Gem/text2d.md @@ -3,7 +3,7 @@ title: text2d description: renders a line of text without 3D transformations. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: description: initial text (defaults to "gem") diff --git a/Resources/Documentation/Gem/text3d.md b/Resources/Documentation/Gem/text3d.md index 5d12c4918e..4c8de9c2ea 100644 --- a/Resources/Documentation/Gem/text3d.md +++ b/Resources/Documentation/Gem/text3d.md @@ -3,7 +3,7 @@ title: text3d description: renders a line of text with 3D transformations. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: description: initial text (defaults to "gem") diff --git a/Resources/Documentation/Gem/textoutline.md b/Resources/Documentation/Gem/textoutline.md index 83a3cb84a0..6ccc0ee17d 100644 --- a/Resources/Documentation/Gem/textoutline.md +++ b/Resources/Documentation/Gem/textoutline.md @@ -3,7 +3,7 @@ title: textoutline description: renders a line of outlined text with 3D transformations categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: description: initial text diff --git a/Resources/Documentation/Gem/torus.md b/Resources/Documentation/Gem/torus.md index 927b3a0d16..c1847829b4 100644 --- a/Resources/Documentation/Gem/torus.md +++ b/Resources/Documentation/Gem/torus.md @@ -6,7 +6,7 @@ description: renders a torus categories: - graphics -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float diff --git a/Resources/Documentation/Gem/translate.md b/Resources/Documentation/Gem/translate.md index 2f58a21be3..e416e38806 100644 --- a/Resources/Documentation/Gem/translate.md +++ b/Resources/Documentation/Gem/translate.md @@ -3,7 +3,7 @@ title: translate description: graphics translation categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: 1st argument: translation scale diff --git a/Resources/Documentation/Gem/translateXYZ.md b/Resources/Documentation/Gem/translateXYZ.md index e1904e6903..17204d0e42 100644 --- a/Resources/Documentation/Gem/translateXYZ.md +++ b/Resources/Documentation/Gem/translateXYZ.md @@ -3,7 +3,7 @@ title: translateXYZ description: graphics translation categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: translation along X-axis diff --git a/Resources/Documentation/Gem/trapezoid.md b/Resources/Documentation/Gem/trapezoid.md index e0be754407..963680c548 100644 --- a/Resources/Documentation/Gem/trapezoid.md +++ b/Resources/Documentation/Gem/trapezoid.md @@ -3,7 +3,7 @@ title: trapezoid description: renders a trapezoid box categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: dimensions of the trapezoid (size topline) diff --git a/Resources/Documentation/Gem/triangle.md b/Resources/Documentation/Gem/triangle.md index 5eb9cc1679..c50bd32043 100644 --- a/Resources/Documentation/Gem/triangle.md +++ b/Resources/Documentation/Gem/triangle.md @@ -3,7 +3,7 @@ title: triangle description: renders an isosceles triangle. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: size of the triangle diff --git a/Resources/Documentation/Gem/vertex_add.md b/Resources/Documentation/Gem/vertex_add.md index f222c78237..daf4883916 100644 --- a/Resources/Documentation/Gem/vertex_add.md +++ b/Resources/Documentation/Gem/vertex_add.md @@ -4,7 +4,7 @@ title: vertex_add description: add vertices categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: v, c, t, or n diff --git a/Resources/Documentation/Gem/vertex_combine.md b/Resources/Documentation/Gem/vertex_combine.md index eb6751e7cb..8d17e8c4ab 100644 --- a/Resources/Documentation/Gem/vertex_combine.md +++ b/Resources/Documentation/Gem/vertex_combine.md @@ -4,7 +4,7 @@ title: vertex_combine description: combine two matrices categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: inlets: 1st: diff --git a/Resources/Documentation/Gem/vertex_draw.md b/Resources/Documentation/Gem/vertex_draw.md index 134665de7e..2fd854e21b 100644 --- a/Resources/Documentation/Gem/vertex_draw.md +++ b/Resources/Documentation/Gem/vertex_draw.md @@ -4,7 +4,7 @@ title: vertex_draw description: draw a vertex array categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: inlets: 1st: diff --git a/Resources/Documentation/Gem/vertex_grid.md b/Resources/Documentation/Gem/vertex_grid.md index 421091d7b1..38332bb5b7 100644 --- a/Resources/Documentation/Gem/vertex_grid.md +++ b/Resources/Documentation/Gem/vertex_grid.md @@ -3,7 +3,7 @@ title: vertex_grid description: create a grid of vertices categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: x diff --git a/Resources/Documentation/Gem/vertex_mul.md b/Resources/Documentation/Gem/vertex_mul.md index 7b7a696ee7..5e270e684f 100644 --- a/Resources/Documentation/Gem/vertex_mul.md +++ b/Resources/Documentation/Gem/vertex_mul.md @@ -4,7 +4,7 @@ title: vertex_mul description: multiply vertices categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: v, c, t, or n diff --git a/Resources/Documentation/Gem/vertex_offset.md b/Resources/Documentation/Gem/vertex_offset.md index 1ab3a1a0e9..73a4da4d8d 100644 --- a/Resources/Documentation/Gem/vertex_offset.md +++ b/Resources/Documentation/Gem/vertex_offset.md @@ -3,7 +3,7 @@ title: vertex_offset description: offset vertices categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: v, c, t, or n diff --git a/Resources/Documentation/Gem/vertex_program.md b/Resources/Documentation/Gem/vertex_program.md index 8fe1f52cd1..903bb0cc53 100644 --- a/Resources/Documentation/Gem/vertex_program.md +++ b/Resources/Documentation/Gem/vertex_program.md @@ -3,7 +3,7 @@ title: vertex_program description: loads and applies an ARB (or NV) vertex shader categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: gemlist diff --git a/Resources/Documentation/Gem/vertex_quad.md b/Resources/Documentation/Gem/vertex_quad.md index f0685dded3..ac51e4ab0b 100644 --- a/Resources/Documentation/Gem/vertex_quad.md +++ b/Resources/Documentation/Gem/vertex_quad.md @@ -4,7 +4,7 @@ title: vertex_quad description: draw a quad with texture coordinates categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: inlets: 1st: diff --git a/Resources/Documentation/Gem/vertex_scale.md b/Resources/Documentation/Gem/vertex_scale.md index 8cc827d194..af40836501 100644 --- a/Resources/Documentation/Gem/vertex_scale.md +++ b/Resources/Documentation/Gem/vertex_scale.md @@ -3,7 +3,7 @@ title: vertex_scale description: scale vertices categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: v, c, t, or n diff --git a/Resources/Documentation/Gem/vertex_set.md b/Resources/Documentation/Gem/vertex_set.md index 5ac4fc7198..833277eec8 100644 --- a/Resources/Documentation/Gem/vertex_set.md +++ b/Resources/Documentation/Gem/vertex_set.md @@ -3,7 +3,7 @@ title: vertex_set description: set vertices categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: symbol description: v, c, t, or n diff --git a/Resources/Documentation/Gem/vertex_tabread.md b/Resources/Documentation/Gem/vertex_tabread.md index 53f1395967..e11c5924fe 100644 --- a/Resources/Documentation/Gem/vertex_tabread.md +++ b/Resources/Documentation/Gem/vertex_tabread.md @@ -3,7 +3,7 @@ title: vertex_tabread description: read from tables to set vertices categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: table description: table V diff --git a/Resources/Documentation/Gem/world_light.md b/Resources/Documentation/Gem/world_light.md index 25ea791bfb..19f07dda96 100644 --- a/Resources/Documentation/Gem/world_light.md +++ b/Resources/Documentation/Gem/world_light.md @@ -3,7 +3,7 @@ title: world_light description: adds a point-light to the scene. categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics arguments: - type: float description: turn light on/off (default: 1) diff --git a/Resources/Documentation/Gem/yuv2rgb.md b/Resources/Documentation/Gem/yuv2rgb.md index 5915956aeb..4b03e1ec6c 100644 --- a/Resources/Documentation/Gem/yuv2rgb.md +++ b/Resources/Documentation/Gem/yuv2rgb.md @@ -3,7 +3,7 @@ title: yuv2rgb description: convert YUV color values to RGB categories: - object -pdcategory: Graphics +pdcategory: Gem, Graphics inlets: 1st: - type: list diff --git a/Source/Dialogs/ObjectBrowserDialog.h b/Source/Dialogs/ObjectBrowserDialog.h index a135ba359a..e69845c3fe 100644 --- a/Source/Dialogs/ObjectBrowserDialog.h +++ b/Source/Dialogs/ObjectBrowserDialog.h @@ -237,9 +237,27 @@ class ObjectsListBox : public ListBox return itemComponent; } } + + void removeAliasedDuplicates(StringArray& objectsToShow) + { + // TODO: this is inefficient! + StringArray gemObjects; + for(auto& object : objectsToShow) + { + if(object.startsWith("Gem")) + { + gemObjects.add(object.fromLastOccurrenceOf("/", false, false)); + } + } + for(auto& gemObject : gemObjects) + { + objectsToShow.removeString(gemObject); + } + } void showObjects(StringArray objectsToShow) { + removeAliasedDuplicates(objectsToShow); objects = std::move(objectsToShow); updateContent(); repaint(); diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 56ced6f47b..e3cbb8d9ef 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -232,7 +232,7 @@ int createGemWindow(WindowInfo& info, WindowHints& hints) gemJUCEWindow[window->instance].reset(window); info.window[window->instance] = window; - #if JUCE_LINUX + #if JUCE_LINUX || JUCE_WINDOWS // Make sure only audio thread has the context set as active window->openGLContext.executeOnGLThread([](OpenGLContext& context){ // We get unpredictable behaviour if the context is active on multiple threads diff --git a/Source/Pd/Library.h b/Source/Pd/Library.h index ed0fa117aa..e7a42e2e38 100644 --- a/Source/Pd/Library.h +++ b/Source/Pd/Library.h @@ -55,7 +55,7 @@ class Library : public FileSystemWatcher::Listener { ProjectInfo::appDataDir.getChildFile("Extra") }; - static inline StringArray objectOrigins = { "vanilla", "ELSE", "cyclone", "heavylib", "pdlua" }; + static inline StringArray objectOrigins = { "vanilla", "ELSE", "cyclone", "Gem", "heavylib", "pdlua"}; private: StringArray allObjects; From c055b9b9be60927822e23626b7b4b7f5bbd57ed3 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Jan 2024 18:00:31 +0100 Subject: [PATCH 0121/1030] Added missing file --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 82abda8fb3..c19bf7187e 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 82abda8fb34ea6174fd20104326c2a5c99726106 +Subproject commit c19bf7187e7a4c88ef001a6a9f6dd9f7fb2c8d78 From 66a10c4db7db37a9abdf7fb0e0a2f37278e30967 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Jan 2024 18:23:55 +0100 Subject: [PATCH 0122/1030] Github action fix --- .github/workflows/cmake.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index cd224fb164..d35855d7c8 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -46,10 +46,6 @@ jobs: p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }} p12-password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }} - - name: Install Gem plugin dependencies - working-directory: ${{github.workspace}}/build - run: cmake --build . --config $BUILD_TYPE - - name: Build working-directory: ${{github.workspace}}/build run: cmake --build . --config $BUILD_TYPE From 7eb242ccf861238305e58ed9e2d2f06889096eb4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Jan 2024 18:34:11 +0100 Subject: [PATCH 0123/1030] Revert change for Windows --- Source/Objects/Gem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index e3cbb8d9ef..56ced6f47b 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -232,7 +232,7 @@ int createGemWindow(WindowInfo& info, WindowHints& hints) gemJUCEWindow[window->instance].reset(window); info.window[window->instance] = window; - #if JUCE_LINUX || JUCE_WINDOWS + #if JUCE_LINUX // Make sure only audio thread has the context set as active window->openGLContext.executeOnGLThread([](OpenGLContext& context){ // We get unpredictable behaviour if the context is active on multiple threads From 9717e54549c7be87bd57855ef4a59ad571b15c81 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Jan 2024 20:14:43 +0100 Subject: [PATCH 0124/1030] Compilation fix --- Libraries/pure-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/pure-data b/Libraries/pure-data index b41e97c299..2cef7bedac 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit b41e97c29903788034e65538b828e5d3d090a9cb +Subproject commit 2cef7bedac212804beb1fe4fe8eb9be40f5f4a0e From cee241549e98b7d5ac8d1976177250aeef3b3885 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 13:13:05 +0100 Subject: [PATCH 0125/1030] Optimisations for labels: only recalculate text width if really needed --- Source/Objects/AtomHelper.h | 16 ++++++++++++++-- Source/Objects/IEMHelper.h | 28 +++++++++++++++++++++++----- Source/Utility/StringUtils.h | 15 +++++++++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/Source/Objects/AtomHelper.h b/Source/Objects/AtomHelper.h index 396b8437be..e68713a8ff 100644 --- a/Source/Objects/AtomHelper.h +++ b/Source/Objects/AtomHelper.h @@ -28,6 +28,10 @@ class AtomHelper { PluginProcessor* pd; pd::WeakReference ptr; + + int lastFontHeight = 10; + hash32 lastLabelTextHash = 0; + int lastLabelLength = 0; public: Value labelColour = SynchronousValue(); @@ -321,8 +325,16 @@ class AtomHelper { auto objectBounds = object->getBounds().reduced(Object::margin); int fontHeight = getAtomHeight() - 6; - int labelLength = Font(fontHeight).getStringWidth(getExpandedLabelText()); - + auto currentHash = hash(getExpandedLabelText()); + int labelLength = lastLabelLength; + if(lastFontHeight != fontHeight || lastLabelTextHash != currentHash) + { + labelLength = Font(fontHeight).getStringWidth(getExpandedLabelText()); + lastFontHeight = fontHeight; + lastLabelTextHash = currentHash; + lastLabelLength = labelLength; + } + int labelPosition = 0; if (auto atom = ptr.get()) { labelPosition = atom->a_wherelabel; diff --git a/Source/Objects/IEMHelper.h b/Source/Objects/IEMHelper.h index f6ad2dd55a..74fa5890b4 100644 --- a/Source/Objects/IEMHelper.h +++ b/Source/Objects/IEMHelper.h @@ -143,8 +143,14 @@ class IEMHelper { setColour(primaryColour, atoms[1]); if (numAtoms > 2) setColour(labelColour, atoms[2]); + + if(auto* label = gui->getLabel()) + { + label->setColour(Label::textColourId, getLabelColour()); + } + gui->repaint(); - gui->updateLabel(); + return true; } case hash("label"): { @@ -299,8 +305,8 @@ class IEMHelper { label.reset(nullptr); } } - - Rectangle getLabelBounds() const + + Rectangle getLabelBounds() { auto objectBounds = object->getBounds().reduced(Object::margin); @@ -308,8 +314,16 @@ class IEMHelper { t_symbol const* sym = canvas_realizedollar(iemgui->x_glist, iemgui->x_lab); if (sym) { int fontHeight = getFontHeight(); - int labelLength = Font(fontHeight).getStringWidth(getExpandedLabelText()); - + auto currentHash = hash(getExpandedLabelText()); + int labelLength = lastLabelLength; + if(lastFontHeight != fontHeight || lastLabelTextHash != currentHash) + { + labelLength = Font(fontHeight).getStringWidth(getExpandedLabelText()); + lastFontHeight = fontHeight; + lastLabelTextHash = currentHash; + lastLabelLength = labelLength; + } + int const posx = objectBounds.getX() + iemgui->x_ldx + 4; int const posy = objectBounds.getY() + iemgui->x_ldy; @@ -521,6 +535,10 @@ class IEMHelper { PluginProcessor* pd; pd::WeakReference ptr; + + int lastFontHeight = 10; + hash32 lastLabelTextHash = 0; + int lastLabelLength = 0; Value primaryColour = SynchronousValue(); Value secondaryColour = SynchronousValue(); diff --git a/Source/Utility/StringUtils.h b/Source/Utility/StringUtils.h index 3f7c7ba15c..b1f9034981 100644 --- a/Source/Utility/StringUtils.h +++ b/Source/Utility/StringUtils.h @@ -82,5 +82,20 @@ struct CachedStringWidth { return maximumLineWidth; } + // Hack so you can use variables instead of only constants + template + static int variableCachedStringWidth(int size, const String& string) { + + + if constexpr(I > 64) return 0; + + if(size == I) { + return CachedStringWidth::calculateStringWidth(string); + } + else { + return variableCachedStringWidth(size, string); // Recursive call + } + } + static inline std::unordered_map stringWidthCache = std::unordered_map(); }; From a6a8b35c08cf0110ff840b5d22403e8531d9444b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 13:56:57 +0100 Subject: [PATCH 0126/1030] Don't log Gem version if Gem is not enabled --- Source/PluginProcessor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 85bea35400..b10d8faee9 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -123,7 +123,9 @@ PluginProcessor::PluginProcessor() logMessage("Libraries:"); logMessage(else_version); logMessage(cyclone_version); +#if ENABLE_GEM logMessage(gem_version); +#endif logMessage(heavylib_version); From 7a76dd3a672bc280236e05987b17153fa5a471d9 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 14:26:58 +0100 Subject: [PATCH 0127/1030] Compilation fix --- Source/Objects/AtomHelper.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Objects/AtomHelper.h b/Source/Objects/AtomHelper.h index e68713a8ff..ac308b72f0 100644 --- a/Source/Objects/AtomHelper.h +++ b/Source/Objects/AtomHelper.h @@ -320,7 +320,7 @@ class AtomHelper { } } - Rectangle getLabelBounds() const + Rectangle getLabelBounds() { auto objectBounds = object->getBounds().reduced(Object::margin); int fontHeight = getAtomHeight() - 6; From 19db3c849e4af70d4d2436f0db79a3a14665ced7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 14:36:12 +0100 Subject: [PATCH 0128/1030] Separate colour in suggestions for Gem objects --- Source/Components/SuggestionComponent.h | 47 ++++++++++++++++++++----- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index 95ab553a51..6a2a287186 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -9,6 +9,11 @@ #include "Objects/ObjectBase.h" #include "Heavy/CompatibleObjects.h" +extern "C" +{ +int is_gem_object(const char* sym); +} + // Component that sits on top of a TextEditor and will draw auto-complete suggestions over it class AutoCompleteComponent : public Component @@ -136,7 +141,15 @@ class SuggestionComponent : public Component , public ComponentListener { class Suggestion : public TextButton { - int type = -1; + + enum ObjectType + { + Data = 0, + Signal = 1, + Gem = 2 + }; + + ObjectType type; String objectDescription; @@ -156,7 +169,12 @@ class SuggestionComponent : public Component { objectDescription = description; setButtonText(name); - type = name.contains("~") ? 1 : 0; + type = name.contains("~") ? Signal : Data; + + if(!type && is_gem_object(name.toRawUTF8())) + { + type = Gem; + } // Argument suggestions don't have icons! drawIcon = icon; @@ -201,19 +219,30 @@ class SuggestionComponent : public Component Fonts::drawText(g, String::fromUTF8(" \xe2\x80\x93 ") + objectDescription, Rectangle(leftIndent, yIndent, textWidth, getHeight() - yIndent * 2), colour, 13); } - if (type == -1) - return; - if (drawIcon) { - auto dataColour = findColour(PlugDataColour::dataColourId); - auto signalColour = findColour(PlugDataColour::signalColourId); - g.setColour(type ? signalColour : dataColour); + Colour iconColour; + String iconText; + if(type == Data) { + iconColour = findColour(PlugDataColour::dataColourId); + iconText = "pd"; + } + else if(type == Signal) { + iconColour = findColour(PlugDataColour::signalColourId); + iconText = "~"; + } + else if (type == Gem) { + iconColour = findColour(PlugDataColour::gemColourId); + iconText = "g"; + } + g.setColour(iconColour); + auto iconbound = getLocalBounds().reduced(4); iconbound.setWidth(getHeight() - 8); iconbound.translate(4, 0); PlugDataLook::fillSmoothedRectangle(g, iconbound.toFloat(), Corners::defaultCornerRadius); - Fonts::drawFittedText(g, type ? "~" : "pd", iconbound.reduced(1), Colours::white, 1, 1.0f, type ? 12 : 10, Justification::centred); + + Fonts::drawFittedText(g, iconText, iconbound.reduced(1), Colours::white, 1, 1.0f, type ? 12 : 10, Justification::centred); } } From c17af995c1339348a550fd806e41659f450a0184 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 15:08:42 +0100 Subject: [PATCH 0129/1030] Fixed compilation --- Libraries/pure-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/pure-data b/Libraries/pure-data index 2cef7bedac..1bf6e97fb7 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit 2cef7bedac212804beb1fe4fe8eb9be40f5f4a0e +Subproject commit 1bf6e97fb7e4a0f7c157b08274c2a8c3dea6bb7b From 06fd81fcfb0927ad792b55a02863bd838a2df2e2 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 15:11:28 +0100 Subject: [PATCH 0130/1030] Removed unnecessary hack --- Source/Pd/MessageListener.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Source/Pd/MessageListener.h b/Source/Pd/MessageListener.h index 48e6fc26c9..81a123432f 100644 --- a/Source/Pd/MessageListener.h +++ b/Source/Pd/MessageListener.h @@ -6,13 +6,7 @@ #pragma once #include "Instance.h" - -// These is an assertion inside readerwriterqueue that doesn't apply to us -// (it doesn't like it when we enqueue from two differen threads, but there is always only 1 thread that has exclusive action to enqueue, so it should be fine -// we set the NDEBUG flag to silence it -#define NDEBUG #include -#undef NDEBUG namespace pd { From 645d10161f04c081f78c33aee344249e84e7528d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 15:17:45 +0100 Subject: [PATCH 0131/1030] Gem: enable sig2pix~ and pix2sig~ --- Libraries/Gem | 2 +- Source/Pd/Setup.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index c19bf7187e..e5f2a27e0d 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit c19bf7187e7a4c88ef001a6a9f6dd9f7fb2c8d78 +Subproject commit e5f2a27e0d7dc907e2450cfce0fadcc517120812 diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index 82acff1256..0ddd60f55c 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -369,6 +369,8 @@ void pix_share_write_setup(); void pix_snap_setup(); void pix_snap2tex_setup(); void pix_subtract_setup(); +void pix_sig2pix_tilde_setup(); +void pix_pix2sig_tilde_setup(); void pix_tIIR_setup(); void pix_tIIRf_setup(); void pix_takealpha_setup(); @@ -1897,6 +1899,8 @@ void Setup::initialiseGem(std::string gemPluginPath) pix_snap_setup(); pix_snap2tex_setup(); pix_subtract_setup(); + pix_sig2pix_tilde_setup(); + pix_pix2sig_tilde_setup(); pix_tIIR_setup(); pix_tIIRf_setup(); pix_takealpha_setup(); From c84f59488b5b0f6a91abd9bfebcba8727b29188a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 15:21:47 +0100 Subject: [PATCH 0132/1030] Fix for last commit --- Source/Pd/Setup.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index 0ddd60f55c..64787af295 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -369,8 +369,8 @@ void pix_share_write_setup(); void pix_snap_setup(); void pix_snap2tex_setup(); void pix_subtract_setup(); -void pix_sig2pix_tilde_setup(); -void pix_pix2sig_tilde_setup(); +void pix_sig2pix_setup(); +void pix_pix2sig_setup(); void pix_tIIR_setup(); void pix_tIIRf_setup(); void pix_takealpha_setup(); @@ -1899,8 +1899,8 @@ void Setup::initialiseGem(std::string gemPluginPath) pix_snap_setup(); pix_snap2tex_setup(); pix_subtract_setup(); - pix_sig2pix_tilde_setup(); - pix_pix2sig_tilde_setup(); + pix_sig2pix_setup(); + pix_pix2sig_setup(); pix_tIIR_setup(); pix_tIIRf_setup(); pix_takealpha_setup(); From 9e477b6970658f458c41c87832bb9da485a891e1 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 15:23:19 +0100 Subject: [PATCH 0133/1030] Fix for sig2pix and pix2sig --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index e5f2a27e0d..b6c4db894e 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit e5f2a27e0d7dc907e2450cfce0fadcc517120812 +Subproject commit b6c4db894e413e0863164c64e2d7ad5e3fdc345a From fbdc295b6fbb2bd003849c7c17bf07ed37e83888 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 15:45:07 +0100 Subject: [PATCH 0134/1030] Improved okay/cancel dialog --- Source/Dialogs/Dialogs.cpp | 45 ++++++++++++++++++++------------------ Source/Dialogs/Dialogs.h | 2 +- Source/Utility/Autosave.h | 4 ++-- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index 30d4e6a424..c6d9979849 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -190,22 +190,24 @@ void Dialogs::showMainMenu(PluginEditor* editor, Component* centre) }); } -void Dialogs::showOkayCancelDialog(std::unique_ptr* target, Component* parent, String const& title, std::function const& callback, StringArray options, bool swapButtons) +void Dialogs::showOkayCancelDialog(std::unique_ptr* target, Component* parent, String const& title, std::function const& callback, StringArray options) { class OkayCancelDialog : public Component { - + + TextLayout layout; + public: OkayCancelDialog(Dialog* dialog, String const& title, std::function const& callback, StringArray& options, bool swap) : label("", title) - , swapButtons(swap) { - setSize(375, 200); - - label.setJustificationType(Justification::centred); - label.setFont(Fonts::getBoldFont().withHeight(14.0f)); + auto attributedTitle = AttributedString(title); + attributedTitle.setJustification(Justification::centred); + attributedTitle.setFont(Fonts::getBoldFont().withHeight(14)); + + setSize(265, 150); + layout.createLayout(attributedTitle, getWidth()); - addAndMakeVisible(label); addAndMakeVisible(cancel); addAndMakeVisible(okay); @@ -231,32 +233,33 @@ void Dialogs::showOkayCancelDialog(std::unique_ptr* target, Component* p dialog->closeDialog(); }; - cancel.changeWidthToFitText(); - okay.changeWidthToFitText(); setOpaque(false); } + + void paint(Graphics& g) override + { + auto contentBounds = getLocalBounds().reduced(16); + layout.draw(g, contentBounds.removeFromTop(48).toFloat()); + } void resized() override { - label.setBounds(20, 25, 360, 30); - if (swapButtons) { - okay.setBounds(20, 80, 80, 25); - cancel.setBounds(300, 80, 80, 25); - } else { - cancel.setBounds(20, 80, 80, 25); - okay.setBounds(300, 80, 80, 25); - } + auto contentBounds = getLocalBounds().reduced(16); + contentBounds.removeFromTop(54); + + okay.setBounds(contentBounds.removeFromTop(28)); + contentBounds.removeFromTop(6); + cancel.setBounds(contentBounds.removeFromTop(28)); } private: Label label; - bool swapButtons; TextButton cancel = TextButton("Cancel"); TextButton okay = TextButton("OK"); }; - auto* dialog = new Dialog(target, parent, 400, 130, false); - auto* dialogContent = new OkayCancelDialog(dialog, title, callback, options, swapButtons); + auto* dialog = new Dialog(target, parent, 265, 150, false); + auto* dialogContent = new OkayCancelDialog(dialog, title, callback, options); dialog->setViewedComponent(dialogContent); target->reset(dialog); diff --git a/Source/Dialogs/Dialogs.h b/Source/Dialogs/Dialogs.h index 9a5c4e0db4..07b71ee4e1 100644 --- a/Source/Dialogs/Dialogs.h +++ b/Source/Dialogs/Dialogs.h @@ -188,7 +188,7 @@ struct Dialogs { static void showMainMenu(PluginEditor* editor, Component* centre); - static void showOkayCancelDialog(std::unique_ptr* target, Component* parent, String const& title, std::function const& callback, StringArray options = { "Okay", "Cancel " }, bool swapButtons = false); + static void showOkayCancelDialog(std::unique_ptr* target, Component* parent, String const& title, std::function const& callback, StringArray options = { "Okay", "Cancel " }); static void showHeavyExportDialog(std::unique_ptr* target, Component* parent); diff --git a/Source/Utility/Autosave.h b/Source/Utility/Autosave.h index 3056ab2d15..b7882660d2 100644 --- a/Source/Utility/Autosave.h +++ b/Source/Utility/Autosave.h @@ -50,7 +50,7 @@ class Autosave : public Timer int minutesDifference = (autoSavedTime - fileChangedTime) / 60000.0; Dialogs::showOkayCancelDialog( - &editor->openedDialog, editor, "Restore autosave? (last autosave is " + String(minutesDifference) + " minutes newer)", [lastAutoSavedPatch, patchPath, callback](bool useAutosaved) { + &editor->openedDialog, editor, "Restore autosave?\n (last autosave is " + String(minutesDifference) + " minutes newer)", [lastAutoSavedPatch, patchPath, callback](bool useAutosaved) { if (useAutosaved) { MemoryOutputStream ostream; Base64::convertFromBase64(ostream, lastAutoSavedPatch.getProperty("Patch").toString()); @@ -61,7 +61,7 @@ class Autosave : public Timer callback(); }, - { "Yes", "No" }, true); + { "Yes", "No" }); } else { callback(); } From 8ea416806b17b8f3b5df4b14f29a2463ef6de8c3 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 15:55:23 +0100 Subject: [PATCH 0135/1030] Fixed compilation --- Source/Dialogs/Dialogs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index c6d9979849..5978751b62 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -198,7 +198,7 @@ void Dialogs::showOkayCancelDialog(std::unique_ptr* target, Component* p TextLayout layout; public: - OkayCancelDialog(Dialog* dialog, String const& title, std::function const& callback, StringArray& options, bool swap) + OkayCancelDialog(Dialog* dialog, String const& title, std::function const& callback, StringArray& options) : label("", title) { auto attributedTitle = AttributedString(title); From c662d6f8845d5de260d8dcfb78517443f23f1f4d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 16:24:40 +0100 Subject: [PATCH 0136/1030] Fixing Windows compilation of Gem --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index b6c4db894e..3960fbaae6 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit b6c4db894e413e0863164c64e2d7ad5e3fdc345a +Subproject commit 3960fbaae65f8c0156a15f8c45d8f12e88b2c565 From 46233b688f604ffae4d18466e6d119fd6c070e7d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 16:26:37 +0100 Subject: [PATCH 0137/1030] Fixed potential compilation issue on Unix --- Source/Utility/OSUtils.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Utility/OSUtils.cpp b/Source/Utility/OSUtils.cpp index eb35fb0015..8aab9ab8c1 100644 --- a/Source/Utility/OSUtils.cpp +++ b/Source/Utility/OSUtils.cpp @@ -5,13 +5,14 @@ */ +#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 +#include + + #if !defined(__APPLE__) # include #endif -#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 -#include - #include "OSUtils.h" #if defined(__APPLE__) From d95d7ba54af8b29281a1bd765d22f6bb523d5df7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 22:17:24 +0100 Subject: [PATCH 0138/1030] Compilation fixes for BSD --- CMakeLists.txt | 28 ++++++++++++++++++++++------ Libraries/CMakeLists.txt | 26 -------------------------- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b1d1ff8a8..18b21feddd 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,8 +56,11 @@ if(("${CMAKE_SYSTEM_NAME}" MATCHES "Linux" AND ${CMAKE_SYSTEM_PROCESSOR} MATCHES set(ENABLE_SFIZZ OFF) endif() -if(MSVC) # Gem is not supported on Windows yet -#set(ENABLE_GEM OFF) +if(UNIX AND NOT LINUX) # on BSD +message(STATUS "Disabled sfizz") +message(STATUS "Disabled Gem") +set(ENABLE_GEM OFF) +set(ENABLE_SFIZZ OFF) endif() add_subdirectory(Libraries/ EXCLUDE_FROM_ALL) @@ -200,12 +203,15 @@ set(JUCE_COMPILE_DEFINITIONS JUCE_USE_COREIMAGE_LOADER=0 JUCE_SILENCE_XCODE_15_LINKER_WARNING=1 JUCE_USE_XRENDER=1 - JUCE_ALSA=1 JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS=1 - #JUCE_JACK=1 - #JUCE_JACK_CLIENT_NAME="plugdata" ) +if(LINUX) + list(APPEND JUCE_COMPILE_DEFINITIONS JUCE_ALSA=1) +elseif(UNIX AND NOT APPLE) # BSD + list(APPEND JUCE_COMPILE_DEFINITIONS JUCE_JACK=1 JUCE_JACK_CLIENT_NAME="plugdata") +endif() + set(PLUGDATA_COMPILE_DEFINITIONS PLUGDATA=1 PLUGDATA_VERSION="${PLUGDATA_VERSION}" @@ -249,6 +255,12 @@ if(UNIX AND NOT APPLE) list(APPEND libs curl X11) endif() +# Fixes BSD compilation +if(UNIX AND NOT LINUX) +include_directories(/usr/local/include) +link_directories(/usr/local/lib) +endif() + list(APPEND PLUGDATA_COMPILE_DEFINITIONS JUCE_MODAL_LOOPS_PERMITTED=1) list(APPEND PLUGDATA_INCLUDE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/Libraries/ELSE/sfont~/") @@ -460,10 +472,14 @@ set_target_properties(plugdata_standalone PROPERTIES RESOURCE "${CMAKE_CURRENT_SOURCE_DIR}/Resources/Icons/pd-file.icns") endif() -if(UNIX AND NOT APPLE) +if(LINUX) target_link_libraries(plugdata_standalone PRIVATE plugdata_core pd-src externals StandaloneBinaryData "-Wl,-export-dynamic") target_link_libraries(plugdata PRIVATE plugdata_core pd-src-multi externals-multi) target_link_libraries(plugdata_fx PRIVATE plugdata_core pd-src-multi externals-multi) +elseif(UNIX AND NOT APPLE) # BSD + target_link_libraries(plugdata_standalone PRIVATE plugdata_core pd-src externals lua fluidlite StandaloneBinaryData "-Wl,-export-dynamic") + target_link_libraries(plugdata PRIVATE plugdata_core pd-src-multi externals-multi lua fluidlite) + target_link_libraries(plugdata_fx PRIVATE plugdata_core pd-src-multi externals-mult lua fluidlite) elseif(APPLE) if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 15.0) set(MACOS_COMPAT_LINKER_FLAGS "-Wl,-ld_classic") diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index d025ac535a..9c19f0e9e1 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -340,32 +340,6 @@ list(APPEND SFONT_SOURCES ${FLUIDLITE_DIR}/src/fluid_sys.c ${FLUIDLITE_DIR}/src/fluid_tuning.c ${FLUIDLITE_DIR}/src/fluid_voice.c - - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/vorbisenc.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/info.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/analysis.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/bitrate.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/block.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/codebook.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/envelope.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/floor0.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/floor1.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/lookup.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/lpc.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/lsp.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/mapping0.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/mdct.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/psy.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/registry.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/res0.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/sharedbook.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/smallft.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/vorbisfile.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/window.c - ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/synthesis.c - - ${FLUIDLITE_DIR}/libogg-1.3.2/src/bitwise.c - ${FLUIDLITE_DIR}/libogg-1.3.2/src/framing.c ) list(APPEND SFONT_INCLUDES From 24c823d601538355443b5807818607e9cb450b40 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 22:22:34 +0100 Subject: [PATCH 0139/1030] BSD build fix --- Libraries/CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 9c19f0e9e1..797496b377 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -289,9 +289,10 @@ if(WIN32) list(APPEND LIBPD_COMPILE_DEFINITIONS HAVE_STRUCT_TIMESPEC=1 _CRT_SECURE_NO_WARNINGS=1 HAVE_ALLOCA=1 DONT_USE_ALLOCA=0) endif() elseif(UNIX) - list(APPEND LIBPD_COMPILE_DEFINITIONS HAVE_LIBDL=1 HAVE_UNISTD_H=1 HAVE_ALLOCA_H=1 HAVE_ALLOCA=1 DONT_USE_ALLOCA=0) - if(NOT APPLE) - list(APPEND LIBPD_COMPILE_DEFINITIONS HAVE_ENDIAN_H=1) + if(LINUX) + list(APPEND LIBPD_COMPILE_DEFINITIONS HAVE_LIBDL=1 HAVE_UNISTD_H=1 HAVE_ALLOCA_H=1 HAVE_ALLOCA=1 DONT_USE_ALLOCA=0) + elseif(NOT APPLE) + list(APPEND LIBPD_COMPILE_DEFINITIONS HAVE_LIBDL=1 HAVE_UNISTD_H=1 HAVE_ALLOCA=1 DONT_USE_ALLOCA=0 HAVE_ENDIAN_H=0) endif() endif() From d332b4e94d070b65e2a0f18acdfa9d5d3917ea8c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 22:24:36 +0100 Subject: [PATCH 0140/1030] macOS build fix --- Libraries/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 797496b377..2588587f6b 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -289,9 +289,9 @@ if(WIN32) list(APPEND LIBPD_COMPILE_DEFINITIONS HAVE_STRUCT_TIMESPEC=1 _CRT_SECURE_NO_WARNINGS=1 HAVE_ALLOCA=1 DONT_USE_ALLOCA=0) endif() elseif(UNIX) - if(LINUX) + if(LINUX OR APPLE) list(APPEND LIBPD_COMPILE_DEFINITIONS HAVE_LIBDL=1 HAVE_UNISTD_H=1 HAVE_ALLOCA_H=1 HAVE_ALLOCA=1 DONT_USE_ALLOCA=0) - elseif(NOT APPLE) + else() # BSD list(APPEND LIBPD_COMPILE_DEFINITIONS HAVE_LIBDL=1 HAVE_UNISTD_H=1 HAVE_ALLOCA=1 DONT_USE_ALLOCA=0 HAVE_ENDIAN_H=0) endif() endif() From e1a4a8f8196a495827423efc93793cfcd9402015 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 22:37:34 +0100 Subject: [PATCH 0141/1030] macOS fix --- CMakeLists.txt | 2 +- Source/Components/SuggestionComponent.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 18b21feddd..48106c4e2f 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,7 @@ if(("${CMAKE_SYSTEM_NAME}" MATCHES "Linux" AND ${CMAKE_SYSTEM_PROCESSOR} MATCHES set(ENABLE_SFIZZ OFF) endif() -if(UNIX AND NOT LINUX) # on BSD +if(UNIX AND NOT LINUX AND NOT APPLE) # on BSD message(STATUS "Disabled sfizz") message(STATUS "Disabled Gem") set(ENABLE_GEM OFF) diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index 6a2a287186..26e2fd8726 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -191,7 +191,7 @@ class SuggestionComponent : public Component void paint(Graphics& g) override { auto scrollbarIndent = parent->port->canScrollVertically() ? 6 : 0; - + auto backgroundColour = findColour(getToggleState() ? PlugDataColour::popupMenuActiveBackgroundColourId : PlugDataColour::popupMenuBackgroundColourId); auto buttonArea = getLocalBounds().withTrimmedRight((parent->canBeTransparent() ? 42 : 2) + scrollbarIndent).toFloat().reduced(4, 1); From 6c674b8ea9b2826fece75fdc46a633e107e97f86 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 22:38:38 +0100 Subject: [PATCH 0142/1030] Update melatonin_blur --- Libraries/melatonin_blur | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/melatonin_blur b/Libraries/melatonin_blur index 2356c3ed5e..ae44478ff6 160000 --- a/Libraries/melatonin_blur +++ b/Libraries/melatonin_blur @@ -1 +1 @@ -Subproject commit 2356c3ed5e84fe5fceca6c6b1342fee53fbadddc +Subproject commit ae44478ff6de763551923c37afb0c81ea96243a8 From 3b91d8d51e47197d62fe1914666c85dd2ea34984 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 22:50:11 +0100 Subject: [PATCH 0143/1030] Linux build fix --- Source/Utility/OSUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Utility/OSUtils.cpp b/Source/Utility/OSUtils.cpp index 8aab9ab8c1..7c171b8fe4 100644 --- a/Source/Utility/OSUtils.cpp +++ b/Source/Utility/OSUtils.cpp @@ -8,8 +8,8 @@ #define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 #include - #if !defined(__APPLE__) +#undef JUCE_GUI_BASICS_INCLUDE_XHEADERS # include #endif From 383f1e2db49ae22ff54e5fefad1eca9bf602fc78 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 22:57:45 +0100 Subject: [PATCH 0144/1030] Fixed linker error --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 48106c4e2f..360908dd4c 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -241,6 +241,7 @@ target_link_libraries(juce juce::juce_dsp juce::juce_cryptography juce::juce_opengl + melatonin_blur ) target_compile_options(juce PUBLIC $<$:${JUCE_LTO_FLAGS}>) From 0321f32070140be79b800c93788618a93a5434be Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Jan 2024 23:45:18 +0100 Subject: [PATCH 0145/1030] Compilation fix --- .../raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h index 62e45188bd..6defa10c07 100644 --- a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h +++ b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h @@ -1,7 +1,5 @@ #pragma once -#include - #include #include #include From 070894537f362862117eb7fde1e92dc8b2e0bc54 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Jan 2024 00:11:29 +0100 Subject: [PATCH 0146/1030] Linux compilation fix --- .../raw_keyboard_input/src/LinuxKeyboard.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/LinuxKeyboard.cpp b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/LinuxKeyboard.cpp index 66c2ae4766..a1f2559946 100644 --- a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/LinuxKeyboard.cpp +++ b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/LinuxKeyboard.cpp @@ -1,7 +1,4 @@ #include "LinuxKeyboard.h" -#include -#include -#include #include LinuxKeyboard::LinuxKeyboard(juce::Component* parent) : Keyboard(parent) From 476f6a31ce0ad710de3fa8c0191274bb361751f6 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Jan 2024 01:53:48 +0100 Subject: [PATCH 0147/1030] Windows compilation fix --- Libraries/CMakeLists.txt | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 2588587f6b..c406c56987 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -343,6 +343,37 @@ list(APPEND SFONT_SOURCES ${FLUIDLITE_DIR}/src/fluid_voice.c ) +if(MSVC) + +list(APPEND SFONT_SOURCES + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/vorbisenc.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/info.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/analysis.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/bitrate.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/block.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/codebook.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/envelope.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/floor0.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/floor1.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/lookup.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/lpc.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/lsp.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/mapping0.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/mdct.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/psy.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/registry.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/res0.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/sharedbook.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/smallft.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/vorbisfile.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/window.c + ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/synthesis.c + + ${FLUIDLITE_DIR}/libogg-1.3.2/src/bitwise.c + ${FLUIDLITE_DIR}/libogg-1.3.2/src/framing.c +) +endif() + list(APPEND SFONT_INCLUDES ${FLUIDLITE_DIR}/libvorbis-1.3.5/include ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib From fc78f4451819bd1d245514fc853fefe9c6710192 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Jan 2024 02:22:21 +0100 Subject: [PATCH 0148/1030] Make all object init sizes the same --- Source/Object.cpp | 2 +- Source/Objects/CommentObject.h | 2 +- Source/Objects/MessageObject.h | 2 +- Source/Objects/TextObject.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index 82eb401206..c09a51b6e0 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -43,7 +43,7 @@ Object::Object(Canvas* parent, String const& name, Point position) // Open editor for undefined objects // Delay the setting of the type to prevent creating an invalid object first if (name.isEmpty()) { - setSize(100, height); + setSize(58, height); } else { setType(name); } diff --git a/Source/Objects/CommentObject.h b/Source/Objects/CommentObject.h index c08809d753..ecc6c361ec 100644 --- a/Source/Objects/CommentObject.h +++ b/Source/Objects/CommentObject.h @@ -166,7 +166,7 @@ class CommentObject final : public ObjectBase int textWidth; if (objText.isEmpty()) { // If text is empty, set to minimum width - textWidth = std::max(charWidth, TextObjectHelper::minWidth) * fontWidth; + textWidth = std::max(charWidth, 6) * fontWidth; } else if (charWidth == 0) { // If width is set to automatic, calculate based on text width textWidth = std::clamp(idealWidth, TextObjectHelper::minWidth * fontWidth, fontWidth * 60); } else { // If width was set manually, calculate what the width is diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index 0fd72b057f..7c99568b90 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -77,7 +77,7 @@ class MessageObject final : public ObjectBase int textWidth; if (objText.isEmpty()) { // If text is empty, set to minimum width - textWidth = std::max(charWidth, TextObjectHelper::minWidth) * fontWidth; + textWidth = std::max(charWidth, 6) * fontWidth; } else if (charWidth == 0) { // If width is set to automatic, calculate based on text width textWidth = std::clamp(idealWidth, TextObjectHelper::minWidth * fontWidth, fontWidth * 60); } else { // If width was set manually, calculate what the width is diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index 62254aee14..c2c1a9bacf 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -324,7 +324,7 @@ class TextBase : public ObjectBase int textWidth; if (objText.isEmpty()) { // If text is empty, set to minimum width - textWidth = std::max(charWidth, TextObjectHelper::minWidth) * fontWidth; + textWidth = std::max(charWidth, 6) * fontWidth; } else if (charWidth == 0) { // If width is set to automatic, calculate based on text width textWidth = std::clamp(idealWidth, TextObjectHelper::minWidth * fontWidth, fontWidth * 60); } else { // If width was set manually, calculate what the width is From 4e3bc326e30e1ee9d5ddee53284f515c43ab8af7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Jan 2024 02:36:41 +0100 Subject: [PATCH 0149/1030] Improved Gem theme colours --- Source/LookAndFeel.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index 6fb9987f8e..5948c7ba6c 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -1481,7 +1481,7 @@ const String PlugDataLook::defaultThemesXml = " " " default_object_background=\"ff333333\" object_outline_colour=\"ff696969\"\n" " selected_object_outline_colour=\"ff72aedf\" gui_internal_outline_colour=\"ff696969\"\n" " toolbar_outline_colour=\"ff393939\" outline_colour=\"ff393939\" data_colour=\"ff72aedf\"\n" -" connection_colour=\"ffb3b3b3\" signal_colour=\"ffe1ef00\" gem_colour=\"ff02f503\" dialog_background=\"ff3b3b3b\"\n" +" connection_colour=\"ffb3b3b3\" signal_colour=\"ffe1ef00\" gem_colour=\"ff01be00\" dialog_background=\"ff3b3b3b\"\n" " sidebar_colour=\"ff3e3e3e\" sidebar_text=\"ffe4e4e4\" sidebar_background_active=\"ff4f4f4f\"\n" " sidebar_active_text=\"ffe4e4e4\" levelmeter_active=\"ff72aedf\" levelmeter_background=\"ff494949\"\n" " levelmeter_thumb=\"ffe4e4e4\" panel_background=\"ff4f4f4f\" panel_foreground=\"ff3f3f3f\"\n" @@ -1501,7 +1501,7 @@ const String PlugDataLook::defaultThemesXml = " " " object_outline_colour=\"ff000000\" selected_object_outline_colour=\"ff000000\"\n" " gui_internal_outline_colour=\"ff000000\" toolbar_outline_colour=\"ff000000\"\n" " outline_colour=\"ff000000\" iolet_area_colour=\"ffffffff\" iolet_outline_colour=\"ff000000\"\n" -" data_colour=\"ff000000\" connection_colour=\"ff000000\" signal_colour=\"ff000000\" gem_colour=\"ff02f503\"\n" +" data_colour=\"ff000000\" connection_colour=\"ff000000\" signal_colour=\"ff000000\" gem_colour=\"ff000000\"\n" " dialog_background=\"ffffffff\" sidebar_colour=\"ffefefef\" sidebar_text=\"ff000000\"\n" " sidebar_background_active=\"ffa0a0a0\" sidebar_active_text=\"ff000000\"\n" " levelmeter_active=\"ff000000\" levelmeter_background=\"ffededed\"\n" @@ -1520,7 +1520,7 @@ const String PlugDataLook::defaultThemesXml = " " " default_object_background=\"ff000000\" object_outline_colour=\"ffffffff\"\n" " selected_object_outline_colour=\"ffffffff\" gui_internal_outline_colour=\"ffffffff\"\n" " toolbar_outline_colour=\"ffffffff\" outline_colour=\"ffffffff\" data_colour=\"ffffffff\"\n" -" connection_colour=\"ffffffff\" signal_colour=\"ffffffff\" gem_colour=\"ff02f503\" dialog_background=\"ff000000\"\n" +" connection_colour=\"ffffffff\" signal_colour=\"ffffffff\" gem_colour=\"ffffffff\" dialog_background=\"ff000000\"\n" " sidebar_colour=\"ff000000\" sidebar_text=\"ffffffff\" sidebar_background_active=\"ffa0a0a0\"\n" " sidebar_active_text=\"ffffffff\" levelmeter_active=\"ffffffff\" levelmeter_background=\"ff808080\"\n" " levelmeter_thumb=\"ffffffff\" panel_background=\"ff0e0e0e\" panel_foreground=\"ff000000\"\n" @@ -1539,7 +1539,7 @@ const String PlugDataLook::defaultThemesXml = " " " default_object_background=\"ff191919\" object_outline_colour=\"ff696969\"\n" " selected_object_outline_colour=\"ff42a2c8\" gui_internal_outline_colour=\"ff696969\"\n" " toolbar_outline_colour=\"ff2f2f2f\" outline_colour=\"ff393939\" data_colour=\"ff42a2c8\"\n" -" connection_colour=\"ffe1e1e1\" signal_colour=\"ffff8500\" gem_colour=\"ff02f503\" dialog_background=\"ff191919\"\n" +" connection_colour=\"ffe1e1e1\" signal_colour=\"ffff8500\" gem_colour=\"ff01be00\" dialog_background=\"ff191919\"\n" " sidebar_colour=\"ff191919\" sidebar_text=\"ffe1e1e1\" sidebar_background_active=\"ff282828\"\n" " sidebar_active_text=\"ffe1e1e1\" levelmeter_active=\"ff42a2c8\" levelmeter_background=\"ff2e2e2e\"\n" " levelmeter_thumb=\"ffe3e3e3\" panel_background=\"ff2c2c2c\" panel_foreground=\"ff1f1f1f\"\n" @@ -1558,7 +1558,7 @@ const String PlugDataLook::defaultThemesXml = " " " default_object_background=\"ffe4e4e4\" object_outline_colour=\"ffb7b7b7\"\n" " selected_object_outline_colour=\"ff007aff\" gui_internal_outline_colour=\"ffb7b7b7\"\n" " toolbar_outline_colour=\"ffdfdfdf\" outline_colour=\"ffd0d0d0\" data_colour=\"ff007aff\"\n" -" connection_colour=\"ffb3b3b3\" signal_colour=\"ffff8500\" gem_colour=\"ff02f503\" dialog_background=\"ffebebeb\"\n" +" connection_colour=\"ffb3b3b3\" signal_colour=\"ffff8500\" gem_colour=\"ff01de00\" dialog_background=\"ffebebeb\"\n" " sidebar_colour=\"ffefefef\" sidebar_text=\"ff373737\" sidebar_background_active=\"ffe4e4e4\"\n" " sidebar_active_text=\"ff373737\" levelmeter_active=\"ff007aff\" levelmeter_background=\"ffe1e1e1\"\n" " levelmeter_thumb=\"ff9a9a9a\" panel_background=\"fff7f7f7\" panel_foreground=\"fffdfdfd\"\n" @@ -1577,7 +1577,7 @@ const String PlugDataLook::defaultThemesXml = " " " default_object_background=\"ffe3dfd9\" object_outline_colour=\"ff968e82\"\n" " selected_object_outline_colour=\"ff5da0c4\" gui_internal_outline_colour=\"ff968e82\"\n" " toolbar_outline_colour=\"ffbdb3a4\" outline_colour=\"ff968e82\" data_colour=\"ff5da0c4\"\n" -" connection_colour=\"ffb3b3b3\" signal_colour=\"ffff8502\" gem_colour=\"ff02f503\" dialog_background=\"ffd2cdc4\"\n" +" connection_colour=\"ffb3b3b3\" signal_colour=\"ffff8502\" gem_colour=\"ff01be00\" dialog_background=\"ffd2cdc4\"\n" " sidebar_colour=\"ffdedad3\" sidebar_text=\"ff5a5a5a\" sidebar_background_active=\"ffefefef\"\n" " sidebar_active_text=\"ff5a5a5a\" levelmeter_active=\"ff5da0c4\" levelmeter_background=\"ffc0bbb2\"\n" " levelmeter_thumb=\"ff7a7a7a\" panel_background=\"ffd2cdc4\" panel_foreground=\"ffe7e2d8\"\n" @@ -1596,7 +1596,7 @@ const String PlugDataLook::defaultThemesXml = " " " default_object_background=\"ff191919\" object_outline_colour=\"ff383838\"\n" " selected_object_outline_colour=\"ffffacab\" gui_internal_outline_colour=\"ff626262\"\n" " toolbar_outline_colour=\"ff343434\" outline_colour=\"ff383838\" data_colour=\"ff5bcefa\"\n" -" connection_colour=\"ffa0a0a0\" signal_colour=\"ffffacab\" gem_colour=\"ff02f503\" dialog_background=\"ff191919\"\n" +" connection_colour=\"ffa0a0a0\" signal_colour=\"ffffacab\" gem_colour=\"ff01de00\" dialog_background=\"ff191919\"\n" " sidebar_colour=\"ff232323\" sidebar_text=\"ffffffff\" sidebar_background_active=\"ff383838\"\n" " sidebar_active_text=\"ffffffff\" levelmeter_active=\"ff5bcefa\" levelmeter_background=\"ff3a3a3a\"\n" " levelmeter_thumb=\"fff5f5f5\" panel_background=\"ff2c2c2c\" panel_foreground=\"ff1f1f1f\"\n" From 29c9ad1eeabcd3a00e9f136cccce8c5936cb1c2d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Jan 2024 02:36:57 +0100 Subject: [PATCH 0150/1030] Fixed okay/cancel dialog colour --- Source/Dialogs/Dialogs.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index 5978751b62..e99b66301e 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -204,6 +204,7 @@ void Dialogs::showOkayCancelDialog(std::unique_ptr* target, Component* p auto attributedTitle = AttributedString(title); attributedTitle.setJustification(Justification::centred); attributedTitle.setFont(Fonts::getBoldFont().withHeight(14)); + attributedTitle.setColour(findColour(PlugDataColour::panelTextColourId)); setSize(265, 150); layout.createLayout(attributedTitle, getWidth()); From e4e4d7e4f337c8baaff3a95c1e6b40c6a7bae23b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Jan 2024 02:57:00 +0100 Subject: [PATCH 0151/1030] BSD improvements --- Source/Heavy/ExportingProgressView.h | 6 ------ Source/Heavy/HeavyExportDialog.cpp | 6 ------ Source/PluginMode.h | 4 ++-- Source/Utility/WindowDragger.h | 2 +- 4 files changed, 3 insertions(+), 15 deletions(-) diff --git a/Source/Heavy/ExportingProgressView.h b/Source/Heavy/ExportingProgressView.h index c28de5889d..0f2b4b2337 100644 --- a/Source/Heavy/ExportingProgressView.h +++ b/Source/Heavy/ExportingProgressView.h @@ -7,12 +7,6 @@ #include "Canvas.h" #include "Utility/OSUtils.h" -#if JUCE_LINUX -# include -# include -# include -#endif - #include class ExportingProgressView : public Component diff --git a/Source/Heavy/HeavyExportDialog.cpp b/Source/Heavy/HeavyExportDialog.cpp index 5838f52ac4..94cb39a1a2 100644 --- a/Source/Heavy/HeavyExportDialog.cpp +++ b/Source/Heavy/HeavyExportDialog.cpp @@ -15,12 +15,6 @@ #include "Components/PropertiesPanel.h" #include "Utility/OSUtils.h" -#if JUCE_LINUX -# include -# include -# include -#endif - #include "Toolchain.h" #include "ExportingProgressView.h" #include "ExporterBase.h" diff --git a/Source/PluginMode.h b/Source/PluginMode.h index 540282f687..519cbfc908 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -139,7 +139,7 @@ class PluginMode : public Component { editor->pluginConstrainer.setSizeLimits(newWidth, newHeight, newWidth, newHeight); } -#if JUCE_LINUX +#if JUCE_LINUX || JUCE_BSD if (ProjectInfo::isStandalone) { OSUtils::updateX11Constraints(getPeer()->getNativeHandle()); } @@ -158,7 +158,7 @@ class PluginMode : public Component { mainWindow->setUsingNativeTitleBar(true); } editor->constrainer.setSizeLimits(850, 650, 99000, 99000); -#if JUCE_LINUX +#if JUCE_LINUX || JUCE_BSD OSUtils::updateX11Constraints(getPeer()->getNativeHandle()); #endif diff --git a/Source/Utility/WindowDragger.h b/Source/Utility/WindowDragger.h index 732e86e3d5..abae70f7bd 100644 --- a/Source/Utility/WindowDragger.h +++ b/Source/Utility/WindowDragger.h @@ -21,7 +21,7 @@ class WindowDragger { if (componentToDrag != nullptr) mouseDownWithinTarget = e.getEventRelativeTo(componentToDrag).getMouseDownPosition(); -#if JUCE_LINUX +#if JUCE_LINUX || JUCE_BSD auto* peer = componentToDrag->getPeer(); peer->startHostManagedResize(e.getPosition(), ResizableBorderComponent::Zone(0)); #endif From adbfc96372b57a64ea8d557c9ea5c21b29632038 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Jan 2024 03:08:22 +0100 Subject: [PATCH 0152/1030] Small BSD fix --- Source/LookAndFeel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index 5948c7ba6c..be96bc0e51 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -773,7 +773,7 @@ void PlugDataLook::getIdealPopupMenuItemSize(String const& text, bool const isSe idealHeight = standardMenuItemHeight > 0 ? standardMenuItemHeight : roundToInt(font.getHeight() * 1.3f); idealWidth = font.getStringWidth(text) + idealHeight; -#if JUCE_LINUX || JUCE_WINDOWS +#if !JUCE_MAC // Dumb check to see if there is a keyboard shortcut after the text. // On Linux and Windows, it seems to reserve way to much space for those. if (text.contains(" ")) { From 595a852643a29e125e1e168bf8a1078c326cbde8 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Jan 2024 13:08:14 +0100 Subject: [PATCH 0153/1030] Fixed theme panel jumping around --- Source/Dialogs/ThemePanel.h | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Source/Dialogs/ThemePanel.h b/Source/Dialogs/ThemePanel.h index 8789193bc5..d00954e55a 100644 --- a/Source/Dialogs/ThemePanel.h +++ b/Source/Dialogs/ThemePanel.h @@ -224,11 +224,15 @@ class ThemePanel : public SettingsDialogPanel void settingsFileReloaded() override { - updateSwatches(); + // TODO: find out if we need to do this? + // it seems to only really cause problems + //updateSwatches(); } void updateSwatches(bool forceUpdate = false) { + auto scrollPosition = panel.getViewport().getViewPositionY(); + panel.clear(); allPanels.clear(); @@ -426,10 +430,18 @@ class ThemePanel : public SettingsDialogPanel }); auto allThemes = PlugDataLook::getAllThemes(); - primaryThemeSelector->setOptions(allThemes); - secondaryThemeSelector->setOptions(allThemes); - primaryThemeSelector->setSelectedItem(allThemes.indexOf(PlugDataLook::selectedThemes[0])); - secondaryThemeSelector->setSelectedItem(allThemes.indexOf(PlugDataLook::selectedThemes[1])); + auto firstThemes = allThemes; + auto secondThemes = allThemes; + + // Remove theme selected in other combobox (so you can't pick the same theme twice) + firstThemes.removeString(PlugDataLook::selectedThemes[1]); + secondThemes.removeString(PlugDataLook::selectedThemes[0]); + + primaryThemeSelector->setOptions(firstThemes); + secondaryThemeSelector->setOptions(secondThemes); + + primaryThemeSelector->setSelectedItem(firstThemes.indexOf(PlugDataLook::selectedThemes[0])); + secondaryThemeSelector->setSelectedItem(secondThemes.indexOf(PlugDataLook::selectedThemes[1])); allPanels.add(fontPanel); allPanels.add(primaryThemeSelector); @@ -509,6 +521,7 @@ class ThemePanel : public SettingsDialogPanel updatingTheme = false; panel.repaint(); + panel.getViewport().setViewPosition(0, scrollPosition); } void valueChanged(Value& v) override @@ -561,19 +574,6 @@ class ThemePanel : public SettingsDialogPanel }*/ } - void paint(Graphics& g) override - { - /* - auto bounds = getLocalBounds().removeFromLeft(getWidth() / 2).withTrimmedLeft(6); - - auto themeRow = bounds.removeFromTop(23); - Fonts::drawText(g, "theme", themeRow, findColour(PlugDataColour::panelTextColourId)); - - auto fullThemeRow = getLocalBounds().removeFromTop(23); - g.setColour(findColour(PlugDataColour::outlineColourId)); - g.drawLine(Line(fullThemeRow.getBottomLeft(), fullThemeRow.getBottomRight()).toFloat(), -1.0f); */ - } - void resized() override { auto bounds = getLocalBounds(); From 4ff849d652d7fac0e374cbe41047614c828396f4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Jan 2024 13:54:36 +0100 Subject: [PATCH 0154/1030] Fixed undo for moving objects --- Source/Objects/IEMHelper.h | 6 +++--- Source/Pd/Interface.h | 16 +++++++++------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Source/Objects/IEMHelper.h b/Source/Objects/IEMHelper.h index 74fa5890b4..0f4edeae93 100644 --- a/Source/Objects/IEMHelper.h +++ b/Source/Objects/IEMHelper.h @@ -270,9 +270,9 @@ class IEMHelper { void setPdBounds(Rectangle const b) { if (auto iemgui = ptr.get()) { - iemgui->x_obj.te_xpix = b.getX(); - iemgui->x_obj.te_ypix = b.getY(); - + + pd::Interface::moveObject(iemgui->x_glist, &iemgui->x_obj.te_g, b.getX(), b.getY()); + iemgui->x_w = b.getWidth() - 1; iemgui->x_h = b.getHeight() - 1; } diff --git a/Source/Pd/Interface.h b/Source/Pd/Interface.h index 6c44365f68..58a2fab9de 100644 --- a/Source/Pd/Interface.h +++ b/Source/Pd/Interface.h @@ -136,10 +136,8 @@ struct Interface { EDITOR->canvas_undo_already_set_move = 0; int resortin = 0, resortout = 0; - if (!EDITOR->canvas_undo_already_set_move) { - canvas_undo_add(cnv, UNDO_MOTION, "motion", canvas_undo_set_move(cnv, 1)); - // EDITOR->canvas_undo_already_set_move = 1; - } + canvas_undo_add(cnv, UNDO_MOTION, "motion", canvas_undo_set_move(cnv, 1)); + for (auto* obj : objects) { gobj_displace(obj, cnv, dx, dy); @@ -468,12 +466,16 @@ struct Interface { static void moveObject(t_canvas* cnv, t_gobj* obj, int x, int y) { + EDITOR->canvas_undo_already_set_move = 0; + glist_noselect(cnv); + glist_select(cnv, obj); + canvas_undo_add(cnv, UNDO_MOTION, "motion", canvas_undo_set_move(cnv, 1)); + glist_noselect(cnv); + if (obj->g_pd->c_wb && obj->g_pd->c_wb->w_getrectfn && obj->g_pd->c_wb && obj->g_pd->c_wb->w_displacefn) { - int x1, y1, x2, y2; - + (*obj->g_pd->c_wb->w_getrectfn)(obj, cnv, &x1, &y1, &x2, &y2); - (*obj->g_pd->c_wb->w_displacefn)(obj, cnv, x - x1, y - y1); } } From d28cb2705697e2ae95844d1b2e960e26944b48a8 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Jan 2024 13:57:46 +0100 Subject: [PATCH 0155/1030] Fix for text object line wrapping --- Source/Objects/TextObject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index c2c1a9bacf..9b4e178a0a 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -355,7 +355,7 @@ class TextBase : public ObjectBase attributedText.setFont(Font(15)); textLayout = TextLayout(); - textLayout.createLayout(attributedText, textWidth); + textLayout.createLayout(attributedText, textWidth + 0.5f); layoutTextHash = currentLayoutHash; lastColourARGB = colour.getARGB(); lastTextWidth = textWidth; From de7d693ebb3379f712e472cf3254cb7e862dc5a9 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Jan 2024 14:00:02 +0100 Subject: [PATCH 0156/1030] Gem tooltip fixes --- Resources/Documentation/Gem/gemcubeframebuffer.md | 6 +++--- Resources/Documentation/Gem/rotateXYZ.md | 2 +- Resources/Documentation/Gem/scale.md | 2 +- Resources/Documentation/Gem/scaleXYZ.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Resources/Documentation/Gem/gemcubeframebuffer.md b/Resources/Documentation/Gem/gemcubeframebuffer.md index a91f5c3ab0..24d55a0a6d 100644 --- a/Resources/Documentation/Gem/gemcubeframebuffer.md +++ b/Resources/Documentation/Gem/gemcubeframebuffer.md @@ -13,14 +13,14 @@ methods: description: set the background color of the framebuffer inlets: 1st: - - type: gemstate + - type: gemlist description: outlets: 1st: - - type: gemstate + - type: gemlist description: 2nd: - - type: gemstate + - type: gemlist description: draft: false --- diff --git a/Resources/Documentation/Gem/rotateXYZ.md b/Resources/Documentation/Gem/rotateXYZ.md index 06bd81fc89..aa004c0265 100644 --- a/Resources/Documentation/Gem/rotateXYZ.md +++ b/Resources/Documentation/Gem/rotateXYZ.md @@ -1,6 +1,6 @@ --- title: rotateXYZ -description: rotates a gemList by the specified rotation around the X-, Y-, and Z-axes +description: rotates a gemlist by the specified rotation around the X-, Y-, and Z-axes categories: - object pdcategory: Gem, Graphics diff --git a/Resources/Documentation/Gem/scale.md b/Resources/Documentation/Gem/scale.md index a03d93af52..408702898a 100644 --- a/Resources/Documentation/Gem/scale.md +++ b/Resources/Documentation/Gem/scale.md @@ -1,6 +1,6 @@ --- title: scale -description: scales a gemList with specified scaling factors +description: scales a gemlist with specified scaling factors categories: - object pdcategory: Gem, Graphics diff --git a/Resources/Documentation/Gem/scaleXYZ.md b/Resources/Documentation/Gem/scaleXYZ.md index 6fcae6b269..2c13a75535 100644 --- a/Resources/Documentation/Gem/scaleXYZ.md +++ b/Resources/Documentation/Gem/scaleXYZ.md @@ -1,6 +1,6 @@ --- title: scaleXYZ -description: scales a gemList along the X-, Y-, and Z-axes +description: scales a gemlist along the X-, Y-, and Z-axes categories: - object pdcategory: Gem, Graphics From 77891d48b77ed0fd92b170708369a2241859c7d2 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Jan 2024 15:59:48 +0100 Subject: [PATCH 0157/1030] Undo move fixes --- Source/Pd/Interface.h | 29 +++++++++++++++++------------ Source/Pd/Patch.cpp | 13 +++++++------ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/Source/Pd/Interface.h b/Source/Pd/Interface.h index 58a2fab9de..a41e166da0 100644 --- a/Source/Pd/Interface.h +++ b/Source/Pd/Interface.h @@ -128,16 +128,17 @@ struct Interface { static void moveObjects(t_canvas* cnv, int dx, int dy, std::vector const& objects) { glist_noselect(cnv); - + for (auto* obj : objects) { glist_select(cnv, obj); } - - EDITOR->canvas_undo_already_set_move = 0; - - int resortin = 0, resortout = 0; - canvas_undo_add(cnv, UNDO_MOTION, "motion", canvas_undo_set_move(cnv, 1)); + if(!EDITOR->canvas_undo_already_set_move) { + canvas_undo_add(cnv, UNDO_MOTION, "motion", canvas_undo_set_move(cnv, 1)); + EDITOR->canvas_undo_already_set_move = 1; + } + + int resortin = 0, resortout = 0; for (auto* obj : objects) { gobj_displace(obj, cnv, dx, dy); @@ -151,7 +152,6 @@ struct Interface { canvas_resortinlets(cnv); if (resortout) canvas_resortoutlets(cnv); - sys_vgui("pdtk_canvas_getscroll .x%lx.c\n", cnv); if (cnv->gl_editor->e_selection) canvas_dirty(cnv, 1); @@ -466,11 +466,14 @@ struct Interface { static void moveObject(t_canvas* cnv, t_gobj* obj, int x, int y) { - EDITOR->canvas_undo_already_set_move = 0; - glist_noselect(cnv); - glist_select(cnv, obj); - canvas_undo_add(cnv, UNDO_MOTION, "motion", canvas_undo_set_move(cnv, 1)); - glist_noselect(cnv); + if(!EDITOR->canvas_undo_already_set_move) { + glist_noselect(cnv); + glist_select(cnv, obj); + canvas_undo_add(cnv, UNDO_MOTION, "motion", canvas_undo_set_move(cnv, 1)); + glist_noselect(cnv); + + EDITOR->canvas_undo_already_set_move = 1; + } if (obj->g_pd->c_wb && obj->g_pd->c_wb->w_getrectfn && obj->g_pd->c_wb && obj->g_pd->c_wb->w_displacefn) { int x1, y1, x2, y2; @@ -478,6 +481,8 @@ struct Interface { (*obj->g_pd->c_wb->w_getrectfn)(obj, cnv, &x1, &y1, &x2, &y2); (*obj->g_pd->c_wb->w_displacefn)(obj, cnv, x - x1, y - y1); } + + EDITOR->canvas_undo_already_set_move = 0; } static bool canConnect(t_canvas* cnv, t_object* src, int nout, t_object* sink, int nin) diff --git a/Source/Pd/Patch.cpp b/Source/Pd/Patch.cpp index a079102d29..52c947b0e1 100644 --- a/Source/Pd/Patch.cpp +++ b/Source/Pd/Patch.cpp @@ -308,6 +308,8 @@ t_gobj* Patch::createObject(int x, int y, String const& name) SETSYMBOL(argv.data() + i + 2, instance->generateSymbol(tokens[i])); } } + + EDITOR->canvas_undo_already_set_move = 1; if (auto patch = ptr.get()) { setCurrent(); @@ -461,7 +463,6 @@ void Patch::deselectAll() { if (auto patch = ptr.get()) { glist_noselect(patch.get()); - libpd_this_instance()->pd_gui->i_editor->canvas_undo_already_set_move = 0; } } @@ -573,10 +574,10 @@ void Patch::undo() setCurrent(); auto x = patch.get(); glist_noselect(x); - libpd_this_instance()->pd_gui->i_editor->canvas_undo_already_set_move = 0; - + pd::Interface::undo(patch.get()); - + EDITOR->canvas_undo_already_set_move = 1; + updateUndoRedoString(); } } @@ -587,9 +588,9 @@ void Patch::redo() setCurrent(); auto x = patch.get(); glist_noselect(x); - libpd_this_instance()->pd_gui->i_editor->canvas_undo_already_set_move = 0; - + pd::Interface::redo(patch.get()); + EDITOR->canvas_undo_already_set_move = 1; updateUndoRedoString(); } From b0c6f6aa333861ac510dea9620172f9e9351f01a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Jan 2024 18:00:24 +0100 Subject: [PATCH 0158/1030] Fixed undo issue --- Source/Pd/Interface.h | 8 +++++++- Source/Pd/Patch.cpp | 3 +-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Source/Pd/Interface.h b/Source/Pd/Interface.h index a41e166da0..103ec30643 100644 --- a/Source/Pd/Interface.h +++ b/Source/Pd/Interface.h @@ -158,7 +158,7 @@ struct Interface { glist_noselect(cnv); - libpd_this_instance()->pd_gui->i_editor->canvas_undo_already_set_move = 0; + EDITOR->canvas_undo_already_set_move = 0; } static t_gobj* getNewest(t_canvas* cnv) @@ -356,6 +356,12 @@ struct Interface { canvas_loadbang(reinterpret_cast(new_object)); else if (zgetfn(&new_object->g_pd, gensym("loadbang"))) vmess(&new_object->g_pd, gensym("loadbang"), "f", LB_LOAD); + + // This is needed since object creation happens in 2 undo steps in pd-vanilla, but is only 1 undo step in plugdata + int pos = glist_getindex(cnv, new_object); + canvas_undo_add(glist_getcanvas(cnv), UNDO_RECREATE, "recreate", + (void *)canvas_undo_set_recreate(cnv, + new_object, pos)); } canvas_unsetcurrent(cnv); diff --git a/Source/Pd/Patch.cpp b/Source/Pd/Patch.cpp index 52c947b0e1..29a4f7d753 100644 --- a/Source/Pd/Patch.cpp +++ b/Source/Pd/Patch.cpp @@ -309,10 +309,9 @@ t_gobj* Patch::createObject(int x, int y, String const& name) } } - EDITOR->canvas_undo_already_set_move = 1; - if (auto patch = ptr.get()) { setCurrent(); + EDITOR->canvas_undo_already_set_move = 1; return pd::Interface::createObject(patch.get(), typesymbol, argc, argv.data()); } From ca34eff6a4ca227a22cb20f24638588c5d54cc6e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Jan 2024 18:36:33 +0100 Subject: [PATCH 0159/1030] Fixed iolet colour updating when renaming Gem objects --- Source/Object.cpp | 8 ++++++-- Source/Object.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index c09a51b6e0..4780705eda 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -29,6 +29,8 @@ extern "C" { #include #include + +int is_gem_object(const char* sym); } Object::Object(Canvas* parent, String const& name, Point position) @@ -348,9 +350,11 @@ void Object::setType(String const& newType, pd::WeakReference existingObject) jassertfalse; return; } - + // Create gui for the object gui.reset(ObjectBase::createGui(objectPtr, this)); + + isGemObject = is_gem_object(gui->getText().toRawUTF8()); if (gui) { gui->initialise(); @@ -732,7 +736,7 @@ void Object::updateIolets() } // Looking up tooltips takes a bit of time, so we make sure we're not constantly updating them for no reason - bool tooltipsNeedUpdate = gui->getPatch() != nullptr || numInputs != oldNumInputs || numOutputs != oldNumOutputs; + bool tooltipsNeedUpdate = gui->getPatch() != nullptr || numInputs != oldNumInputs || numOutputs != oldNumOutputs || isGemObject; for (auto* iolet : iolets) { if (gui && !iolet->isInlet) { diff --git a/Source/Object.h b/Source/Object.h index 93a98236a7..b6e6f5cdd4 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -126,6 +126,7 @@ class Object : public Component bool wasLockedOnMouseDown = false; bool indexShown = false; bool isHvccCompatible = true; + bool isGemObject = false; bool showActiveState = false; float activeStateAlpha = 0.0f; From 966cb124c90cfaab94356ce2ceb2283721b962e2 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Jan 2024 21:40:43 +0100 Subject: [PATCH 0160/1030] Fixed parameter names not reloading in DAW --- Source/PluginProcessor.cpp | 17 ++++++++++++++++- Source/PluginProcessor.h | 2 +- Source/Utility/PluginParameter.h | 1 - 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index b10d8faee9..6a0291fdd2 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -176,6 +176,7 @@ PluginProcessor::~PluginProcessor() patches.clear(); } + void PluginProcessor::initialiseFilesystem() { auto const& homeDir = ProjectInfo::appDataDir; @@ -1150,7 +1151,7 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) jassert(xmlState); PlugDataParameter::loadStateInformation(*xmlState, getParameters()); - + auto versionString = String("0.6.1"); // latest version that didn't have version inside the daw state if (!xmlState->hasAttribute("Legacy") || xmlState->getBoolAttribute("Legacy")) { @@ -1199,6 +1200,20 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) } } }); + + // After loading a state, we need to update all the parameters + if(PluginHostType::getPluginLoadedAs() == AudioProcessor::wrapperType_AudioUnit || PluginHostType::getPluginLoadedAs() == AudioProcessor::wrapperType_AudioUnitv3) + { + // In Logic, loading them instantly causes a crash :( + Timer::callAfterDelay(800, [this, _this = WeakReference(this)](){ + if(_this) { + updateHostDisplay(AudioProcessorListener::ChangeDetails {}.withParameterInfoChanged(true)); + } + }); + } + else { + updateHostDisplay(AudioProcessorListener::ChangeDetails {}.withParameterInfoChanged(true)); + } } pd::Patch::Ptr PluginProcessor::loadPatch(File const& patchFile, PluginEditor* editor, int splitIndex) diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index e93cdb4d11..3719d5bef4 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -113,7 +113,7 @@ class PluginProcessor : public AudioProcessor Array getEditors() const; void performParameterChange(int type, String const& name, float value) override; - + // Jyg added this void fillDataBuffer(std::vector const& list) override; void parseDataBuffer(XmlElement const& xml) override; diff --git a/Source/Utility/PluginParameter.h b/Source/Utility/PluginParameter.h index c811c42c30..8947edc07b 100644 --- a/Source/Utility/PluginParameter.h +++ b/Source/Utility/PluginParameter.h @@ -29,7 +29,6 @@ class PlugDataParameter : public RangedAudioParameter { , enabled(enabled) , mode(Float) { - value = range.convertFrom0to1(getDefaultValue()); } From c86297943bd5cbfa1ab6595e6a2df51004d36010 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 27 Jan 2024 14:28:50 +0100 Subject: [PATCH 0161/1030] Fixed hanging keys for keyboard --- Source/Objects/KeyboardObject.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Source/Objects/KeyboardObject.h b/Source/Objects/KeyboardObject.h index 31534fbbdf..4b12778158 100644 --- a/Source/Objects/KeyboardObject.h +++ b/Source/Objects/KeyboardObject.h @@ -5,7 +5,7 @@ */ // Inherit to customise drawing -class MIDIKeyboard : public MidiKeyboardComponent { +class MIDIKeyboard : public MidiKeyboardState, public MidiKeyboardComponent { Object* object; @@ -17,9 +17,9 @@ class MIDIKeyboard : public MidiKeyboardComponent { std::set toggledKeys; std::function noteOn; std::function noteOff; - - MIDIKeyboard(Object* parent, MidiKeyboardState& stateToUse, Orientation orientationToUse) - : MidiKeyboardComponent(stateToUse, orientationToUse) + + MIDIKeyboard(Object* parent) + : MidiKeyboardComponent(*this, MidiKeyboardComponent::horizontalKeyboard) , object(parent) { // Make sure nothing is drawn outside of our custom draw functions @@ -213,14 +213,13 @@ class KeyboardObject final : public ObjectBase Value toggleMode = SynchronousValue(); Value sizeProperty = SynchronousValue(); - MidiKeyboardState state; MIDIKeyboard keyboard; int keyRatio = 5; public: KeyboardObject(pd::WeakReference ptr, Object* object) : ObjectBase(ptr, object) - , keyboard(object, state, MidiKeyboardComponent::horizontalKeyboard) + , keyboard(object) { keyboard.setMidiChannel(1); keyboard.setScrollButtonsVisible(false); @@ -395,11 +394,11 @@ class KeyboardObject final : public ObjectBase if (auto obj = ptr.get()) { for (int i = keyboard.getRangeStart(); i < keyboard.getRangeEnd(); i++) { - if (obj->x_tgl_notes[i] && !(state.isNoteOn(2, i) && state.isNoteOn(1, i))) { - state.noteOn(2, i, 1.0f); + if (obj->x_tgl_notes[i] && !keyboard.heldKeys.contains(i)) { + keyboard.heldKeys.insert(i); } - if (!obj->x_tgl_notes[i] && !(state.isNoteOn(2, i) && state.isNoteOn(1, i))) { - state.noteOff(2, i, 1.0f); + if (!obj->x_tgl_notes[i] && keyboard.heldKeys.contains(i)) { + keyboard.heldKeys.erase(i); } } } @@ -431,8 +430,9 @@ class KeyboardObject final : public ObjectBase auto elseKeyboard = ptr.get(); switch (symbol) { - case hash("float"): { - noteOn(atoms[0].getFloat(), elseKeyboard->x_vel_in > 0); + case hash("float"): { + auto note = std::clamp(atoms[0].getFloat(), 0, 128); + noteOn(atoms[0].getFloat(), elseKeyboard->x_tgl_notes[note]); break; } case hash("list"): { From b51a850b28149c9013dad0021a88087b3ef2f01b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 27 Jan 2024 19:00:42 +0100 Subject: [PATCH 0162/1030] Make sure iOS is allowed to read/write to files selected from file browser --- Source/Components/PropertiesPanel.h | 3 ++- Source/Dialogs/Dialogs.cpp | 18 +++++---------- Source/Dialogs/Dialogs.h | 4 ++-- Source/Dialogs/MainMenu.h | 2 +- Source/Dialogs/PatchStorage.h | 2 +- Source/Dialogs/PathsAndLibrariesPanel.h | 6 +++-- Source/Dialogs/ThemePanel.h | 6 +++-- Source/Heavy/DaisyExporter.h | 6 +++-- Source/Heavy/ExporterBase.h | 6 +++-- Source/Object.cpp | 2 +- Source/Objects/CloneObject.h | 2 +- Source/Objects/ImplementationBase.cpp | 2 +- Source/Objects/ObjectBase.cpp | 6 +++-- Source/Objects/PdTildeObject.h | 3 ++- Source/Pd/Instance.cpp | 4 ++-- Source/Pd/Patch.cpp | 23 ++++++++++--------- Source/Pd/Patch.h | 5 +++-- Source/PluginEditor.cpp | 14 +++++++----- Source/PluginProcessor.cpp | 25 ++++++++++++++------- Source/PluginProcessor.h | 2 +- Source/Sidebar/DocumentationBrowser.h | 8 +++---- Source/Standalone/PlugDataApp.cpp | 6 ++--- Source/Tabbar/Tabbar.cpp | 2 +- Source/Utility/Autosave.h | 2 +- Source/Utility/FileSystemWatcher.cxx | 2 +- Source/Utility/OSUtils.h | 18 --------------- Source/Utility/OSUtils.mm | 30 ------------------------- 27 files changed, 90 insertions(+), 119 deletions(-) diff --git a/Source/Components/PropertiesPanel.h b/Source/Components/PropertiesPanel.h index ed556c865e..56b0e633de 100644 --- a/Source/Components/PropertiesPanel.h +++ b/Source/Components/PropertiesPanel.h @@ -834,7 +834,8 @@ class PropertiesPanel : public Component { addAndMakeVisible(browseButton); browseButton.onClick = [this]() { - Dialogs::showSaveDialog([this](File& result) { + Dialogs::showSaveDialog([this](URL url) { + auto result = url.getLocalFile(); if (result.getParentDirectory().exists()) { label.setText(result.getFullPathName(), sendNotification); } diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index e99b66301e..37bbb60ce5 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -670,7 +670,7 @@ void Dialogs::showObjectMenu(PluginEditor* editor, Component* target) AddObjectMenu::show(editor, editor->getLocalArea(target, target->getLocalBounds())); } -void Dialogs::showOpenDialog(std::function callback, bool canSelectFiles, bool canSelectDirectories, String const& extension, String const& lastFileId) +void Dialogs::showOpenDialog(std::function callback, bool canSelectFiles, bool canSelectDirectories, String const& extension, String const& lastFileId) { bool nativeDialog = SettingsFile::getInstance()->wantsNativeDialog(); auto initialFile = lastFileId.isNotEmpty() ? SettingsFile::getInstance()->getLastBrowserPathForId(lastFileId) : ProjectInfo::appDataDir; @@ -692,18 +692,15 @@ void Dialogs::showOpenDialog(std::function callback, bool canSelect fileChooser->launchAsync(openChooserFlags, [callback, lastFileId](FileChooser const& fileChooser) { auto result = fileChooser.getResult(); - -#if JUCE_IOS - OSUtils::iOSScopedResourceAccess scopedSecurityResource(result); -#endif + auto lastDir = result.isDirectory() ? result : result.getParentDirectory(); SettingsFile::getInstance()->setLastBrowserPathForId(lastFileId, lastDir); - callback(result); + callback(fileChooser.getURLResult()); Dialogs::fileChooser = nullptr; }); } -void Dialogs::showSaveDialog(std::function callback, String const& extension, String const& lastFileId, bool directoryMode) +void Dialogs::showSaveDialog(std::function callback, String const& extension, String const& lastFileId, bool directoryMode) { bool nativeDialog = SettingsFile::getInstance()->wantsNativeDialog(); auto initialFile = lastFileId.isNotEmpty() ? SettingsFile::getInstance()->getLastBrowserPathForId(lastFileId) : ProjectInfo::appDataDir; @@ -726,15 +723,10 @@ void Dialogs::showSaveDialog(std::function callback, String const& fileChooser->launchAsync(saveChooserFlags, [callback, lastFileId](FileChooser const& fileChooser) { auto result = fileChooser.getResult(); - auto urlResult = fileChooser.getURLResults(); -#if JUCE_IOS - OSUtils::iOSScopedResourceAccess scopedSecurityResource(result); -#endif - auto parentDirectory = result.getParentDirectory(); if (parentDirectory.exists()) { SettingsFile::getInstance()->setLastBrowserPathForId(lastFileId, parentDirectory); - callback(result); + callback(fileChooser.getURLResult()); Dialogs::fileChooser = nullptr; } diff --git a/Source/Dialogs/Dialogs.h b/Source/Dialogs/Dialogs.h index 07b71ee4e1..893e50e3bc 100644 --- a/Source/Dialogs/Dialogs.h +++ b/Source/Dialogs/Dialogs.h @@ -204,9 +204,9 @@ struct Dialogs { static PopupMenu createObjectMenu(PluginEditor* parent); - static void showOpenDialog(std::function callback, bool canSelectFiles, bool canSelectDirectories, String const& lastFileId, String const& extension); + static void showOpenDialog(std::function callback, bool canSelectFiles, bool canSelectDirectories, String const& lastFileId, String const& extension); - static void showSaveDialog(std::function callback, String const& extension, String const& lastFileId, bool directoryMode = false); + static void showSaveDialog(std::function callback, String const& extension, String const& lastFileId, bool directoryMode = false); static inline std::unique_ptr fileChooser = nullptr; }; diff --git a/Source/Dialogs/MainMenu.h b/Source/Dialogs/MainMenu.h index 2c54951c33..92d2c0a15a 100644 --- a/Source/Dialogs/MainMenu.h +++ b/Source/Dialogs/MainMenu.h @@ -35,7 +35,7 @@ class MainMenu : public PopupMenu { auto path = File(recentlyOpenedTree.getChild(i).getProperty("Path").toString()); recentlyOpened->addItem(path.getFileName(), [path, editor]() mutable { editor->autosave->checkForMoreRecentAutosave(path, [editor, path]() { - editor->pd->loadPatch(path, editor, -1); + editor->pd->loadPatch(URL(path), editor, -1); SettingsFile::getInstance()->addToRecentlyOpened(path); }); }); diff --git a/Source/Dialogs/PatchStorage.h b/Source/Dialogs/PatchStorage.h index e5bfe9742a..956ea7e0ee 100644 --- a/Source/Dialogs/PatchStorage.h +++ b/Source/Dialogs/PatchStorage.h @@ -21,7 +21,7 @@ class OnlineImage : public Component imageDownloadPool.removeJob(this, true, -1); } - void setImageURL(const URL& url) + void setImageURL(const URL url) { downloadedImage = Image(); diff --git a/Source/Dialogs/PathsAndLibrariesPanel.h b/Source/Dialogs/PathsAndLibrariesPanel.h index efb735e27c..809da3eeb0 100644 --- a/Source/Dialogs/PathsAndLibrariesPanel.h +++ b/Source/Dialogs/PathsAndLibrariesPanel.h @@ -353,7 +353,8 @@ class SearchPathPanel : public Component if (start == File()) start = File::getCurrentWorkingDirectory(); - Dialogs::showOpenDialog([this](File& result) { + Dialogs::showOpenDialog([this](URL url) { + auto result = url.getLocalFile(); if (result.exists()) { paths.addIfNotAlreadyThere(result.getFullPathName(), listBox.getSelectedRow()); internalChange(); @@ -375,7 +376,8 @@ class SearchPathPanel : public Component auto row = listBox.getSelectedRow(); - Dialogs::showOpenDialog([this, row](File& result) { + Dialogs::showOpenDialog([this, row](URL url) { + auto result = url.getLocalFile(); if (result.exists()) { paths.remove(row); paths.addIfNotAlreadyThere(result.getFullPathName(), row); diff --git a/Source/Dialogs/ThemePanel.h b/Source/Dialogs/ThemePanel.h index d00954e55a..c27600abef 100644 --- a/Source/Dialogs/ThemePanel.h +++ b/Source/Dialogs/ThemePanel.h @@ -325,7 +325,8 @@ class ThemePanel : public SettingsDialogPanel Icons::New, "New theme..."); loadButton = new PropertiesPanel::ActionComponent([this]() { - Dialogs::showOpenDialog([this](File& result) { + Dialogs::showOpenDialog([this](URL url) { + auto result = url.getLocalFile(); if (!result.exists()) return; @@ -373,7 +374,8 @@ class ThemePanel : public SettingsDialogPanel auto themeXml = themeTree.toXmlString(); - Dialogs::showSaveDialog([themeXml](File& result) { + Dialogs::showSaveDialog([themeXml](URL url) { + auto result = url.getLocalFile(); if (result.getParentDirectory().exists()) { result.replaceWithText(themeXml); } diff --git a/Source/Heavy/DaisyExporter.h b/Source/Heavy/DaisyExporter.h index e9ec479ddb..010e059451 100644 --- a/Source/Heavy/DaisyExporter.h +++ b/Source/Heavy/DaisyExporter.h @@ -150,7 +150,8 @@ class DaisyExporter : public ExporterBase { // Custom board option if (idx == 9 && !dontOpenFileChooser) { - Dialogs::showOpenDialog([this](File& result) { + Dialogs::showOpenDialog([this](URL url) { + auto result = url.getLocalFile(); if (result.existsAsFile()) { customBoardDefinition = result; } else { @@ -166,7 +167,8 @@ class DaisyExporter : public ExporterBase { // Custom linker option if (idx == 4 && !dontOpenFileChooser) { - Dialogs::showOpenDialog([this](File& result) { + Dialogs::showOpenDialog([this](URL url) { + auto result = url.getLocalFile(); if (result.existsAsFile()) { customLinker = result; } else { diff --git a/Source/Heavy/ExporterBase.h b/Source/Heavy/ExporterBase.h index b1c45b2445..26bb1d296a 100644 --- a/Source/Heavy/ExporterBase.h +++ b/Source/Heavy/ExporterBase.h @@ -97,7 +97,8 @@ struct ExporterBase : public Component } exportButton.onClick = [this]() { - Dialogs::showSaveDialog([this](File& result) { + Dialogs::showSaveDialog([this](URL url) { + auto result = url.getLocalFile(); if (result.getParentDirectory().exists()) { startExport(result); } @@ -184,7 +185,8 @@ struct ExporterBase : public Component patchFile = openedPatchFile; validPatchSelected = true; } else if (idx == 2) { - Dialogs::showOpenDialog([this](File& result) { + Dialogs::showOpenDialog([this](URL url) { + auto result = url.getLocalFile(); if (result.existsAsFile()) { patchFile = result; validPatchSelected = true; diff --git a/Source/Object.cpp b/Source/Object.cpp index 4780705eda..94cee8d1be 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -1368,7 +1368,7 @@ void Object::openHelpPatch() const } cnv->pd->lockAudioThread(); - auto patchPtr = cnv->pd->loadPatch(file, cnv->editor, -1); + auto patchPtr = cnv->pd->loadPatch(URL(file), cnv->editor, -1); if(patchPtr) { if(auto patch = patchPtr->getPointer()) { patch->gl_edit = 0; diff --git a/Source/Objects/CloneObject.h b/Source/Objects/CloneObject.h index 992629ccfd..ad8bf4dc63 100644 --- a/Source/Objects/CloneObject.h +++ b/Source/Objects/CloneObject.h @@ -111,7 +111,7 @@ class CloneObject final : public TextBase { auto newPatch = cnv->editor->pd->patches.getLast(); auto* newCanvas = cnv->editor->canvases.add(new Canvas(cnv->editor, *newPatch, nullptr)); - newPatch->setCurrentFile(path); + newPatch->setCurrentFile(URL(path)); cnv->editor->addTab(newCanvas); } diff --git a/Source/Objects/ImplementationBase.cpp b/Source/Objects/ImplementationBase.cpp index 9ca5df1cff..c8de5e572f 100644 --- a/Source/Objects/ImplementationBase.cpp +++ b/Source/Objects/ImplementationBase.cpp @@ -131,7 +131,7 @@ void ImplementationBase::openSubpatch(pd::Patch* subpatch) if (canvas_isabstraction(glist.get())) { auto path = File(String::fromUTF8(canvas_getdir(glist.get())->s_name)).getChildFile(String::fromUTF8(glist->gl_name->s_name)).withFileExtension("pd"); - subpatch->setCurrentFile(path); + subpatch->setCurrentFile(URL(path)); } pd->patches.add(subpatch); } else { diff --git a/Source/Objects/ObjectBase.cpp b/Source/Objects/ObjectBase.cpp index ccc40a6a71..8f6f16e991 100644 --- a/Source/Objects/ObjectBase.cpp +++ b/Source/Objects/ObjectBase.cpp @@ -360,8 +360,10 @@ void ObjectBase::openSubpatch() cnv->editor->pd->patches.add(subpatch); auto newPatch = cnv->editor->pd->patches.getLast(); auto* newCanvas = cnv->editor->canvases.add(new Canvas(cnv->editor, *newPatch, nullptr)); - - newPatch->setCurrentFile(path); + + if(path.getFullPathName().isNotEmpty()) { + newPatch->setCurrentFile(URL(path)); + } cnv->editor->addTab(newCanvas); } diff --git a/Source/Objects/PdTildeObject.h b/Source/Objects/PdTildeObject.h index f9ff4d4983..44065a2896 100644 --- a/Source/Objects/PdTildeObject.h +++ b/Source/Objects/PdTildeObject.h @@ -29,7 +29,8 @@ class PdTildeObject final : public TextBase { { if (!pdLocation.exists()) { - Dialogs::showOpenDialog([this](File& result) { + Dialogs::showOpenDialog([this](URL url) { + auto result = url.getLocalFile(); if (!result.exists()) return; diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 3e53086f4e..35c7cb8c9d 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -731,8 +731,8 @@ void Instance::createPanel(int type, char const* snd, char const* location, char } else { MessageManager::callAsync( [this, obj, defaultFile, callback = String(callbackName)]() mutable { - Dialogs::showSaveDialog([this, obj, callback](File& result) { - auto pathName = result.getFullPathName(); + Dialogs::showSaveDialog([this, obj, callback](URL result) { + auto pathName = result.toString(false); const auto* path = pathName.toRawUTF8(); t_atom argv[1]; diff --git a/Source/Pd/Patch.cpp b/Source/Pd/Patch.cpp index 29a4f7d753..562299feaa 100644 --- a/Source/Pd/Patch.cpp +++ b/Source/Pd/Patch.cpp @@ -86,9 +86,9 @@ bool Patch::canRedo() const return canPatchRedo.load(); } -void Patch::savePatch(File const& location) +void Patch::savePatch(URL const& locationURL) { - + auto location = locationURL.getLocalFile(); String fullPathname = location.getParentDirectory().getFullPathName(); String filename = location.withFileExtension(".pd").getFileName(); @@ -101,9 +101,14 @@ void Patch::savePatch(File const& location) canvas_dirty(patch.get(), 0); #if JUCE_IOS + auto patchText = getCanvasContent(); + auto outputStream = locationURL.createOutputStream(); + // on iOS, saving with pd's normal method doesn't work - // However, there are also rare cases where writing files like this doens't work as well - location.replaceWithText(getCanvasContent()); + // we need to use an outputstream on a URL + outputStream->write(patchText.toRawUTF8(), patchText.getNumBytesAsUTF8()); + outputStream->flush(); + instance->logMessage("saved to: " + location.getFullPathName()); #else pd::Interface::saveToFile(patch.get(), file, dir); @@ -113,6 +118,7 @@ void Patch::savePatch(File const& location) } currentFile = location; + currentURL = locationURL; } t_glist* Patch::getRoot() @@ -170,10 +176,6 @@ void Patch::savePatch() setTitle(filename); untitledPatchNum = 0; canvas_dirty(patch.get(), 0); - -#if JUCE_IOS - OSUtils::iOSScopedResourceAccess scopedSecurityResource(currentFile); -#endif pd::Interface::saveToFile(patch.get(), file, dir); } @@ -718,9 +720,10 @@ File Patch::getPatchFile() const return File(); } -void Patch::setCurrentFile(File newFile) +void Patch::setCurrentFile(URL const& newURL) { - currentFile = std::move(newFile); + currentFile = newURL.getLocalFile(); + currentURL = newURL; } String Patch::getCanvasContent() diff --git a/Source/Pd/Patch.h b/Source/Pd/Patch.h index 9e6fe35fcd..cb500feedf 100644 --- a/Source/Pd/Patch.h +++ b/Source/Pd/Patch.h @@ -80,13 +80,13 @@ class Patch : public ReferenceCountedObject { bool canUndo() const; bool canRedo() const; - void savePatch(File const& location); + void savePatch(URL const& location); void savePatch(); File getCurrentFile() const; File getPatchFile() const; - void setCurrentFile(File newFile); + void setCurrentFile(URL const& newFile); void updateUndoRedoState(); @@ -135,6 +135,7 @@ class Patch : public ReferenceCountedObject { std::atomic isPatchDirty; File currentFile; + URL currentURL; // We hold a URL to the patch as well, which is needed for file IO on iOS WeakReference ptr; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index df2e4db1c1..ba8405fd46 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -584,7 +584,7 @@ void PluginEditor::filesDropped(StringArray const& files, int x, int y) if (file.exists() && file.hasFileExtension("pd")) { openedPdFiles = true; autosave->checkForMoreRecentAutosave(file, [this, file]() { - pd->loadPatch(file, this, -1); + pd->loadPatch(URL(file), this, -1); SettingsFile::getInstance()->addToRecentlyOpened(file); pd->titleChanged(); }); @@ -680,11 +680,12 @@ void PluginEditor::newProject() void PluginEditor::openProject() { - Dialogs::showOpenDialog([this](File& result) { + Dialogs::showOpenDialog([this](URL resultURL) { + auto result = resultURL.getLocalFile(); if (result.exists() && result.getFileExtension().equalsIgnoreCase(".pd")) { - autosave->checkForMoreRecentAutosave(result, [this, result]() { - pd->loadPatch(result, this, -1); + autosave->checkForMoreRecentAutosave(result, [this, result, resultURL]() { + pd->loadPatch(resultURL, this, -1); SettingsFile::getInstance()->addToRecentlyOpened(result); pd->titleChanged(); }); @@ -695,13 +696,14 @@ void PluginEditor::openProject() void PluginEditor::saveProjectAs(std::function const& nestedCallback) { - Dialogs::showSaveDialog([this, nestedCallback](File& result) mutable { + Dialogs::showSaveDialog([this, nestedCallback](URL resultURL) mutable { + auto result = resultURL.getLocalFile(); if (result.getFullPathName().isNotEmpty()) { if (result.exists()) result.deleteFile(); result = result.withFileExtension(".pd"); - getCurrentCanvas()->patch.savePatch(result); + getCurrentCanvas()->patch.savePatch(resultURL); SettingsFile::getInstance()->addToRecentlyOpened(result); pd->titleChanged(); } diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 6a0291fdd2..0f46785a82 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -45,6 +45,10 @@ bool gemWinUnsetCurrent(); AudioProcessor::BusesProperties PluginProcessor::buildBusesProperties() { #if JUCE_IOS + + if(ProjectInfo::isStandalone) { + return BusesProperties().withOutput ("Output", AudioChannelSet::stereo(), true).withInput ("Input", AudioChannelSet::mono(), true); + } // If you intend to build AUv3 on macOS, you'll also need these if(ProjectInfo::isFx) { return BusesProperties().withOutput ("Output", AudioChannelSet::stereo(), true).withInput ("Input", AudioChannelSet::stereo(), true); @@ -1098,7 +1102,7 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) auto openPatch = [this](String const& content, File const& location, bool pluginMode = false, int splitIndex = 0) { if (location.getFullPathName().isNotEmpty() && location.existsAsFile()) { - auto patch = loadPatch(location, getEditors()[0], splitIndex); + auto patch = loadPatch(URL(location), getEditors()[0], splitIndex); if (patch) { patch->setTitle(location.getFileName()); patch->openInPluginMode = pluginMode; @@ -1114,7 +1118,7 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) patch->openInPluginMode = pluginMode; patch->splitViewIndex = splitIndex; } else if (patch && location.existsAsFile()) { - patch->setCurrentFile(location); + patch->setCurrentFile(URL(location)); patch->setTitle(location.getFileName()); patch->openInPluginMode = pluginMode; patch->splitViewIndex = splitIndex; @@ -1216,8 +1220,9 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) } } -pd::Patch::Ptr PluginProcessor::loadPatch(File const& patchFile, PluginEditor* editor, int splitIndex) +pd::Patch::Ptr PluginProcessor::loadPatch(URL const& patchURL, PluginEditor* editor, int splitIndex) { + auto patchFile = patchURL.getLocalFile(); // First, check if patch is already opened for (auto const& patch : patches) { if (patch->getCurrentFile() == patchFile) { @@ -1244,7 +1249,10 @@ pd::Patch::Ptr PluginProcessor::loadPatch(File const& patchFile, PluginEditor* e #if JUCE_IOS auto tempFile = File::createTempFile(".pd"); - tempFile.replaceWithText(patchFile.loadFileAsString()); + auto patchContent = patchFile.loadFileAsString(); + + auto inputStream = patchURL.createInputStream (URL::InputStreamOptions (URL::ParameterHandling::inAddress)); + tempFile.appendText(inputStream->readEntireStreamAsString()); auto newPatch = openPatch(tempFile); if(newPatch) @@ -1257,6 +1265,7 @@ pd::Patch::Ptr PluginProcessor::loadPatch(File const& patchFile, PluginEditor* e auto const* file = filename.toRawUTF8(); canvas_rename(patch.get(), gensym(file), gensym(dir)); newPatch->setTitle(filename); + newPatch->setCurrentFile(patchURL); } } #else @@ -1287,7 +1296,7 @@ pd::Patch::Ptr PluginProcessor::loadPatch(File const& patchFile, PluginEditor* e _editor->addTab(cnv, splitIndex); }); } - patch->setCurrentFile(patchFile); + patch->setCurrentFile(URL(patchFile)); return patch; } @@ -1300,10 +1309,10 @@ pd::Patch::Ptr PluginProcessor::loadPatch(String patchText, PluginEditor* editor auto patchFile = File::createTempFile(".pd"); patchFile.replaceWithText(patchText); - auto patch = loadPatch(patchFile, editor, splitIndex); + auto patch = loadPatch(URL(patchFile), editor, splitIndex); // Set to unknown file when loading temp patch - patch->setCurrentFile(File()); + patch->setCurrentFile(URL("file://")); return patch; } @@ -1444,7 +1453,7 @@ void PluginProcessor::receiveSysMessage(String const& selector, std::vector extraData; pd::Patch::Ptr loadPatch(String patch, PluginEditor* editor, int splitIndex = 0); - pd::Patch::Ptr loadPatch(File const& patch, PluginEditor* editor, int splitIndex = 0); + pd::Patch::Ptr loadPatch(URL const& patchURL, PluginEditor* editor, int splitIndex = 0); void titleChanged() override; diff --git a/Source/Sidebar/DocumentationBrowser.h b/Source/Sidebar/DocumentationBrowser.h index 5085f33f9f..e9aa94f813 100644 --- a/Source/Sidebar/DocumentationBrowser.h +++ b/Source/Sidebar/DocumentationBrowser.h @@ -108,7 +108,7 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri fileList.onClick = [this](ValueTree& tree){ auto file = File(tree.getProperty("Path").toString()); if (file.existsAsFile() && file.hasFileExtension("pd")) { - pd->loadPatch(file, findParentComponentOfClass()); + pd->loadPatch(URL(file), findParentComponentOfClass()); SettingsFile::getInstance()->addToRecentlyOpened(file); } else if(file.isDirectory()) @@ -367,9 +367,9 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri auto* sidebar = getParentComponent(); auto bounds = editor->getLocalArea(sidebar, settingsCalloutButton->getBounds()); auto openFolderCallback = [this]() { - Dialogs::showOpenDialog([this](File& result) { - if (result.isDirectory()) { - pd->settingsFile->setProperty("browser_path", result.getFullPathName()); + Dialogs::showOpenDialog([this](URL result) { + if (result.getLocalFile().isDirectory()) { + pd->settingsFile->setProperty("browser_path", result.toString(false)); updateContent(); } }, diff --git a/Source/Standalone/PlugDataApp.cpp b/Source/Standalone/PlugDataApp.cpp index 230aed475e..5e888af2b2 100644 --- a/Source/Standalone/PlugDataApp.cpp +++ b/Source/Standalone/PlugDataApp.cpp @@ -90,7 +90,7 @@ class PlugDataApp : public JUCEApplication { auto* editor = dynamic_cast(mainWindow->mainComponent->getEditor()); if (pd && editor && file.existsAsFile()) { auto* editor = dynamic_cast(mainWindow->mainComponent->getEditor()); - pd->loadPatch(file, editor); + pd->loadPatch(URL(file), editor); SettingsFile::getInstance()->addToRecentlyOpened(file); } } @@ -185,7 +185,7 @@ class PlugDataApp : public JUCEApplication { auto* editor = dynamic_cast(mainWindow->mainComponent->getEditor()); if (auto* pd = dynamic_cast(pluginHolder->processor.get())) { - pd->loadPatch(toOpen, editor); + pd->loadPatch(URL(toOpen), editor); SettingsFile::getInstance()->addToRecentlyOpened(toOpen); openedPatches.add(toOpen.getFullPathName()); } @@ -205,7 +205,7 @@ class PlugDataApp : public JUCEApplication { if (toOpen.existsAsFile() && toOpen.hasFileExtension("pd") && !openedPatches.contains(toOpen.getFullPathName())) { auto* pd = dynamic_cast(pluginHolder->processor.get()); auto* editor = dynamic_cast(mainWindow->mainComponent->getEditor()); - pd->loadPatch(toOpen, editor); + pd->loadPatch(URL(toOpen), editor); SettingsFile::getInstance()->addToRecentlyOpened(toOpen); } } diff --git a/Source/Tabbar/Tabbar.cpp b/Source/Tabbar/Tabbar.cpp index f5ad811f63..a47fe42ff6 100644 --- a/Source/Tabbar/Tabbar.cpp +++ b/Source/Tabbar/Tabbar.cpp @@ -597,7 +597,7 @@ void TabComponent::changeCallback(int newCurrentTabIndex, String const& newTabNa void TabComponent::openProjectFile(File& patchFile) { editor->autosave->checkForMoreRecentAutosave(patchFile, [this, patchFile]() { - editor->pd->loadPatch(patchFile, editor); + editor->pd->loadPatch(URL(patchFile), editor); SettingsFile::getInstance()->addToRecentlyOpened(patchFile); editor->pd->titleChanged(); }); diff --git a/Source/Utility/Autosave.h b/Source/Utility/Autosave.h index b7882660d2..989f2aed2b 100644 --- a/Source/Utility/Autosave.h +++ b/Source/Utility/Autosave.h @@ -189,7 +189,7 @@ class AutosaveHistoryComponent : public Component { Base64::convertFromBase64(ostream, patch); auto patch = editor->pd->loadPatch(String::fromUTF8(static_cast(ostream.getData()), ostream.getDataSize()), editor); patch->setTitle(patchPath.fromLastOccurrenceOf("/", false, false)); - patch->setCurrentFile(File(patchPath)); + patch->setCurrentFile(URL(patchPath)); MessageManager::callAsync([editor]() { // Close the whole chain of dialogs diff --git a/Source/Utility/FileSystemWatcher.cxx b/Source/Utility/FileSystemWatcher.cxx index e08a93b396..5f2e8fba7d 100644 --- a/Source/Utility/FileSystemWatcher.cxx +++ b/Source/Utility/FileSystemWatcher.cxx @@ -380,7 +380,7 @@ FileSystemWatcher::~FileSystemWatcher() void FileSystemWatcher::addFolder (const File& folder) { // You can only listen to folders that exist - jassert (folder.isDirectory()); + //jassert (folder.isDirectory()); if ( ! getWatchedFolders().contains (folder)) watched.add (new Impl (*this, folder)); diff --git a/Source/Utility/OSUtils.h b/Source/Utility/OSUtils.h index a61197e82f..d74df25aa6 100644 --- a/Source/Utility/OSUtils.h +++ b/Source/Utility/OSUtils.h @@ -87,23 +87,5 @@ struct OSUtils { static void showMobileMainMenu(juce::ComponentPeer* peer, std::function callback); static void showMobileCanvasMenu(juce::ComponentPeer* peer, std::function callback); - static void startAccessingSecurityScopedResource(juce::File file); - static void stopAccessingSecurityScopedResource(juce::File file); - - struct iOSScopedResourceAccess - { - iOSScopedResourceAccess(const juce::File& file) : scopedAccessFile(file) - { - startAccessingSecurityScopedResource(scopedAccessFile); - } - - ~iOSScopedResourceAccess() - { - stopAccessingSecurityScopedResource(scopedAccessFile); - } - - juce::File scopedAccessFile; - }; - #endif }; diff --git a/Source/Utility/OSUtils.mm b/Source/Utility/OSUtils.mm index f235ddba98..770bd653cc 100644 --- a/Source/Utility/OSUtils.mm +++ b/Source/Utility/OSUtils.mm @@ -279,36 +279,6 @@ - (void)scrollEventOccurred:(UIPanGestureRecognizer*)gesture { return [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad; } -void OSUtils::startAccessingSecurityScopedResource(juce::File file) -{ - // Convert JUCE File to NSString - NSString* filePath = [NSString stringWithUTF8String:file.getFullPathName().toRawUTF8()]; - - // Create NSURL from file path - NSURL* fileURL = [NSURL fileURLWithPath:filePath]; - - // Start accessing security-scoped resource - NSError* error = nil; - BOOL success = [fileURL startAccessingSecurityScopedResource]; - - if (!success) { - // Handle error if needed - NSLog(@"Error starting access to security-scoped resource: %@", error); - } -} - -void OSUtils::stopAccessingSecurityScopedResource(juce::File file) -{ - // Convert JUCE File to NSString - NSString* filePath = [NSString stringWithUTF8String:file.getFullPathName().toRawUTF8()]; - - // Create NSURL from file path - NSURL* fileURL = [NSURL fileURLWithPath:filePath]; - - // Stop accessing security-scoped resource - [fileURL stopAccessingSecurityScopedResource]; -} - void OSUtils::showMobileMainMenu(juce::ComponentPeer* peer, std::function callback) { auto* view = (UIView*)peer->getNativeHandle(); From 521b8062a03ce8dad546fd8b9147f2e58b22fa50 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 27 Jan 2024 19:19:04 +0100 Subject: [PATCH 0163/1030] Don't show recently opened on iOS --- Source/Tabbar/Tabbar.cpp | 5 +++++ Source/Utility/OSUtils.mm | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Source/Tabbar/Tabbar.cpp b/Source/Tabbar/Tabbar.cpp index a47fe42ff6..d3780d0f65 100644 --- a/Source/Tabbar/Tabbar.cpp +++ b/Source/Tabbar/Tabbar.cpp @@ -116,7 +116,12 @@ class WelcomePanel : public Component { { addAndMakeVisible(newButton); addAndMakeVisible(openButton); + + // Opening files from recently opened list will likely fail, + // since the file browser is what grants us the permission to read/write files +#if !JUCE_IOS addAndMakeVisible(recentlyOpened); +#endif } void resized() override diff --git a/Source/Utility/OSUtils.mm b/Source/Utility/OSUtils.mm index 770bd653cc..218e847ac5 100644 --- a/Source/Utility/OSUtils.mm +++ b/Source/Utility/OSUtils.mm @@ -154,20 +154,14 @@ - (instancetype)initWithComponentPeer:(juce::ComponentPeer*)componentPeer scroll lastScale = 1.0; UIPinchGestureRecognizer* pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchEventOccurred:)]; - //[pinchGesture setCancelsTouchesInView: true]; - //[pinchGesture setDelaysTouchesBegan: true]; [view addGestureRecognizer:pinchGesture]; UILongPressGestureRecognizer* longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressEventOccurred:)]; - //[longPressGesture setCancelsTouchesInView: true]; - //[longPressGesture setDelaysTouchesBegan: true]; [view addGestureRecognizer:longPressGesture]; UIPanGestureRecognizer* panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(scrollEventOccurred:)]; [panGesture setMaximumNumberOfTouches: 2]; [panGesture setMinimumNumberOfTouches: 2]; - //[panGesture setCancelsTouchesInView: true]; - //[panGesture setDelaysTouchesBegan: true]; [view addGestureRecognizer:panGesture]; return self; From 65de072efb50d8858bd7d4e9e26eae9472d82adc Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 28 Jan 2024 01:45:14 +0100 Subject: [PATCH 0164/1030] More iOS fixes --- Source/Components/PropertiesPanel.h | 2 +- Source/Dialogs/Dialogs.cpp | 11 ++++++----- Source/Dialogs/Dialogs.h | 4 ++-- Source/Dialogs/PathsAndLibrariesPanel.h | 4 ++-- Source/Dialogs/ThemePanel.h | 6 +++--- Source/Heavy/DaisyExporter.h | 4 ++-- Source/Heavy/ExporterBase.h | 4 ++-- Source/Objects/ObjectBase.cpp | 2 +- Source/Objects/ObjectBase.h | 2 +- Source/Objects/PdTildeObject.h | 2 +- Source/Objects/SliderObject.h | 14 +++++++++++++- Source/Pd/Instance.cpp | 10 +++++++++- Source/PluginEditor.cpp | 16 +++++++++------- Source/PluginProcessor.cpp | 2 ++ Source/Sidebar/DocumentationBrowser.h | 4 ++-- Source/Utility/SettingsFile.cpp | 5 ++++- 16 files changed, 60 insertions(+), 32 deletions(-) diff --git a/Source/Components/PropertiesPanel.h b/Source/Components/PropertiesPanel.h index 56b0e633de..0122055be8 100644 --- a/Source/Components/PropertiesPanel.h +++ b/Source/Components/PropertiesPanel.h @@ -840,7 +840,7 @@ class PropertiesPanel : public Component { label.setText(result.getFullPathName(), sendNotification); } }, - "", ""); + "", "", getTopLevelComponent()); }; } diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index 37bbb60ce5..000977adce 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -670,7 +670,7 @@ void Dialogs::showObjectMenu(PluginEditor* editor, Component* target) AddObjectMenu::show(editor, editor->getLocalArea(target, target->getLocalBounds())); } -void Dialogs::showOpenDialog(std::function callback, bool canSelectFiles, bool canSelectDirectories, String const& extension, String const& lastFileId) +void Dialogs::showOpenDialog(std::function callback, bool canSelectFiles, bool canSelectDirectories, String const& extension, String const& lastFileId, Component* parentComponent) { bool nativeDialog = SettingsFile::getInstance()->wantsNativeDialog(); auto initialFile = lastFileId.isNotEmpty() ? SettingsFile::getInstance()->getLastBrowserPathForId(lastFileId) : ProjectInfo::appDataDir; @@ -678,9 +678,9 @@ void Dialogs::showOpenDialog(std::function callback, bool canSelectFi initialFile = ProjectInfo::appDataDir; #if JUCE_IOS - fileChooser = std::make_unique("Choose file to open...", initialFile, "*", nativeDialog); + fileChooser = std::make_unique("Choose file to open...", initialFile, "*", nativeDialog, false, parentComponent); #else - fileChooser = std::make_unique("Choose file to open...", initialFile, extension, nativeDialog); + fileChooser = std::make_unique("Choose file to open...", initialFile, extension, nativeDialog, false, parentComponent); #endif auto openChooserFlags = FileBrowserComponent::openMode; @@ -689,6 +689,7 @@ void Dialogs::showOpenDialog(std::function callback, bool canSelectFi if (canSelectDirectories) openChooserFlags = static_cast(openChooserFlags | FileBrowserComponent::canSelectDirectories); + fileChooser->launchAsync(openChooserFlags, [callback, lastFileId](FileChooser const& fileChooser) { auto result = fileChooser.getResult(); @@ -700,14 +701,14 @@ void Dialogs::showOpenDialog(std::function callback, bool canSelectFi }); } -void Dialogs::showSaveDialog(std::function callback, String const& extension, String const& lastFileId, bool directoryMode) +void Dialogs::showSaveDialog(std::function callback, String const& extension, String const& lastFileId, Component* parentComponent, bool directoryMode) { bool nativeDialog = SettingsFile::getInstance()->wantsNativeDialog(); auto initialFile = lastFileId.isNotEmpty() ? SettingsFile::getInstance()->getLastBrowserPathForId(lastFileId) : ProjectInfo::appDataDir; if (!initialFile.exists()) initialFile = ProjectInfo::appDataDir; - fileChooser = std::make_unique("Choose save location...", initialFile, extension, nativeDialog); + fileChooser = std::make_unique("Choose save location...", initialFile, extension, nativeDialog, false, parentComponent); auto saveChooserFlags = FileBrowserComponent::saveMode; diff --git a/Source/Dialogs/Dialogs.h b/Source/Dialogs/Dialogs.h index 893e50e3bc..6a08951f89 100644 --- a/Source/Dialogs/Dialogs.h +++ b/Source/Dialogs/Dialogs.h @@ -204,9 +204,9 @@ struct Dialogs { static PopupMenu createObjectMenu(PluginEditor* parent); - static void showOpenDialog(std::function callback, bool canSelectFiles, bool canSelectDirectories, String const& lastFileId, String const& extension); + static void showOpenDialog(std::function callback, bool canSelectFiles, bool canSelectDirectories, String const& lastFileId, String const& extension, Component* parentComponent); - static void showSaveDialog(std::function callback, String const& extension, String const& lastFileId, bool directoryMode = false); + static void showSaveDialog(std::function callback, String const& extension, String const& lastFileId, Component* parentComponent = nullptr, bool directoryMode = false); static inline std::unique_ptr fileChooser = nullptr; }; diff --git a/Source/Dialogs/PathsAndLibrariesPanel.h b/Source/Dialogs/PathsAndLibrariesPanel.h index 809da3eeb0..952c9cf96d 100644 --- a/Source/Dialogs/PathsAndLibrariesPanel.h +++ b/Source/Dialogs/PathsAndLibrariesPanel.h @@ -360,7 +360,7 @@ class SearchPathPanel : public Component internalChange(); } }, - false, true, "", "PathBrowser"); + false, true, "", "PathBrowser", getTopLevelComponent()); } void deleteSelected() @@ -384,7 +384,7 @@ class SearchPathPanel : public Component internalChange(); } }, - false, true, "", "PathBrowser"); + false, true, "", "PathBrowser", getTopLevelComponent()); internalChange(); } diff --git a/Source/Dialogs/ThemePanel.h b/Source/Dialogs/ThemePanel.h index c27600abef..2ed59558b7 100644 --- a/Source/Dialogs/ThemePanel.h +++ b/Source/Dialogs/ThemePanel.h @@ -351,7 +351,7 @@ class ThemePanel : public SettingsDialogPanel SettingsFile::getInstance()->getColourThemesTree().appendChild(themeTree, nullptr); updateSwatches(); }, - true, false, "*.plugdatatheme", "ThemeLocation"); + true, false, "*.plugdatatheme", "ThemeLocation", getTopLevelComponent()); }, Icons::Open, "Import theme..."); @@ -364,7 +364,7 @@ class ThemePanel : public SettingsDialogPanel menu.addItem(i + 1, allThemes[i]); } - menu.showMenuAsync(PopupMenu::Options().withMinimumWidth(100).withMaximumNumColumns(1).withTargetComponent(saveButton).withParentComponent(this), [allThemes](int result) { + menu.showMenuAsync(PopupMenu::Options().withMinimumWidth(100).withMaximumNumColumns(1).withTargetComponent(saveButton).withParentComponent(this), [this, allThemes](int result) { if (result < 1) return; @@ -380,7 +380,7 @@ class ThemePanel : public SettingsDialogPanel result.replaceWithText(themeXml); } }, - "*.plugdatatheme", "ThemeLocation"); + "*.plugdatatheme", "ThemeLocation", getTopLevelComponent()); }); }, Icons::Save, "Export theme..."); diff --git a/Source/Heavy/DaisyExporter.h b/Source/Heavy/DaisyExporter.h index 010e059451..1fe079ee39 100644 --- a/Source/Heavy/DaisyExporter.h +++ b/Source/Heavy/DaisyExporter.h @@ -158,7 +158,7 @@ class DaisyExporter : public ExporterBase { customBoardDefinition = File(); } }, - true, false, "*.json", "DaisyCustomBoard"); + true, false, "*.json", "DaisyCustomBoard", nullptr); } } @@ -175,7 +175,7 @@ class DaisyExporter : public ExporterBase { customLinker = File(); } }, - true, false, "*.lds", "DaisyCustomLinker"); + true, false, "*.lds", "DaisyCustomLinker", nullptr); } } } diff --git a/Source/Heavy/ExporterBase.h b/Source/Heavy/ExporterBase.h index 26bb1d296a..91adc2adc5 100644 --- a/Source/Heavy/ExporterBase.h +++ b/Source/Heavy/ExporterBase.h @@ -103,7 +103,7 @@ struct ExporterBase : public Component startExport(result); } }, - "", "HeavyExport", true); + "", "HeavyExport", nullptr, true); }; } @@ -196,7 +196,7 @@ struct ExporterBase : public Component validPatchSelected = false; } }, - true, false, "*.pd", "HeavyPatchLocation"); + true, false, "*.pd", "HeavyPatchLocation", nullptr); } } diff --git a/Source/Objects/ObjectBase.cpp b/Source/Objects/ObjectBase.cpp index 8f6f16e991..637297546e 100644 --- a/Source/Objects/ObjectBase.cpp +++ b/Source/Objects/ObjectBase.cpp @@ -196,7 +196,7 @@ void ObjectBase::initialise() void ObjectBase::objectMovedOrResized(bool resized) { auto objectBounds = object->getObjectBounds(); - + setParameterExcludingListener(positionParameter, Array { var(objectBounds.getX()), var(objectBounds.getY()) }, &objectSizeListener); if (resized) diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index c19fafebfc..ba5dbf864a 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -144,7 +144,7 @@ class ObjectBase : public Component virtual ObjectParameters getParameters(); virtual bool showParametersWhenSelected(); - void objectMovedOrResized(bool resized); + virtual void objectMovedOrResized(bool resized); virtual void updateSizeProperty() { } virtual void updateLabel() { } diff --git a/Source/Objects/PdTildeObject.h b/Source/Objects/PdTildeObject.h index 44065a2896..33a6462c03 100644 --- a/Source/Objects/PdTildeObject.h +++ b/Source/Objects/PdTildeObject.h @@ -58,7 +58,7 @@ class PdTildeObject final : public TextBase { pd->sendDirectMessage(pdTilde.get(), "pd~", { pd->generateSymbol("start") }); } }, - true, true, "", "LastPdLocation"); + true, true, "", "LastPdLocation", cnv->editor); } else { if (auto pdTilde = ptr.get()) { auto pdPath = pdLocation.getFullPathName(); diff --git a/Source/Objects/SliderObject.h b/Source/Objects/SliderObject.h index 0fc830076f..a1c8fdf794 100644 --- a/Source/Objects/SliderObject.h +++ b/Source/Objects/SliderObject.h @@ -198,6 +198,18 @@ class SliderObject : public ObjectBase { { iemHelper.setPdBounds(b.reduced(2, 0).withTrimmedLeft(1)); } + + void objectMovedOrResized(bool resized) override + { + auto objectBounds = object->getObjectBounds().reduced(2, 0).withTrimmedLeft(1); + + setParameterExcludingListener(positionParameter, Array { var(objectBounds.getX()), var(objectBounds.getY()) }, &objectSizeListener); + + if (resized) + updateSizeProperty(); + + updateLabel(); + } void updateRange() { @@ -301,7 +313,7 @@ class SliderObject : public ObjectBase { void updateSizeProperty() override { - setPdBounds(object->getObjectBounds()); + //setPdBounds(object->getObjectBounds()); if (auto iem = ptr.get()) { setParameterExcludingListener(sizeProperty, Array { var(iem->x_w), var(iem->x_h) }); diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 35c7cb8c9d..c480c19ed0 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -731,6 +731,13 @@ void Instance::createPanel(int type, char const* snd, char const* location, char } else { MessageManager::callAsync( [this, obj, defaultFile, callback = String(callbackName)]() mutable { + +#if JUCE_IOS + Component* dialogParent = dynamic_cast(this)->getActiveEditor(); +#else + Component* dialogParent = nullptr; +#endif + Dialogs::showSaveDialog([this, obj, callback](URL result) { auto pathName = result.toString(false); const auto* path = pathName.toRawUTF8(); @@ -742,7 +749,8 @@ void Instance::createPanel(int type, char const* snd, char const* location, char pd_typedmess(obj, generateSymbol(callback), 1, argv); unlockAudioThread(); }, - "", "openpanel"); + "", "openpanel", dialogParent); + }); } } diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index ba8405fd46..9647d88060 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -87,18 +87,20 @@ PluginEditor::PluginEditor(PluginProcessor& p) return true; }) { +#if JUCE_IOS + //constrainer.setMinimumSize(100, 100); + //pluginConstrainer.setMinimumSize(100, 100); + //setResizable(true, false); +#else // if we are inside a DAW / host set up the border resizer now if (!ProjectInfo::isStandalone) { // NEVER touch pluginConstrainer outside of plugin mode! pluginConstrainer.setMinimumSize(850, 650); setUseBorderResizer(true); } else { -#if JUCE_IOS - constrainer.setMinimumSize(250, 250); -#else constrainer.setMinimumSize(850, 650); -#endif } +#endif mainMenuButton.setButtonText(Icons::Menu); undoButton.setButtonText(Icons::Undo); @@ -230,7 +232,7 @@ PluginEditor::PluginEditor(PluginProcessor& p) sidebar->setSize(250, pd->lastUIHeight - statusbar->getHeight()); setSize(pd->lastUIWidth, pd->lastUIHeight); - + sidebar->toFront(false); // Make sure existing console messages are processed @@ -691,7 +693,7 @@ void PluginEditor::openProject() }); } }, - true, false, "*.pd", "Patch"); + true, false, "*.pd", "Patch", this); } void PluginEditor::saveProjectAs(std::function const& nestedCallback) @@ -710,7 +712,7 @@ void PluginEditor::saveProjectAs(std::function const& nestedCallback) nestedCallback(); }, - "*.pd", "Patch"); + "*.pd", "Patch", this); } void PluginEditor::saveProject(std::function const& nestedCallback) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 0f46785a82..123a3902a2 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1182,7 +1182,9 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) MessageManager::callAsync([editor = Component::SafePointer(editor), windowWidth, windowHeight]() { if (!editor) return; +#if !JUCE_IOS editor->setSize(windowWidth, windowHeight); +#endif }); } } diff --git a/Source/Sidebar/DocumentationBrowser.h b/Source/Sidebar/DocumentationBrowser.h index e9aa94f813..f732072cf2 100644 --- a/Source/Sidebar/DocumentationBrowser.h +++ b/Source/Sidebar/DocumentationBrowser.h @@ -366,14 +366,14 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri auto* editor = findParentComponentOfClass(); auto* sidebar = getParentComponent(); auto bounds = editor->getLocalArea(sidebar, settingsCalloutButton->getBounds()); - auto openFolderCallback = [this]() { + auto openFolderCallback = [this, editor]() { Dialogs::showOpenDialog([this](URL result) { if (result.getLocalFile().isDirectory()) { pd->settingsFile->setProperty("browser_path", result.toString(false)); updateContent(); } }, - false, true, "", "DocumentationFileChooser"); + false, true, "", "DocumentationFileChooser", editor); }; auto resetFolderCallback = [this]() { diff --git a/Source/Utility/SettingsFile.cpp b/Source/Utility/SettingsFile.cpp index da805dfaa7..b189b95ecb 100644 --- a/Source/Utility/SettingsFile.cpp +++ b/Source/Utility/SettingsFile.cpp @@ -69,7 +69,10 @@ SettingsFile* SettingsFile::initialise() initialiseOverlayTree(); #if JUCE_IOS - if (OSUtils::isIPad()) { + if(!ProjectInfo::isStandalone) { + Desktop::getInstance().setGlobalScaleFactor(1.0f); // scaling inside AUv3 is a bad idea + } + else if (OSUtils::isIPad()) { Desktop::getInstance().setGlobalScaleFactor(1.125f); } else { Desktop::getInstance().setGlobalScaleFactor(0.825f); From f48cbba800baaf4bb13bb563e87aefd74aa8d55c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 28 Jan 2024 03:08:50 +0100 Subject: [PATCH 0165/1030] Fixed size inconsistencies with pd-vanilla for sliders and graphs --- Source/Objects/GraphOnParent.h | 3 +-- Source/Objects/SliderObject.h | 29 ++++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Source/Objects/GraphOnParent.h b/Source/Objects/GraphOnParent.h index 3089e45224..f7d9d6f5a5 100644 --- a/Source/Objects/GraphOnParent.h +++ b/Source/Objects/GraphOnParent.h @@ -72,7 +72,7 @@ class GraphOnParent final : public ObjectBase { int x = 0, y = 0, w = 0, h = 0; pd::Interface::getObjectBounds(patch, gobj.get(), &x, &y, &w, &h); - bounds = Rectangle(x, y, atoms[4].getFloat(), atoms[5].getFloat()); + bounds = Rectangle(x, y, atoms[4].getFloat() + 1, atoms[5].getFloat() + 1); } update(); object->setObjectBounds(bounds); @@ -122,7 +122,6 @@ class GraphOnParent final : public ObjectBase { void setPdBounds(Rectangle b) override { - if (auto glist = ptr.get<_glist>()) { auto* patch = cnv->patch.getPointer().get(); if (!patch) diff --git a/Source/Objects/SliderObject.h b/Source/Objects/SliderObject.h index a1c8fdf794..31d5a47c2a 100644 --- a/Source/Objects/SliderObject.h +++ b/Source/Objects/SliderObject.h @@ -191,17 +191,40 @@ class SliderObject : public ObjectBase { Rectangle getPdBounds() override { - return iemHelper.getPdBounds().expanded(2, 0).withTrimmedLeft(-1); + if(isVertical) + { + return iemHelper.getPdBounds().expanded(0, 2).withTrimmedBottom(-1); + } + else { + return iemHelper.getPdBounds().expanded(2, 0).withTrimmedLeft(-1); + } + } void setPdBounds(Rectangle b) override { - iemHelper.setPdBounds(b.reduced(2, 0).withTrimmedLeft(1)); + if(isVertical) + { + iemHelper.setPdBounds(b.reduced(0, 2).withTrimmedBottom(1)); + } + else { + iemHelper.setPdBounds(b.reduced(2, 0).withTrimmedLeft(1)); + } + } void objectMovedOrResized(bool resized) override { - auto objectBounds = object->getObjectBounds().reduced(2, 0).withTrimmedLeft(1); + auto objectBounds = object->getObjectBounds(); + + if(isVertical) + { + objectBounds = objectBounds.reduced(0, 2).withTrimmedBottom(1); + } + else { + objectBounds = objectBounds.reduced(2, 0).withTrimmedLeft(1); + } + setParameterExcludingListener(positionParameter, Array { var(objectBounds.getX()), var(objectBounds.getY()) }, &objectSizeListener); From cf68b79c6e543c68c53fb98d4acbe5599b12f0dc Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 28 Jan 2024 03:24:19 +0100 Subject: [PATCH 0166/1030] Fixed radio object size issue --- Source/Objects/RadioObject.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Source/Objects/RadioObject.h b/Source/Objects/RadioObject.h index 535df65b50..084f69e5f7 100644 --- a/Source/Objects/RadioObject.h +++ b/Source/Objects/RadioObject.h @@ -81,8 +81,8 @@ class RadioObject final : public ObjectBase { int x = 0, y = 0, w = 0, h = 0; pd::Interface::getObjectBounds(patch, radio.cast(), &x, &y, &w, &h); - auto width = !isVertical ? (radio->x_gui.x_h + 1) * numItems : (radio->x_gui.x_w + 1); - auto height = isVertical ? (radio->x_gui.x_w + 1) * numItems : (radio->x_gui.x_h + 1); + auto width = !isVertical ? (radio->x_gui.x_h * numItems) + 1 : (radio->x_gui.x_w + 1); + auto height = isVertical ? (radio->x_gui.x_w * numItems) + 1 : (radio->x_gui.x_h + 1); return { x, y, width, height }; } @@ -204,16 +204,17 @@ class RadioObject final : public ObjectBase { void updateAspectRatio() { - float verticalLength = ((object->getWidth() - Object::doubleMargin) * numItems) + Object::doubleMargin; - float horizontalLength = ((object->getHeight() - Object::doubleMargin) * numItems) + Object::doubleMargin; + auto b = getPdBounds(); + float verticalLength = (b.getWidth() * numItems) + Object::doubleMargin; + float horizontalLength = (b.getHeight() * numItems) + Object::doubleMargin; auto minLongSide = object->minimumSize * numItems; auto minShortSide = object->minimumSize; if (isVertical) { - object->setSize(object->getWidth(), verticalLength); + object->setSize(b.getWidth() + Object::doubleMargin, verticalLength); constrainer->setMinimumSize(minShortSide, minLongSide); } else { - object->setSize(horizontalLength, object->getHeight()); + object->setSize(horizontalLength, b.getHeight() + Object::doubleMargin); constrainer->setMinimumSize(minLongSide, minShortSide); } constrainer->setFixedAspectRatio(isVertical ? 1.0f / numItems : static_cast(numItems) / 1.0f); From 5e0dd1cc1788b6faf046e4399fd576bb8f510f80 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 28 Jan 2024 03:42:10 +0100 Subject: [PATCH 0167/1030] Trying to fix heavy exported finding search paths in plugin --- Source/Heavy/ExporterBase.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Heavy/ExporterBase.h b/Source/Heavy/ExporterBase.h index 91adc2adc5..dddf2cc895 100644 --- a/Source/Heavy/ExporterBase.h +++ b/Source/Heavy/ExporterBase.h @@ -140,6 +140,8 @@ struct ExporterBase : public Component // Add file location to search paths auto searchPaths = StringArray { patchFile.getParentDirectory().getFullPathName() }; + editor->pd->setThis(); + // Get pd's search paths char* paths[1024]; int numItems; From 7ed6bf6c7742e0220db26779d3dd791a8e5dc3c3 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 28 Jan 2024 03:59:10 +0100 Subject: [PATCH 0168/1030] Fixed compilation --- Source/Objects/RadioObject.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Objects/RadioObject.h b/Source/Objects/RadioObject.h index 084f69e5f7..fc9bf6a264 100644 --- a/Source/Objects/RadioObject.h +++ b/Source/Objects/RadioObject.h @@ -25,6 +25,11 @@ class RadioObject final : public ObjectBase { objectParameters.addParamSize(&sizeProperty, true); objectParameters.addParamInt("Options", cGeneral, &max, 8); iemHelper.addIemParameters(objectParameters); + + if (auto radio = ptr.get()) { + isVertical = radio->x_orientation; + sizeProperty = isVertical ? radio->x_gui.x_w : radio->x_gui.x_h; + } } void update() override From 39d75350c63b33de94d973cd2a25feb55e3bf31c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 28 Jan 2024 04:08:15 +0100 Subject: [PATCH 0169/1030] Fixed compilation --- Source/Heavy/ExporterBase.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Heavy/ExporterBase.h b/Source/Heavy/ExporterBase.h index dddf2cc895..fdc69df409 100644 --- a/Source/Heavy/ExporterBase.h +++ b/Source/Heavy/ExporterBase.h @@ -5,6 +5,7 @@ */ #include "PluginEditor.h" +#include "PluginProcessor.h" #include "Pd/Patch.h" struct ExporterBase : public Component From 10bb6aba430293171de3d053d02ec7603771ccc0 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 28 Jan 2024 04:10:24 +0100 Subject: [PATCH 0170/1030] Fixed radio object bug --- Source/Objects/RadioObject.h | 4 ++-- Source/Objects/SliderObject.h | 1 - Source/Pd/Patch.cpp | 5 +---- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Source/Objects/RadioObject.h b/Source/Objects/RadioObject.h index fc9bf6a264..736d9a4fd1 100644 --- a/Source/Objects/RadioObject.h +++ b/Source/Objects/RadioObject.h @@ -273,9 +273,9 @@ class RadioObject final : public ObjectBase { void updateSizeProperty() override { - setPdBounds(object->getObjectBounds()); - if (auto radio = ptr.get()) { + radio->x_gui.x_w = object->getWidth() - Object::doubleMargin - 1; + radio->x_gui.x_h = object->getHeight() - Object::doubleMargin - 1; setParameterExcludingListener(sizeProperty, isVertical ? var(radio->x_gui.x_w) : var(radio->x_gui.x_h)); } } diff --git a/Source/Objects/SliderObject.h b/Source/Objects/SliderObject.h index 31d5a47c2a..4ffb55ea3b 100644 --- a/Source/Objects/SliderObject.h +++ b/Source/Objects/SliderObject.h @@ -224,7 +224,6 @@ class SliderObject : public ObjectBase { else { objectBounds = objectBounds.reduced(2, 0).withTrimmedLeft(1); } - setParameterExcludingListener(positionParameter, Array { var(objectBounds.getX()), var(objectBounds.getY()) }, &objectSizeListener); diff --git a/Source/Pd/Patch.cpp b/Source/Pd/Patch.cpp index 562299feaa..d82294fc30 100644 --- a/Source/Pd/Patch.cpp +++ b/Source/Pd/Patch.cpp @@ -444,11 +444,8 @@ void Patch::paste(Point position) { auto text = SystemClipboard::getTextFromClipboard(); - // for some reason when we paste into PD, we need to apply a translation? - auto translatedObjects = translatePatchAsString(text, position.translated(1540, 1540)); - if (auto patch = ptr.get()) { - pd::Interface::paste(patch.get(), translatedObjects.toRawUTF8()); + pd::Interface::paste(patch.get(), text.toRawUTF8()); } } From fa26fc8ad9fe5fcec54c1ad181e09997743c728f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 28 Jan 2024 04:31:43 +0100 Subject: [PATCH 0171/1030] Fixed more radio object issues --- Source/Objects/RadioObject.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Source/Objects/RadioObject.h b/Source/Objects/RadioObject.h index 736d9a4fd1..e5d3de2bbb 100644 --- a/Source/Objects/RadioObject.h +++ b/Source/Objects/RadioObject.h @@ -195,7 +195,8 @@ class RadioObject final : public ObjectBase { float selectionY = isVertical ? selected * size : 0; auto selectionBounds = Rectangle(selectionX, selectionY, size, size); - g.fillRoundedRectangle(selectionBounds.reduced(5), Corners::objectCornerRadius / 2.0f); + + g.fillRoundedRectangle(selectionBounds.reduced(jmin(size * 0.25f, 5)), Corners::objectCornerRadius / 2.0f); } void paintOverChildren(Graphics& g) override @@ -235,10 +236,10 @@ class RadioObject final : public ObjectBase { if (auto radio = ptr.get()) { if (isVertical) { radio->x_gui.x_w = size; - radio->x_gui.x_h = size * numItems; + radio->x_gui.x_h = size; } else { radio->x_gui.x_h = size; - radio->x_gui.x_w = size * numItems; + radio->x_gui.x_w = size; } } @@ -274,8 +275,12 @@ class RadioObject final : public ObjectBase { void updateSizeProperty() override { if (auto radio = ptr.get()) { - radio->x_gui.x_w = object->getWidth() - Object::doubleMargin - 1; - radio->x_gui.x_h = object->getHeight() - Object::doubleMargin - 1; + auto size = isVertical ? object->getWidth() : object->getHeight(); + size -= (Object::doubleMargin + 1); + + radio->x_gui.x_w = size; + radio->x_gui.x_h = size; + setParameterExcludingListener(sizeProperty, isVertical ? var(radio->x_gui.x_w) : var(radio->x_gui.x_h)); } } From 97bc5bb6f88817c261729f817d7f212cea28659a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 28 Jan 2024 15:33:11 +0100 Subject: [PATCH 0172/1030] Slider fixes --- Source/Objects/SliderObject.h | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Source/Objects/SliderObject.h b/Source/Objects/SliderObject.h index 4ffb55ea3b..7dee4503c8 100644 --- a/Source/Objects/SliderObject.h +++ b/Source/Objects/SliderObject.h @@ -210,23 +210,14 @@ class SliderObject : public ObjectBase { else { iemHelper.setPdBounds(b.reduced(2, 0).withTrimmedLeft(1)); } - } void objectMovedOrResized(bool resized) override { auto objectBounds = object->getObjectBounds(); - - if(isVertical) - { - objectBounds = objectBounds.reduced(0, 2).withTrimmedBottom(1); - } - else { - objectBounds = objectBounds.reduced(2, 0).withTrimmedLeft(1); - } - - setParameterExcludingListener(positionParameter, Array { var(objectBounds.getX()), var(objectBounds.getY()) }, &objectSizeListener); + setParameterExcludingListener(positionParameter, Array{ var(objectBounds.getX()), var(objectBounds.getY()) }, &objectSizeListener); + if (resized) updateSizeProperty(); @@ -335,9 +326,16 @@ class SliderObject : public ObjectBase { void updateSizeProperty() override { - //setPdBounds(object->getObjectBounds()); - if (auto iem = ptr.get()) { + if(isVertical) { + iem->x_w = object->getObjectBounds().getWidth() - 1; + iem->x_h = object->getObjectBounds().getHeight() - 6; + } + else { + iem->x_w = object->getObjectBounds().getWidth() - 6; + iem->x_h = object->getObjectBounds().getHeight() - 1; + } + setParameterExcludingListener(sizeProperty, Array { var(iem->x_w), var(iem->x_h) }); } } From 96405ebdbe0d4c2ac1080a7fa37cd7db412a8c09 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 28 Jan 2024 15:55:28 +0100 Subject: [PATCH 0173/1030] Fix for paste, fix for undo/redo --- Source/Pd/Interface.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Pd/Interface.h b/Source/Pd/Interface.h index 103ec30643..4e494b2b2d 100644 --- a/Source/Pd/Interface.h +++ b/Source/Pd/Interface.h @@ -345,6 +345,8 @@ struct Interface { { canvas_setcurrent(cnv); pd_typedmess((t_pd*)cnv, s, argc, argv); + + canvas_undo_add(cnv, UNDO_SEQUENCE_START, "create", nullptr); canvas_undo_add(cnv, UNDO_CREATE, "create", (void*)canvas_undo_set_create(cnv)); @@ -364,6 +366,8 @@ struct Interface { new_object, pos)); } + canvas_undo_add(cnv, UNDO_SEQUENCE_END, "create", nullptr); + canvas_unsetcurrent(cnv); glist_noselect(cnv); @@ -473,11 +477,7 @@ struct Interface { static void moveObject(t_canvas* cnv, t_gobj* obj, int x, int y) { if(!EDITOR->canvas_undo_already_set_move) { - glist_noselect(cnv); - glist_select(cnv, obj); - canvas_undo_add(cnv, UNDO_MOTION, "motion", canvas_undo_set_move(cnv, 1)); - glist_noselect(cnv); - + canvas_undo_add(cnv, UNDO_MOTION, "motion", canvas_undo_set_move(cnv, 0)); EDITOR->canvas_undo_already_set_move = 1; } From ec0553fe31756f19e9babc3f60af11f891cece4d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 29 Jan 2024 01:22:11 +0100 Subject: [PATCH 0174/1030] Fixed label size and position inaccuracies compared to pd-vanilla --- Source/Objects/AtomHelper.h | 12 ++++++------ Source/Objects/IEMHelper.h | 8 ++++---- Source/Objects/SliderObject.h | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Source/Objects/AtomHelper.h b/Source/Objects/AtomHelper.h index ac308b72f0..73f5219c2b 100644 --- a/Source/Objects/AtomHelper.h +++ b/Source/Objects/AtomHelper.h @@ -277,7 +277,7 @@ class AtomHelper { setFontHeight(atomSizes[idx - 1]); - int fontHeight = getAtomHeight() - 6; + int fontHeight = getAtomHeight() - 5; String const text = getExpandedLabelText(); if (text.isNotEmpty()) { @@ -323,7 +323,7 @@ class AtomHelper { Rectangle getLabelBounds() { auto objectBounds = object->getBounds().reduced(Object::margin); - int fontHeight = getAtomHeight() - 6; + int fontHeight = getAtomHeight() - 5; auto currentHash = hash(getExpandedLabelText()); int labelLength = lastLabelLength; @@ -342,16 +342,16 @@ class AtomHelper { auto labelBounds = objectBounds.withSizeKeepingCentre(labelLength, fontHeight); if (labelPosition == 0) { // left - return labelBounds.withRightX(objectBounds.getX() - 4); + return labelBounds.withRightX(objectBounds.getX() - 2); } if (labelPosition == 1) { // right - return labelBounds.withX(objectBounds.getRight() + 4); + return labelBounds.withX(objectBounds.getRight() + 2); } if (labelPosition == 2) { // top - return labelBounds.withX(objectBounds.getX()).withBottomY(objectBounds.getY()); + return labelBounds.withX(objectBounds.getX()).withBottomY(objectBounds.getY() - 2); } - return labelBounds.withX(objectBounds.getX()).withY(objectBounds.getBottom()); + return labelBounds.withX(objectBounds.getX()).withY(objectBounds.getBottom() + 2); } String getExpandedLabelText() const diff --git a/Source/Objects/IEMHelper.h b/Source/Objects/IEMHelper.h index 0f4edeae93..cb449604a1 100644 --- a/Source/Objects/IEMHelper.h +++ b/Source/Objects/IEMHelper.h @@ -278,7 +278,7 @@ class IEMHelper { } } - void updateLabel(std::unique_ptr& label) + void updateLabel(std::unique_ptr& label, Point offset = {0, 0}) { String const text = labelText.toString(); @@ -293,7 +293,7 @@ class IEMHelper { bounds.translate(0, bounds.getHeight() / -2.0f); label->setFont(Font(bounds.getHeight())); - label->setBounds(bounds); + label->setBounds(bounds + offset); label->setText(text, dontSendNotification); label->setColour(Label::textColourId, getLabelColour()); @@ -313,7 +313,7 @@ class IEMHelper { if (auto iemgui = ptr.get()) { t_symbol const* sym = canvas_realizedollar(iemgui->x_glist, iemgui->x_lab); if (sym) { - int fontHeight = getFontHeight(); + int fontHeight = getFontHeight() + 2; auto currentHash = hash(getExpandedLabelText()); int labelLength = lastLabelLength; if(lastFontHeight != fontHeight || lastLabelTextHash != currentHash) @@ -324,7 +324,7 @@ class IEMHelper { lastLabelLength = labelLength; } - int const posx = objectBounds.getX() + iemgui->x_ldx + 4; + int const posx = objectBounds.getX() + iemgui->x_ldx; int const posy = objectBounds.getY() + iemgui->x_ldy; return { posx, posy, labelLength, fontHeight }; diff --git a/Source/Objects/SliderObject.h b/Source/Objects/SliderObject.h index 7dee4503c8..f39ff2ad86 100644 --- a/Source/Objects/SliderObject.h +++ b/Source/Objects/SliderObject.h @@ -186,7 +186,7 @@ class SliderObject : public ObjectBase { void updateLabel() override { - iemHelper.updateLabel(label); + iemHelper.updateLabel(label, isVertical ? Point(0, 2) : Point(2, 0)); } Rectangle getPdBounds() override From fc8ea3e3a47ca74fa449be36a79c55a1ce6b00d7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 29 Jan 2024 02:14:37 +0100 Subject: [PATCH 0175/1030] Make label size even more close to pd-vanilla --- Source/LookAndFeel.cpp | 2 +- Source/Objects/AtomHelper.h | 15 ++++++++++++--- Source/Objects/IEMHelper.h | 21 ++++----------------- Source/Objects/ObjectBase.h | 2 +- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index be96bc0e51..2754ddfb15 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -1125,7 +1125,7 @@ void PlugDataLook::drawLabel(Graphics& g, Label& label) g.setFont(font); g.setColour(label.findColour(Label::textColourId)); - g.drawFittedText(label.getText(), textArea, label.getJustificationType(), 1, 1.0f); + g.drawFittedText(label.getText(), textArea, label.getJustificationType(), 1, label.getMinimumHorizontalScale()); g.setColour(label.findColour(Label::outlineColourId).withMultipliedAlpha(alpha)); } else if (label.isEnabled()) { diff --git a/Source/Objects/AtomHelper.h b/Source/Objects/AtomHelper.h index 73f5219c2b..8ef645065a 100644 --- a/Source/Objects/AtomHelper.h +++ b/Source/Objects/AtomHelper.h @@ -324,9 +324,12 @@ class AtomHelper { { auto objectBounds = object->getBounds().reduced(Object::margin); int fontHeight = getAtomHeight() - 5; - + int fontWidth = sys_fontwidth(fontHeight); + int labelSpace = fontWidth * (getExpandedLabelText().length() + 1); + auto currentHash = hash(getExpandedLabelText()); int labelLength = lastLabelLength; + if(lastFontHeight != fontHeight || lastLabelTextHash != currentHash) { labelLength = Font(fontHeight).getStringWidth(getExpandedLabelText()); @@ -340,13 +343,19 @@ class AtomHelper { labelPosition = atom->a_wherelabel; } auto labelBounds = objectBounds.withSizeKeepingCentre(labelLength, fontHeight); - + int lengthDifference = labelLength - labelSpace; // difference between width in pd-vanilla and plugdata + if (labelPosition == 0) { // left - return labelBounds.withRightX(objectBounds.getX() - 2); + labelBounds.removeFromLeft(lengthDifference); + return labelBounds.withRightX(objectBounds.getX() - lengthDifference - 2); } + + labelBounds.removeFromRight(lengthDifference); + if (labelPosition == 1) { // right return labelBounds.withX(objectBounds.getRight() + 2); } + if (labelPosition == 2) { // top return labelBounds.withX(objectBounds.getX()).withBottomY(objectBounds.getY() - 2); } diff --git a/Source/Objects/IEMHelper.h b/Source/Objects/IEMHelper.h index cb449604a1..774a98c846 100644 --- a/Source/Objects/IEMHelper.h +++ b/Source/Objects/IEMHelper.h @@ -313,21 +313,12 @@ class IEMHelper { if (auto iemgui = ptr.get()) { t_symbol const* sym = canvas_realizedollar(iemgui->x_glist, iemgui->x_lab); if (sym) { - int fontHeight = getFontHeight() + 2; - auto currentHash = hash(getExpandedLabelText()); - int labelLength = lastLabelLength; - if(lastFontHeight != fontHeight || lastLabelTextHash != currentHash) - { - labelLength = Font(fontHeight).getStringWidth(getExpandedLabelText()); - lastFontHeight = fontHeight; - lastLabelTextHash = currentHash; - lastLabelLength = labelLength; - } - + int fontHeight = getFontHeight(); + int fontWidth = sys_fontwidth(fontHeight); int const posx = objectBounds.getX() + iemgui->x_ldx; int const posy = objectBounds.getY() + iemgui->x_ldy; - - return { posx, posy, labelLength, fontHeight }; + + return { posx, posy, fontWidth * (getExpandedLabelText().length() + 1), fontHeight + 2 }; } } @@ -535,10 +526,6 @@ class IEMHelper { PluginProcessor* pd; pd::WeakReference ptr; - - int lastFontHeight = 10; - hash32 lastLabelTextHash = 0; - int lastLabelLength = 0; Value primaryColour = SynchronousValue(); Value secondaryColour = SynchronousValue(); diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index ba5dbf864a..13f4ccd332 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -28,7 +28,7 @@ class ObjectLabel : public Label { { setJustificationType(Justification::centredLeft); setBorderSize(BorderSize(0, 0, 0, 0)); - setMinimumHorizontalScale(1.f); + setMinimumHorizontalScale(0.2f); setEditable(false, false); setInterceptsMouseClicks(false, false); } From ea30b1e4b05b548e8ec88140586223b575cf11f9 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Mon, 29 Jan 2024 16:45:54 +1030 Subject: [PATCH 0176/1030] implement overlay option to allow 'run' mode to show connections behind or infront of objects --- Source/Canvas.cpp | 58 +++++++++++++++---------- Source/Canvas.h | 3 ++ Source/Constants.h | 6 ++- Source/Dialogs/OverlayDisplaySettings.h | 29 ++++++++----- 4 files changed, 59 insertions(+), 37 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 3e0de9ec7e..4b459246f0 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -105,8 +105,6 @@ Canvas::Canvas(PluginEditor* parent, pd::Patch::Ptr p, Component* parentGraph) graphArea->setAlwaysOnTop(true); } - updateOverlays(); - setSize(infiniteCanvasSize, infiniteCanvasSize); // initialize to default zoom @@ -138,6 +136,10 @@ Canvas::Canvas(PluginEditor* parent, pd::Patch::Ptr p, Component* parentGraph) locked.addListener(this); editor->addModifierKeyListener(this); + + updateOverlays(); + orderConnections(); + parameters.addParamBool("Is graph", cGeneral, &isGraphChild, { "No", "Yes" }, 0); parameters.addParamBool("Hide name and arguments", cGeneral, &hideNameAndArgs, { "No", "Yes" }, 0); parameters.addParamRange("X range", cGeneral, &xRange, { 0.0f, 1.0f }); @@ -204,6 +206,9 @@ void Canvas::updateOverlays() showBorder = overlayState & Border; showOrigin = overlayState & Origin; + connectionsBehind = overlayState & Behind; + + orderConnections(); for (auto* object : objects) { object->updateOverlays(overlayState); @@ -645,11 +650,12 @@ void Canvas::mouseDown(MouseEvent const& e) deselectAll(); presentationMode.setValue(false); - if (locked.getValue()) { - locked.setValue(false); - } else { - locked.setValue(true); - } + + // when command + click on canvas, swap between locked / edit mode + locked.setValue(!locked.getValue()); + locked.getValueSource().sendChangeMessage(true); + + updateOverlays(); } if (!e.mods.isShiftDown()) { deselectAll(); @@ -1718,29 +1724,13 @@ void Canvas::valueChanged(Value& v) cancelConnectionCreation(); deselectAll(); - // move all connections to back when canvas is locked - if (locked == var(true)) { - // use reverse order to preserve correct connection layering - for (int i = connections.size() - 1; i >= 0; i--) { - connections[i]->setAlwaysOnTop(false); - connections[i]->toBack(); - } - } else { - // otherwise move all connections to front - for (auto connection : connections) { - connection->setAlwaysOnTop(true); - connection->toFront(false); - } - } - - repaint(); - // Makes sure no objects keep keyboard focus after locking/unlocking if (isShowing() && isVisible()) grabKeyboardFocus(); editor->updateCommandStatus(); updateOverlays(); + orderConnections(); } else if (v.refersToSameSourceAs(commandLocked)) { updateOverlays(); repaint(); @@ -1800,6 +1790,26 @@ void Canvas::valueChanged(Value& v) } } +void Canvas::orderConnections() +{ + // move all connections to back when canvas is locked & connections behind is active + if (locked == var(true) && connectionsBehind) { + // use reverse order to preserve correct connection layering + for (int i = connections.size() - 1; i >= 0; i--) { + connections[i]->setAlwaysOnTop(false); + connections[i]->toBack(); + } + } else { + // otherwise move all connections to front + for (auto connection : connections) { + connection->setAlwaysOnTop(true); + connection->toFront(false); + } + } + + repaint(); +} + void Canvas::showSuggestions(Object* object, TextEditor* textEditor) { suggestor->createCalloutBox(object, textEditor); diff --git a/Source/Canvas.h b/Source/Canvas.h index 480a416352..b431a41bc7 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -131,6 +131,8 @@ class Canvas : public Component void updateSidebarSelection(); + void orderConnections(); + void showSuggestions(Object* object, TextEditor* textEditor); void hideSuggestions(); @@ -179,6 +181,7 @@ class Canvas : public Component bool showOrigin = false; bool showBorder = false; + bool connectionsBehind = true; bool isGraph = false; bool hasParentCanvas = false; diff --git a/Source/Constants.h b/Source/Constants.h index 9355c3b946..75d16aa7bf 100644 --- a/Source/Constants.h +++ b/Source/Constants.h @@ -402,7 +402,8 @@ enum Overlay { Coordinate = 8, ActivationState = 16, Order = 32, - Direction = 64 + Direction = 64, + Behind = 128 }; enum OverlayItem { @@ -411,7 +412,8 @@ enum OverlayItem { OverlayIndex, OverlayActivationState, OverlayDirection, - OverlayOrder + OverlayOrder, + OverlayConnectionsBehind }; enum Align { diff --git a/Source/Dialogs/OverlayDisplaySettings.h b/Source/Dialogs/OverlayDisplaySettings.h index d6be086eef..1ef325960e 100644 --- a/Source/Dialogs/OverlayDisplaySettings.h +++ b/Source/Dialogs/OverlayDisplaySettings.h @@ -37,11 +37,14 @@ class OverlayDisplaySettings : public Component { , group(groupType) { auto controlVisibility = [this](String const& mode) { - if (settingName == "origin" || settingName == "border" || mode == "edit" || mode == "lock" || mode == "alt") { - return true; - } else { + if (settingName == "behind" && (mode == "edit" || mode == "alt")) { return false; } + else if (settingName == "origin" || settingName == "border" || mode == "edit" || mode == "lock" || mode == "alt") { + return true; + } + + return false; }; for (auto* button : buttons) { @@ -55,10 +58,12 @@ class OverlayDisplaySettings : public Component { buttons[Run]->setButtonText(Icons::Presentation); buttons[Alt]->setButtonText(Icons::Eye); - buttons[Edit]->setTooltip("Show " + groupName.toLowerCase() + " in edit mode"); - buttons[Lock]->setTooltip("Show " + groupName.toLowerCase() + " in run mode"); - buttons[Run]->setTooltip("Show " + groupName.toLowerCase() + " in presentation mode"); - buttons[Alt]->setTooltip("Show " + groupName.toLowerCase() + " when overlay button is active"); + auto lowerCaseToolTip = toolTip.toLowerCase(); + + buttons[Edit]->setTooltip("Show " + lowerCaseToolTip + " in edit mode"); + buttons[Lock]->setTooltip("Show " + lowerCaseToolTip + " in run mode"); + buttons[Run]->setTooltip("Show " + lowerCaseToolTip + " in presentation mode"); + buttons[Alt]->setTooltip("Show " + lowerCaseToolTip + " when overlay button is active"); textLabel.setText(groupName, dontSendNotification); textLabel.setTooltip(toolTip); @@ -131,13 +136,14 @@ class OverlayDisplaySettings : public Component { connectionLabel.setFont(Fonts::getSemiBoldFont().withHeight(14)); addAndMakeVisible(connectionLabel); - buttonGroups.add(new OverlaySelector(overlayTree, Origin, "origin", "Origin", "0,0 point of canvas")); + buttonGroups.add(new OverlaySelector(overlayTree, Origin, "origin", "Origin", "Origin point of canvas")); buttonGroups.add(new OverlaySelector(overlayTree, Border, "border", "Border", "Plugin / window workspace size")); buttonGroups.add(new OverlaySelector(overlayTree, Index, "index", "Index", "Object index in patch")); // buttonGroups.add(new OverlaySelector(overlayTree, Coordinate, "coordinate", "Coordinate", "Object coordinate in patch")); - buttonGroups.add(new OverlaySelector(overlayTree, ActivationState, "activation_state", "Activity", "Show object activity")); - buttonGroups.add(new OverlaySelector(overlayTree, Direction, "direction", "Direction", "Direction of connection")); + buttonGroups.add(new OverlaySelector(overlayTree, ActivationState, "activation_state", "Activity", "Object activity")); + buttonGroups.add(new OverlaySelector(overlayTree, Direction, "direction", "Direction", "Direction of connections")); buttonGroups.add(new OverlaySelector(overlayTree, Order, "order", "Order", "Trigger order of multiple outlets")); + buttonGroups.add(new OverlaySelector(overlayTree, Behind, "behind", "Behind", "Connection cables behind objects")); for (auto* buttonGroup : buttonGroups) { addAndMakeVisible(buttonGroup); @@ -169,6 +175,7 @@ class OverlayDisplaySettings : public Component { connectionLabel.setBounds(bounds.removeFromTop(labelHeight)); buttonGroups[OverlayDirection]->setBounds(bounds.removeFromTop(itemHeight)); buttonGroups[OverlayOrder]->setBounds(bounds.removeFromTop(itemHeight)); + buttonGroups[OverlayConnectionsBehind]->setBounds(bounds.removeFromTop(itemHeight)); setSize(200, bounds.getY() + 5); } @@ -176,7 +183,7 @@ class OverlayDisplaySettings : public Component { { auto firstPanelBounds = buttonGroups[OverlayOrigin]->getBounds().getUnion(buttonGroups[OverlayBorder]->getBounds()); auto secondPanelBounds = buttonGroups[OverlayIndex]->getBounds().getUnion(buttonGroups[OverlayActivationState]->getBounds()); - auto thirdPanelBounds = buttonGroups[OverlayDirection]->getBounds().getUnion(buttonGroups[OverlayOrder]->getBounds()); + auto thirdPanelBounds = buttonGroups[OverlayDirection]->getBounds().getUnion(buttonGroups[OverlayConnectionsBehind]->getBounds()); for (auto& bounds : std::vector> { firstPanelBounds, secondPanelBounds, thirdPanelBounds }) { g.setColour(findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.035f)); From 93ae39e69c61ebf188d84d1e0ff102829e151eaf Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 31 Jan 2024 14:41:59 +0100 Subject: [PATCH 0177/1030] Fixed potential crash on startup --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 3960fbaae6..184934dc18 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 3960fbaae65f8c0156a15f8c45d8f12e88b2c565 +Subproject commit 184934dc183821b2dc3d071f391d26e1685d989e From 692ed8e32e9c52d425d8987d7b26f2acde0b7661 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 31 Jan 2024 16:19:14 +0100 Subject: [PATCH 0178/1030] Fix comment text colour --- Source/Objects/CommentObject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Objects/CommentObject.h b/Source/Objects/CommentObject.h index ecc6c361ec..9704b386f4 100644 --- a/Source/Objects/CommentObject.h +++ b/Source/Objects/CommentObject.h @@ -182,7 +182,7 @@ class CommentObject final : public ObjectBase int textWidth = getTextObjectWidth() - 12; // Reserve a bit of extra space for the text margin auto currentLayoutHash = hash(objText); - auto colour = object->findColour(PlugDataColour::canvasTextColourId); + auto colour = object->findColour(PlugDataColour::commentTextColourId); if(layoutTextHash != currentLayoutHash || colour.getARGB() != lastColourARGB || textWidth != lastTextWidth) { auto attributedText = AttributedString(objText); From 6650d57799b5b177166058cbf4fdd324fe74d1d9 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 1 Feb 2024 10:53:41 +0100 Subject: [PATCH 0179/1030] Fixed copy/paste bug --- Source/Canvas.cpp | 2 +- Source/Pd/Patch.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 4b459246f0..2d62e64d60 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -1049,7 +1049,7 @@ void Canvas::pasteSelection() patch.startUndoSequence("Paste object/s"); // Paste at mousePos, adds padding if pasted the same place - auto mousePosition = getMouseXYRelative(); + auto mousePosition = getMouseXYRelative() - canvasOrigin; if (mousePosition == pastedPosition) { pastedPadding.addXY(10, 10); } else { diff --git a/Source/Pd/Patch.cpp b/Source/Pd/Patch.cpp index d82294fc30..4f1364d7c2 100644 --- a/Source/Pd/Patch.cpp +++ b/Source/Pd/Patch.cpp @@ -444,8 +444,10 @@ void Patch::paste(Point position) { auto text = SystemClipboard::getTextFromClipboard(); + auto translatedObjects = translatePatchAsString(text, position); + if (auto patch = ptr.get()) { - pd::Interface::paste(patch.get(), text.toRawUTF8()); + pd::Interface::paste(patch.get(), translatedObjects.toRawUTF8()); } } From cd3b84f4ddafac7226c1ce24dca96c2f8128c184 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 1 Feb 2024 10:54:05 +0100 Subject: [PATCH 0180/1030] Small optimisation to reduce memory usage on large patches --- Source/Object.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index 94cee8d1be..298d528707 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -476,7 +476,21 @@ void Object::triggerOverlayActiveState() if (rateReducer.tooFast()) return; + + if (!getLocalBounds().isEmpty() && activityOverlayImage.getBounds() != getLocalBounds()) { + // render activity state overlay once for this object size since it'll always look the same for the same object size anyway + activityOverlayImage = Image(Image::ARGB, getWidth(), getHeight(), true); + Graphics g(activityOverlayImage); + g.saveState(); + + g.excludeClipRegion(getLocalBounds().reduced(Object::margin + 1)); + Path objectShadow; + objectShadow.addRoundedRectangle(getLocalBounds().reduced(Object::margin - 2), Corners::objectCornerRadius); + StackShadow::renderDropShadow(g, objectShadow, findColour(PlugDataColour::dataColourId), 6, { 0, 0 }, 0); + g.restoreState(); + } + activeStateAlpha = 1.0f; startTimer(1000 / ACTIVITY_UPDATE_RATE); @@ -591,20 +605,6 @@ void Object::resized() index++; } - - if (!getLocalBounds().isEmpty() && activityOverlayImage.getBounds() != getLocalBounds()) { - // Pre-render activity state overlay here since it'll always look the same for the same object size - activityOverlayImage = Image(Image::ARGB, getWidth(), getHeight(), true); - Graphics g(activityOverlayImage); - g.saveState(); - - g.excludeClipRegion(getLocalBounds().reduced(Object::margin + 1)); - - Path objectShadow; - objectShadow.addRoundedRectangle(getLocalBounds().reduced(Object::margin - 2), Corners::objectCornerRadius); - StackShadow::renderDropShadow(g, objectShadow, findColour(PlugDataColour::dataColourId), 6, { 0, 0 }, 0); - g.restoreState(); - } } void Object::updateTooltips() From d96a7ef01ea0f15b471b293bc305f10faa6cb373 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 1 Feb 2024 10:54:21 +0100 Subject: [PATCH 0181/1030] Trying to fix potential crash when closing DAW project --- Source/Pd/Instance.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Source/Pd/Instance.h b/Source/Pd/Instance.h index aaaea92e79..2d6417f297 100644 --- a/Source/Pd/Instance.h +++ b/Source/Pd/Instance.h @@ -316,8 +316,6 @@ class Instance { private: std::unordered_map> pdWeakReferences; - std::unique_ptr objectImplementations; - moodycamel::ConcurrentQueue> functionQueue = moodycamel::ConcurrentQueue>(4096); std::unique_ptr openChooser; @@ -328,7 +326,8 @@ class Instance { struct internal; std::unique_ptr messageDispatcher; - + std::unique_ptr objectImplementations; // must be after messageDispatcher (!) + struct ConsoleHandler : public Timer { Instance* instance; From 8ae5edfbef624c7e2d55b86ba305b09e49914d62 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 3 Feb 2024 17:56:46 +0100 Subject: [PATCH 0182/1030] Update ELSE to rc11, update cyclone to 0.8-1 (pre-release) --- .../cyclone_objects/binaries/control/zl.c | 103 +++++++++++++++++- .../help_files/phaseshift~-help.pd | 74 ++++++------- Libraries/pd-else | 2 +- Resources/Documentation/cyclone/zl.md | 2 +- Source/Pd/Setup.cpp | 82 +++++++++++++- Source/PluginProcessor.h | 4 +- Source/Utility/Config.h | 2 +- 7 files changed, 221 insertions(+), 48 deletions(-) diff --git a/Libraries/cyclone/cyclone_objects/binaries/control/zl.c b/Libraries/cyclone/cyclone_objects/binaries/control/zl.c index d2d73ba63d..fa48c286eb 100644 --- a/Libraries/cyclone/cyclone_objects/binaries/control/zl.c +++ b/Libraries/cyclone/cyclone_objects/binaries/control/zl.c @@ -102,7 +102,8 @@ static void zldata_realloc(t_zldata *d, int reqsz){ } else if(reqsz > ZL_DEF_SIZE && !heaped){ d->d_buf = getbytes(reqsz * sizeof(t_atom)); - memcpy(d->d_buf, d->d_bufini, curmax * sizeof(t_atom)); + int maxsize = curmax > ZL_DEF_SIZE ? ZL_DEF_SIZE : curmax; + memcpy(d->d_buf, d->d_bufini, maxsize * sizeof(t_atom)); } else if(reqsz > ZL_DEF_SIZE && heaped) d->d_buf = (t_atom *)resizebytes(d->d_buf, cursz*sizeof(t_atom), reqsz*sizeof(t_atom)); @@ -1592,8 +1593,8 @@ static void *zl_new(t_symbol *s, int argc, t_atom *argv){ } i--; // iterate a++; - }; - if(a->a_type == A_SYMBOL){ // is symbol + } + else if(a->a_type == A_SYMBOL){ // is symbol if(!first_arg) // is first arg, so mark it first_arg = 1; t_symbol * cursym = atom_getsymbolarg(0, i, a); @@ -1705,3 +1706,99 @@ CYCLONE_OBJ_API void zl_setup(void){ zl_setupmode("indexmap", 0, 0, zl_indexmap_anyarg, zl_indexmap_count, zl_indexmap, 30); zl_setupmode("swap", 0, 0, zl_swapmode_anyarg, zl_swapmode_count, zl_swapmode, 31); } + + +#define ZL_ALIAS_SETUP(MODE) \ + static void* zl_##MODE##_new(t_symbol *s, int argc, t_atom *argv) \ + { \ + int ac = argc + 1; \ + t_atom* av = malloc(ac * sizeof(t_atom)); \ + memcpy(av + sizeof(t_atom), argv, argc * sizeof(t_atom)); \ + SETSYMBOL(av, gensym(#MODE)); \ + return zl_new(s, ac, av); \ + } \ + \ + CYCLONE_OBJ_API void setup_zl0x2e##MODE(void) { \ + zl_class = class_new(gensym("zl." #MODE), (t_newmethod)zl_##MODE##_new, \ + (t_method)zl_free, sizeof(t_zl), 0, A_GIMME, 0); \ + class_addbang(zl_class, zl_bang); \ + class_addfloat(zl_class, zl_float); \ + class_addsymbol(zl_class, zl_symbol); \ + class_addlist(zl_class, zl_list); \ + class_addanything(zl_class, zl_anything); \ + class_addmethod(zl_class, (t_method)zl_mode, gensym("mode"), A_GIMME, 0); \ + class_addmethod(zl_class, (t_method)zl_zlmaxsize, gensym("zlmaxsize"), A_FLOAT, 0); \ + class_addmethod(zl_class, (t_method)zl_zlclear, gensym("zlclear"), 0); \ + class_sethelpsymbol(zl_class, gensym("zl")); \ + zlproxy_class = class_new(gensym("_zlproxy"), 0, 0, \ + sizeof(t_zlproxy), CLASS_PD | CLASS_NOINLET, 0); \ + class_addbang(zlproxy_class, zlproxy_bang); \ + class_addfloat(zlproxy_class, zlproxy_float); \ + class_addsymbol(zlproxy_class, zlproxy_symbol); \ + class_addlist(zlproxy_class, zlproxy_list); \ + class_addanything(zlproxy_class, zlproxy_anything); \ + zl_setupmode("unknown", 0, 0, 0, zl_nop_count, zl_nop, 0); \ + zl_setupmode("ecils", 0, zl_ecils_intarg, 0, zl_ecils_count, zl_ecils, 1); \ + zl_setupmode("group", 1, zl_group_intarg, 0, zl_group_count, zl_group, 2); \ + zl_setupmode("iter", 0, zl_iter_intarg, 0, zl_iter_count, zl_iter, 3); \ + zl_setupmode("join", 0, 0, zl_join_anyarg, zl_join_count, zl_join, 4); \ + zl_setupmode("len", 0, 0, 0, zl_len_count, zl_len, 5); \ + zl_setupmode("mth", 0, zl_mth_intarg, zl_mth_anyarg, zl_mth_count, zl_mth, 6); \ + zl_setupmode("nth", 0, zl_nth_intarg, zl_nth_anyarg, zl_nth_count, zl_nth, 7); \ + zl_setupmode("reg", 0, 0, zl_reg_anyarg, zl_reg_count, zl_reg, 8); \ + zl_setupmode("rev", 0, 0, 0, zl_rev_count, zl_rev, 9); \ + zl_setupmode("rot", 0, zl_rot_intarg, 0, zl_rot_count, zl_rot, 10); \ + zl_setupmode("sect", 0, 0, zl_sect_anyarg, zl_sect_count, zl_sect, 11); \ + zl_setupmode("slice", 0, zl_slice_intarg, 0, zl_slice_count, zl_slice, 12); \ + zl_setupmode("sort", 0, zl_sort_intarg, 0, zl_sort_count, zl_sort, 13); \ + zl_setupmode("sub", 0, 0, zl_sub_anyarg, zl_sub_count, zl_sub, 14); \ + zl_setupmode("union", 0, 0, zl_union_anyarg, zl_union_count, zl_union, 15); \ + zl_setupmode("change", 0, 0, zl_change_anyarg, zl_change_count, zl_change, 16); \ + zl_setupmode("compare", 0, 0, zl_compare_anyarg, zl_compare_count, zl_compare, 17); \ + zl_setupmode("delace", 0, 0, 0, zl_delace_count, zl_delace, 18); \ + zl_setupmode("filter", 0, 0, zl_filter_anyarg, zl_filter_count, zl_filter, 19); \ + zl_setupmode("lace", 0, 0, zl_lace_anyarg, zl_lace_count, zl_lace, 20); \ + zl_setupmode("lookup", 0, 0, zl_lookup_anyarg, zl_lookup_count, zl_lookup, 21); \ + zl_setupmode("median", 0, 0, 0, zl_median_count, zl_median, 22); \ + zl_setupmode("queue", 0, 0, 0, zl_queue_count, zl_queue, 23); \ + zl_setupmode("scramble", 0, 0, zl_scramble_anyarg, zl_scramble_count, zl_scramble, 24); \ + zl_setupmode("stack", 1, 0, 0, zl_stack_count, zl_stack, 25); \ + zl_setupmode("stream", 0, zl_stream_intarg, 0, zl_stream_count, zl_stream, 26); \ + zl_setupmode("sum", 0, 0, 0, zl_sum_count, zl_sum, 27); \ + zl_setupmode("thin", 0, 0, 0, zl_thin_count, zl_thin, 28); \ + zl_setupmode("unique", 0, 0, zl_unique_anyarg, zl_unique_count, zl_unique, 29); \ + zl_setupmode("indexmap", 0, 0, zl_indexmap_anyarg, zl_indexmap_count, zl_indexmap, 30); \ + zl_setupmode("swap", 0, 0, zl_swapmode_anyarg, zl_swapmode_count, zl_swapmode, 31); \ + } + +ZL_ALIAS_SETUP(ecils) +ZL_ALIAS_SETUP(group) +ZL_ALIAS_SETUP(iter) +ZL_ALIAS_SETUP(join) +ZL_ALIAS_SETUP(len) +ZL_ALIAS_SETUP(mth) +ZL_ALIAS_SETUP(nth) +ZL_ALIAS_SETUP(reg) +ZL_ALIAS_SETUP(rev) +ZL_ALIAS_SETUP(rot) +ZL_ALIAS_SETUP(sect) +ZL_ALIAS_SETUP(slice) +ZL_ALIAS_SETUP(sort) +ZL_ALIAS_SETUP(sub) +ZL_ALIAS_SETUP(union) +ZL_ALIAS_SETUP(change) +ZL_ALIAS_SETUP(compare) +ZL_ALIAS_SETUP(delace) +ZL_ALIAS_SETUP(filter) +ZL_ALIAS_SETUP(lace) +ZL_ALIAS_SETUP(lookup) +ZL_ALIAS_SETUP(median) +ZL_ALIAS_SETUP(queue) +ZL_ALIAS_SETUP(scramble) +ZL_ALIAS_SETUP(stack) +ZL_ALIAS_SETUP(stream) +ZL_ALIAS_SETUP(sum) +ZL_ALIAS_SETUP(thin) +ZL_ALIAS_SETUP(unique) +ZL_ALIAS_SETUP(indexmap) +ZL_ALIAS_SETUP(swap) diff --git a/Libraries/cyclone/documentation/help_files/phaseshift~-help.pd b/Libraries/cyclone/documentation/help_files/phaseshift~-help.pd index 6fd2c57e9f..43dc863357 100644 --- a/Libraries/cyclone/documentation/help_files/phaseshift~-help.pd +++ b/Libraries/cyclone/documentation/help_files/phaseshift~-help.pd @@ -16,10 +16,10 @@ #X text 216 480 - sets Q; #X text 170 437 clear; #X text 216 561 - sets Q (default ?); -#X obj 59 266 +~; -#X obj 59 194 noise~; -#X obj 132 188 hsl 128 15 100 2000 1 0 empty \$0-Freq_set empty -2 -8 0 10 #dcdcdc #000000 #000000 0 1; -#X obj 227 210 hsl 128 15 0.1 5 1 0 empty \$0-Q_set empty -2 -8 0 10 #dcdcdc #000000 #000000 0 1; +#X obj 39 266 +~; +#X obj 39 194 noise~; +#X obj 112 188 hsl 128 15 100 2000 1 0 empty \$0-Freq_set empty -2 -8 0 10 #dcdcdc #000000 #000000 0 1; +#X obj 207 210 hsl 128 15 0.1 5 1 0 empty \$0-Q_set empty -2 -8 0 10 #dcdcdc #000000 #000000 0 1; #N canvas 614 353 726 323 phase_response 0; #X obj 101 101 cnv 15 298 98 empty empty empty 20 12 0 14 #e0e0e0 #404040 0; #N canvas 0 22 450 278 (subpatch) 0; @@ -244,44 +244,44 @@ #X connect 8 2 5 0; #X connect 10 0 7 0; #X coords 0 -1 1 1 300 100 2 100 100; -#X restore 191 279 pd phase_response; -#X text 497 273 180ª; -#X text 498 370 -180ª; -#X text 497 323 0ª/360ª; +#X restore 201 279 pd phase_response; +#X text 507 273 180ª; +#X text 508 370 -180ª; +#X text 507 323 0ª/360ª; #X text 216 418 - signal whose phase will be shifted; #X text 216 437 - clears filter's memory; -#X text 161 187 frequency; -#X text 283 210 Q; -#X text 321 383 500; -#X text 251 383 100; -#X text 189 383 0; -#X text 382 383 2k; -#X text 472 383 22k; -#X text 175 294 p; -#X text 175 307 h; -#X text 175 320 a; -#X text 175 333 s; -#X text 175 344 e; +#X text 141 187 frequency; +#X text 263 210 Q; +#X text 331 383 500; +#X text 261 383 100; +#X text 199 383 0; +#X text 392 383 2k; +#X text 482 383 22k; +#X text 185 294 p; +#X text 185 307 h; +#X text 185 320 a; +#X text 185 333 s; +#X text 185 344 e; #X text 216 457 - sets filter's frequency point to be shifted to 180º; #X text 216 512 - the phase shifted signal; -#X text 83 143 In this example \, we add the phase shifted signal to the original \, which cancels frequencies by phase opposition., f 65; -#X text 83 82 [phaseshift~] is a 2nd allpass filter \, which keeps the gain and only alters the phase from 0 (at 0 hz) to 360º (at the Nyquist frequency). The frequency at which it shifts to 180º is specified as the filter's frequency and the steepness of the curve is determined by the Q parameter (see graph below)., f 65; +#X text 44 142 In this example \, we add the phase shifted signal to the original \, which cancels frequencies by phase opposition., f 79; #X text 151 545 1) float; -#X obj 224 233 nbx 4 14 -1e+37 1e+37 0 0 \$0-Q empty empty 0 -8 0 10 #dcdcdc #000000 #000000 0 256; -#X obj 129 212 nbx 4 14 -1e+37 1e+37 0 0 \$0-Freq empty empty 0 -8 0 10 #dcdcdc #000000 #000000 0 256; -#X obj 74 235 cyclone/phaseshift~; -#X text 91 382 frequency (hz):; +#X obj 204 233 nbx 4 14 -1e+37 1e+37 0 0 \$0-Q empty empty 0 -8 0 10 #dcdcdc #000000 #000000 0 256; +#X obj 109 212 nbx 4 14 -1e+37 1e+37 0 0 \$0-Freq empty empty 0 -8 0 10 #dcdcdc #000000 #000000 0 256; +#X obj 54 235 cyclone/phaseshift~; +#X text 101 382 frequency (hz):; #X text 216 543 - sets filter's frequency (default 1); -#X obj 59 304 output~; -#X text 383 254 phase response:; +#X obj 39 304 output~; +#X text 401 257 phase response:; #X obj 3 3 ./header phaseshift~; -#X text 22 61 2nd order Allpass filter; -#X obj 416 203 declare -stdpath ./; -#X connect 16 0 48 0; +#X text 22 60 2nd order Allpass filter; +#X obj 416 211 declare -stdpath ./; +#X text 44 82 [phaseshift~] is a 2nd allpass filter \, which keeps the gain and only alters the phase from 0 (at 0 hz) to 360º (at the Nyquist frequency). The frequency at which it shifts to 180º is specified as the filter's frequency (minimum 1o hz) and the steepness of the curve is determined by the Q parameter - see graph below., f 79; +#X connect 16 0 47 0; #X connect 17 0 16 0; -#X connect 17 0 45 0; -#X connect 18 0 44 0; -#X connect 19 0 43 0; -#X connect 43 0 45 2; -#X connect 44 0 45 1; -#X connect 45 0 16 1; +#X connect 17 0 44 0; +#X connect 18 0 43 0; +#X connect 19 0 42 0; +#X connect 42 0 44 2; +#X connect 43 0 44 1; +#X connect 44 0 16 1; diff --git a/Libraries/pd-else b/Libraries/pd-else index 999c6d6cc8..cbd5a5f7c3 160000 --- a/Libraries/pd-else +++ b/Libraries/pd-else @@ -1 +1 @@ -Subproject commit 999c6d6cc8f9a61716476016bc5e084cbe0bdd8e +Subproject commit cbd5a5f7c3cedbfbefd77b651e43c3b5c0b1864f diff --git a/Resources/Documentation/cyclone/zl.md b/Resources/Documentation/cyclone/zl.md index 29d138044d..3d137c1df4 100644 --- a/Resources/Documentation/cyclone/zl.md +++ b/Resources/Documentation/cyclone/zl.md @@ -1,5 +1,5 @@ --- -title: zl +title: zl zl.ecils zl.group zl.iter zl.join zl.len zl.mth zl.nth zl.reg zl.rev zl.rot zl.sect zl.slice zl.sort zl.sub zl.union zl.change zl.compare zl.delace zl.filter zl.lace zl.lookup zl.median zl.queue zl.scramble zl.stack zl.stream zl.sum zl.thin zl.unique zl.indexmap zl.swap description: list processor diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index 64787af295..7ee2fd5783 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -807,6 +807,38 @@ void xbendout2_setup(); void xnotein_setup(); void xnoteout_setup(); void zl_setup(); +void setup_zl0x2eecils(); +void setup_zl0x2egroup(); +void setup_zl0x2eiter(); +void setup_zl0x2ejoin(); +void setup_zl0x2elen(); +void setup_zl0x2emth(); +void setup_zl0x2enth(); +void setup_zl0x2ereg(); +void setup_zl0x2erev(); +void setup_zl0x2erot(); +void setup_zl0x2esect(); +void setup_zl0x2eslice(); +void setup_zl0x2esort(); +void setup_zl0x2esub(); +void setup_zl0x2eunion(); +void setup_zl0x2echange(); +void setup_zl0x2ecompare(); +void setup_zl0x2edelace(); +void setup_zl0x2efilter(); +void setup_zl0x2elace(); +void setup_zl0x2elookup(); +void setup_zl0x2emedian(); +void setup_zl0x2equeue(); +void setup_zl0x2escramble(); +void setup_zl0x2estack(); +void setup_zl0x2estream(); +void setup_zl0x2esum(); +void setup_zl0x2ethin(); +void setup_zl0x2eunique(); +void setup_zl0x2eindexmap(); +void setup_zl0x2eswap(); + void acos_tilde_setup(); void acosh_tilde_setup(); void allpass_tilde_setup(); @@ -953,7 +985,6 @@ void setup_canvas0x2epos(); void setup_canvas0x2esetname(); void setup_canvas0x2evis(); void setup_canvas0x2ezoom(); -void setup_canvas0x2efile(); void ceil_setup(); void ceil_tilde_setup(); void cents2ratio_setup(); @@ -1204,6 +1235,14 @@ void setup_rotate0x2emc_tilde(); void pipe2_setup(); void circuit_tilde_setup(); +void autofade20x2emc_tilde(); +void autofade2_tilde(); +void mtx0x2emc_tilde(); +void pan_tilde(); +void pan0x2emc_tilde(); +void xgate20x2emc_tilde(); +void xselect20x2emc_tilde(); + void pm_tilde_setup(); void pm2_tilde_setup(); void pm4_tilde_setup(); @@ -1410,7 +1449,6 @@ void Setup::initialiseELSE() setup_canvas0x2esetname(); setup_canvas0x2evis(); setup_canvas0x2ezoom(); - setup_canvas0x2efile(); ceil_setup(); ceil_tilde_setup(); cents2ratio_setup(); @@ -1664,6 +1702,14 @@ void Setup::initialiseELSE() setup_rotate0x2emc_tilde(); pipe2_setup(); circuit_tilde_setup(); + + autofade20x2emc_tilde(); + autofade2_tilde(); + mtx0x2emc_tilde(); + pan_tilde(); + pan0x2emc_tilde(); + xgate20x2emc_tilde(); + xselect20x2emc_tilde(); pm_tilde_setup(); pm2_tilde_setup(); @@ -2327,7 +2373,37 @@ void Setup::initialiseCyclone() xnotein_setup(); xnoteout_setup(); zl_setup(); - + setup_zl0x2eecils(); + setup_zl0x2egroup(); + setup_zl0x2eiter(); + setup_zl0x2ejoin(); + setup_zl0x2elen(); + setup_zl0x2emth(); + setup_zl0x2enth(); + setup_zl0x2ereg(); + setup_zl0x2erev(); + setup_zl0x2erot(); + setup_zl0x2esect(); + setup_zl0x2eslice(); + setup_zl0x2esort(); + setup_zl0x2esub(); + setup_zl0x2eunion(); + setup_zl0x2echange(); + setup_zl0x2ecompare(); + setup_zl0x2edelace(); + setup_zl0x2efilter(); + setup_zl0x2elace(); + setup_zl0x2elookup(); + setup_zl0x2emedian(); + setup_zl0x2equeue(); + setup_zl0x2escramble(); + setup_zl0x2estack(); + setup_zl0x2estream(); + setup_zl0x2esum(); + setup_zl0x2ethin(); + setup_zl0x2eunique(); + setup_zl0x2eindexmap(); + setup_zl0x2eswap(); acos_tilde_setup(); acosh_tilde_setup(); allpass_tilde_setup(); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 11fce1a783..cdb36f94e3 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -205,8 +205,8 @@ class PluginProcessor : public AudioProcessor std::map> textEditorDialogs; - static inline String const else_version = "ELSE v1.0-rc10"; - static inline String const cyclone_version = "cyclone v0.8-0"; + static inline String const else_version = "ELSE v1.0-rc11"; + static inline String const cyclone_version = "cyclone v0.8-1"; static inline String const heavylib_version = "heavylib v0.3.1"; static inline String const gem_version = "Gem v0.94"; // this gets updated with live version data later diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index 486f86e2cd..3b8d39f572 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -38,7 +38,7 @@ struct ProjectInfo { static inline File const appDataDir = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory).getChildFile("plugdata"); - static inline String const versionSuffix = "-2"; + static inline String const versionSuffix = "-3"; static inline File const versionDataDir = appDataDir.getChildFile("Versions").getChildFile(ProjectInfo::versionString + versionSuffix); }; From feb34c1c6c726a76fb6d6573a4316d1bfd76a4cf Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 3 Feb 2024 17:58:05 +0100 Subject: [PATCH 0183/1030] Small ELSE update --- Libraries/pd-else | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/pd-else b/Libraries/pd-else index cbd5a5f7c3..52f9779bcf 160000 --- a/Libraries/pd-else +++ b/Libraries/pd-else @@ -1 +1 @@ -Subproject commit cbd5a5f7c3cedbfbefd77b651e43c3b5c0b1864f +Subproject commit 52f9779bcf5949d6a957b98231e7054ebd85428c From eb8a083f640e23e59ae175840fd06a3b699038de Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 3 Feb 2024 17:59:25 +0100 Subject: [PATCH 0184/1030] ELSE update fixes --- Source/Dialogs/AboutPanel.h | 2 +- Source/Pd/Setup.cpp | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Source/Dialogs/AboutPanel.h b/Source/Dialogs/AboutPanel.h index 435cf49851..4e136c3626 100644 --- a/Source/Dialogs/AboutPanel.h +++ b/Source/Dialogs/AboutPanel.h @@ -10,7 +10,7 @@ EXTERN char* pd_version; class AboutPanel : public Component { - String creditsText = " Thanks to Alex Mitchell and tomara-x for helping out with development, documentation and testing.\n\n - ELSE v1.0-rc10 by Alexandre Porres\n - cyclone v0.7-0 by Krzysztof Czaja, Hans-Christoph Steiner, Fred Jan Kraan, Alexandre Porres, Derek Kwan, Matt Barber et al.\n - Based on Camomile by Pierre Guillot\n - Inter font by Rasmus Andersson\n - Made with JUCE\n\n\nSpecial thanks to: Deskew Technologies, PowerUser64, DSBHproject, CFDAF, cotik1 , alfonso73, spamfunnel, ooroor, bla9kdog, KPY7030P, duddex, rubenlorenzo, mungbean, jamescorrea, Soundworlds-JO, vasilymilovidov, polarity, chee, Joshua A.C. Newman and ludnny for supporting this project\n\n\nThis program is published under the terms of the GPL3 license"; + String creditsText = " Thanks to Alex Mitchell and tomara-x for helping out with development, documentation and testing.\n\n - ELSE v1.0-rc11 by Alexandre Porres\n - cyclone v0.8-1 by Krzysztof Czaja, Hans-Christoph Steiner, Fred Jan Kraan, Alexandre Porres, Derek Kwan, Matt Barber et al.\n - Based on Camomile by Pierre Guillot\n - Inter font by Rasmus Andersson\n - Made with JUCE\n\n\nSpecial thanks to: Deskew Technologies, PowerUser64, DSBHproject, CFDAF, cotik1 , alfonso73, spamfunnel, ooroor, bla9kdog, KPY7030P, duddex, rubenlorenzo, mungbean, jamescorrea, Soundworlds-JO, vasilymilovidov, polarity, chee, Joshua A.C. Newman and ludnny for supporting this project\n\n\nThis program is published under the terms of the GPL3 license"; TextEditor credits; TextButton viewOnGithub; diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index 7ee2fd5783..060b253006 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -1235,13 +1235,12 @@ void setup_rotate0x2emc_tilde(); void pipe2_setup(); void circuit_tilde_setup(); -void autofade20x2emc_tilde(); -void autofade2_tilde(); -void mtx0x2emc_tilde(); -void pan_tilde(); -void pan0x2emc_tilde(); -void xgate20x2emc_tilde(); -void xselect20x2emc_tilde(); +void setup_autofade20x2emc_tilde(); +void setup_mtx0x2emc_tilde(); +void pan_tilde_setup(); +void setup_pan0x2emc_tilde(); +void setup_xgate20x2emc_tilde(); +void setup_xselect20x2emc_tilde(); void pm_tilde_setup(); void pm2_tilde_setup(); @@ -1703,13 +1702,12 @@ void Setup::initialiseELSE() pipe2_setup(); circuit_tilde_setup(); - autofade20x2emc_tilde(); - autofade2_tilde(); - mtx0x2emc_tilde(); - pan_tilde(); - pan0x2emc_tilde(); - xgate20x2emc_tilde(); - xselect20x2emc_tilde(); + setup_autofade20x2emc_tilde(); + setup_mtx0x2emc_tilde(); + pan_tilde_setup(); + setup_pan0x2emc_tilde(); + setup_xgate20x2emc_tilde(); + setup_xselect20x2emc_tilde(); pm_tilde_setup(); pm2_tilde_setup(); From bd5ff23af2acca09969455f15c4285edcc404311 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 4 Feb 2024 13:00:22 +0100 Subject: [PATCH 0185/1030] Windows compilation fix --- Libraries/cyclone/cyclone_objects/binaries/control/zl.c | 2 +- Libraries/pd-else | 2 +- Source/Pd/Setup.cpp | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Libraries/cyclone/cyclone_objects/binaries/control/zl.c b/Libraries/cyclone/cyclone_objects/binaries/control/zl.c index fa48c286eb..cb32b705f9 100644 --- a/Libraries/cyclone/cyclone_objects/binaries/control/zl.c +++ b/Libraries/cyclone/cyclone_objects/binaries/control/zl.c @@ -1713,7 +1713,7 @@ CYCLONE_OBJ_API void zl_setup(void){ { \ int ac = argc + 1; \ t_atom* av = malloc(ac * sizeof(t_atom)); \ - memcpy(av + sizeof(t_atom), argv, argc * sizeof(t_atom)); \ + memcpy(av + 1, argv, argc * sizeof(t_atom)); \ SETSYMBOL(av, gensym(#MODE)); \ return zl_new(s, ac, av); \ } \ diff --git a/Libraries/pd-else b/Libraries/pd-else index 52f9779bcf..c7042b5e7e 160000 --- a/Libraries/pd-else +++ b/Libraries/pd-else @@ -1 +1 @@ -Subproject commit 52f9779bcf5949d6a957b98231e7054ebd85428c +Subproject commit c7042b5e7ebb74c4faca5b293a77953f71edd730 diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index 060b253006..96ec46c500 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -1241,6 +1241,9 @@ void pan_tilde_setup(); void setup_pan0x2emc_tilde(); void setup_xgate20x2emc_tilde(); void setup_xselect20x2emc_tilde(); +void findfile_setup(); +void setup_autofade0x2emc_tilde(); +void wt2d_tilde_setup(); void pm_tilde_setup(); void pm2_tilde_setup(); @@ -1708,6 +1711,9 @@ void Setup::initialiseELSE() setup_pan0x2emc_tilde(); setup_xgate20x2emc_tilde(); setup_xselect20x2emc_tilde(); + findfile_setup(); + setup_autofade0x2emc_tilde(); + wt2d_tilde_setup(); pm_tilde_setup(); pm2_tilde_setup(); From 5fb413989c9e27b9833b4bc663bdca5981f0808d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 4 Feb 2024 15:51:53 +0100 Subject: [PATCH 0186/1030] Fixed pdlua bug --- Source/Objects/LuaObject.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Objects/LuaObject.h b/Source/Objects/LuaObject.h index ef01499152..3b3e3935b4 100644 --- a/Source/Objects/LuaObject.h +++ b/Source/Objects/LuaObject.h @@ -65,7 +65,7 @@ class LuaObject : public ObjectBase, public Timer { Image image; bool isSelected = false; moodycamel::ReaderWriterQueue guiQueue = moodycamel::ReaderWriterQueue(40); - + Value zoomScale; std::unique_ptr textEditor; std::unique_ptr saveDialog; @@ -79,7 +79,8 @@ class LuaObject : public ObjectBase, public Timer { pdlua->gfx.plugdata_callback_target = this; } - cnv->zoomScale.addListener(this); + zoomScale.referTo(cnv->zoomScale); + zoomScale.addListener(this); startTimerHz(60); // Check for paint messages at 60hz (but we only really repaint when needed) } @@ -89,7 +90,7 @@ class LuaObject : public ObjectBase, public Timer { { pdlua->gfx.plugdata_callback_target = NULL; } - cnv->zoomScale.removeListener(this); + zoomScale.removeListener(this); } Rectangle getPdBounds() override From 91a123c0092f67c3b9d165d0c2e8725aa12dee70 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 5 Feb 2024 12:49:31 +0100 Subject: [PATCH 0187/1030] Added md files for new [zl.*] object aliases --- Resources/Documentation/cyclone/zl.change.md | 53 +++++++++ Resources/Documentation/cyclone/zl.compare.md | 53 +++++++++ Resources/Documentation/cyclone/zl.delace.md | 53 +++++++++ Resources/Documentation/cyclone/zl.ecils.md | 53 +++++++++ Resources/Documentation/cyclone/zl.filter.md | 53 +++++++++ Resources/Documentation/cyclone/zl.group.md | 53 +++++++++ .../Documentation/cyclone/zl.indexmap.md | 53 +++++++++ Resources/Documentation/cyclone/zl.iter.md | 53 +++++++++ Resources/Documentation/cyclone/zl.join.md | 53 +++++++++ Resources/Documentation/cyclone/zl.lace.md | 53 +++++++++ Resources/Documentation/cyclone/zl.len.md | 53 +++++++++ Resources/Documentation/cyclone/zl.lookup.md | 53 +++++++++ Resources/Documentation/cyclone/zl.md | 106 +++++++++--------- Resources/Documentation/cyclone/zl.median.md | 53 +++++++++ Resources/Documentation/cyclone/zl.mth.md | 53 +++++++++ Resources/Documentation/cyclone/zl.nth.md | 53 +++++++++ Resources/Documentation/cyclone/zl.queue.md | 53 +++++++++ Resources/Documentation/cyclone/zl.reg.md | 53 +++++++++ Resources/Documentation/cyclone/zl.rev.md | 53 +++++++++ Resources/Documentation/cyclone/zl.rot.md | 53 +++++++++ .../Documentation/cyclone/zl.scramble.md | 53 +++++++++ Resources/Documentation/cyclone/zl.sect.md | 53 +++++++++ Resources/Documentation/cyclone/zl.slice.md | 53 +++++++++ Resources/Documentation/cyclone/zl.sort.md | 53 +++++++++ Resources/Documentation/cyclone/zl.stack.md | 53 +++++++++ Resources/Documentation/cyclone/zl.stream.md | 53 +++++++++ Resources/Documentation/cyclone/zl.sub.md | 53 +++++++++ Resources/Documentation/cyclone/zl.sum.md | 53 +++++++++ Resources/Documentation/cyclone/zl.swap.md | 53 +++++++++ Resources/Documentation/cyclone/zl.thin.md | 53 +++++++++ Resources/Documentation/cyclone/zl.union.md | 53 +++++++++ Resources/Documentation/cyclone/zl.unique.md | 53 +++++++++ 32 files changed, 1696 insertions(+), 53 deletions(-) create mode 100644 Resources/Documentation/cyclone/zl.change.md create mode 100644 Resources/Documentation/cyclone/zl.compare.md create mode 100644 Resources/Documentation/cyclone/zl.delace.md create mode 100644 Resources/Documentation/cyclone/zl.ecils.md create mode 100644 Resources/Documentation/cyclone/zl.filter.md create mode 100644 Resources/Documentation/cyclone/zl.group.md create mode 100644 Resources/Documentation/cyclone/zl.indexmap.md create mode 100644 Resources/Documentation/cyclone/zl.iter.md create mode 100644 Resources/Documentation/cyclone/zl.join.md create mode 100644 Resources/Documentation/cyclone/zl.lace.md create mode 100644 Resources/Documentation/cyclone/zl.len.md create mode 100644 Resources/Documentation/cyclone/zl.lookup.md create mode 100644 Resources/Documentation/cyclone/zl.median.md create mode 100644 Resources/Documentation/cyclone/zl.mth.md create mode 100644 Resources/Documentation/cyclone/zl.nth.md create mode 100644 Resources/Documentation/cyclone/zl.queue.md create mode 100644 Resources/Documentation/cyclone/zl.reg.md create mode 100644 Resources/Documentation/cyclone/zl.rev.md create mode 100644 Resources/Documentation/cyclone/zl.rot.md create mode 100644 Resources/Documentation/cyclone/zl.scramble.md create mode 100644 Resources/Documentation/cyclone/zl.sect.md create mode 100644 Resources/Documentation/cyclone/zl.slice.md create mode 100644 Resources/Documentation/cyclone/zl.sort.md create mode 100644 Resources/Documentation/cyclone/zl.stack.md create mode 100644 Resources/Documentation/cyclone/zl.stream.md create mode 100644 Resources/Documentation/cyclone/zl.sub.md create mode 100644 Resources/Documentation/cyclone/zl.sum.md create mode 100644 Resources/Documentation/cyclone/zl.swap.md create mode 100644 Resources/Documentation/cyclone/zl.thin.md create mode 100644 Resources/Documentation/cyclone/zl.union.md create mode 100644 Resources/Documentation/cyclone/zl.unique.md diff --git a/Resources/Documentation/cyclone/zl.change.md b/Resources/Documentation/cyclone/zl.change.md new file mode 100644 index 0000000000..e14ae9bcce --- /dev/null +++ b/Resources/Documentation/cyclone/zl.change.md @@ -0,0 +1,53 @@ +--- +title: zl.change + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.compare.md b/Resources/Documentation/cyclone/zl.compare.md new file mode 100644 index 0000000000..7af94d4199 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.compare.md @@ -0,0 +1,53 @@ +--- +title: zl.compare + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.delace.md b/Resources/Documentation/cyclone/zl.delace.md new file mode 100644 index 0000000000..ad97f79ffa --- /dev/null +++ b/Resources/Documentation/cyclone/zl.delace.md @@ -0,0 +1,53 @@ +--- +title: zl.delace + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.ecils.md b/Resources/Documentation/cyclone/zl.ecils.md new file mode 100644 index 0000000000..16703e87cd --- /dev/null +++ b/Resources/Documentation/cyclone/zl.ecils.md @@ -0,0 +1,53 @@ +--- +title: zl.ecils + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.filter.md b/Resources/Documentation/cyclone/zl.filter.md new file mode 100644 index 0000000000..dba6bac2b2 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.filter.md @@ -0,0 +1,53 @@ +--- +title: zl.filter + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.group.md b/Resources/Documentation/cyclone/zl.group.md new file mode 100644 index 0000000000..346ee446b1 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.group.md @@ -0,0 +1,53 @@ +--- +title: zl.group + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.indexmap.md b/Resources/Documentation/cyclone/zl.indexmap.md new file mode 100644 index 0000000000..33c69d9357 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.indexmap.md @@ -0,0 +1,53 @@ +--- +title: zl.indexmap + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.iter.md b/Resources/Documentation/cyclone/zl.iter.md new file mode 100644 index 0000000000..3c94572e99 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.iter.md @@ -0,0 +1,53 @@ +--- +title: zl.iter + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.join.md b/Resources/Documentation/cyclone/zl.join.md new file mode 100644 index 0000000000..433afd7e60 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.join.md @@ -0,0 +1,53 @@ +--- +title: zl.join + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.lace.md b/Resources/Documentation/cyclone/zl.lace.md new file mode 100644 index 0000000000..cda8732022 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.lace.md @@ -0,0 +1,53 @@ +--- +title: zl.lace + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.len.md b/Resources/Documentation/cyclone/zl.len.md new file mode 100644 index 0000000000..877becfbc4 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.len.md @@ -0,0 +1,53 @@ +--- +title: zl.len + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.lookup.md b/Resources/Documentation/cyclone/zl.lookup.md new file mode 100644 index 0000000000..a0a22102ee --- /dev/null +++ b/Resources/Documentation/cyclone/zl.lookup.md @@ -0,0 +1,53 @@ +--- +title: zl.lookup + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.md b/Resources/Documentation/cyclone/zl.md index 3d137c1df4..98ea563b43 100644 --- a/Resources/Documentation/cyclone/zl.md +++ b/Resources/Documentation/cyclone/zl.md @@ -1,53 +1,53 @@ ---- -title: zl zl.ecils zl.group zl.iter zl.join zl.len zl.mth zl.nth zl.reg zl.rev zl.rot zl.sect zl.slice zl.sort zl.sub zl.union zl.change zl.compare zl.delace zl.filter zl.lace zl.lookup zl.median zl.queue zl.scramble zl.stack zl.stream zl.sum zl.thin zl.unique zl.indexmap zl.swap - -description: list processor - -categories: - - object - -pdcategory: cyclone, Data Management - -arguments: -- type: float - description: maximum list size (optional, 1 - 32767) - default: 256 -- type: symbol - description: mode - default: - -inlets: - 1st: - - type: bang - description: mode dependent operation on current data see - - type: anything - description: one or more element messages to be processed - 2nd: - - type: anything - description: depends on the mode, see [pd examples] - -outlets: - 1st: - - type: anything - description: output according to the mode: see details in [pd examples] - 2nd: - - type: anything - description: output according to the mode: see details in [pd examples] - -flags: - - name: @zlmaxsize - description: max list size (1 - 32767) - default: 256 - -methods: - - type: mode - description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) - - type: zlclear - description: clears mode's arguments and received data in both inlets - - type: zlmaxsize - description: sets the maximum list size (1 - 32767, default 256) - -draft: true ---- - -[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). +--- +title: zl + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.median.md b/Resources/Documentation/cyclone/zl.median.md new file mode 100644 index 0000000000..3d7c89d938 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.median.md @@ -0,0 +1,53 @@ +--- +title: zl.median + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.mth.md b/Resources/Documentation/cyclone/zl.mth.md new file mode 100644 index 0000000000..c0e857d0e4 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.mth.md @@ -0,0 +1,53 @@ +--- +title: zl.mth + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.nth.md b/Resources/Documentation/cyclone/zl.nth.md new file mode 100644 index 0000000000..168d3d82c1 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.nth.md @@ -0,0 +1,53 @@ +--- +title: zl.nth + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.queue.md b/Resources/Documentation/cyclone/zl.queue.md new file mode 100644 index 0000000000..298b233449 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.queue.md @@ -0,0 +1,53 @@ +--- +title: zl.queue + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.reg.md b/Resources/Documentation/cyclone/zl.reg.md new file mode 100644 index 0000000000..0f014d1e59 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.reg.md @@ -0,0 +1,53 @@ +--- +title: zl.reg + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.rev.md b/Resources/Documentation/cyclone/zl.rev.md new file mode 100644 index 0000000000..8628e35674 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.rev.md @@ -0,0 +1,53 @@ +--- +title: zl.rev + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.rot.md b/Resources/Documentation/cyclone/zl.rot.md new file mode 100644 index 0000000000..69c7199ce4 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.rot.md @@ -0,0 +1,53 @@ +--- +title: zl.rot + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.scramble.md b/Resources/Documentation/cyclone/zl.scramble.md new file mode 100644 index 0000000000..c5be4d6a77 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.scramble.md @@ -0,0 +1,53 @@ +--- +title: zl.scramble + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.sect.md b/Resources/Documentation/cyclone/zl.sect.md new file mode 100644 index 0000000000..954ef3958c --- /dev/null +++ b/Resources/Documentation/cyclone/zl.sect.md @@ -0,0 +1,53 @@ +--- +title: zl.sect + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.slice.md b/Resources/Documentation/cyclone/zl.slice.md new file mode 100644 index 0000000000..66c6b8cccb --- /dev/null +++ b/Resources/Documentation/cyclone/zl.slice.md @@ -0,0 +1,53 @@ +--- +title: zl.slice + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.sort.md b/Resources/Documentation/cyclone/zl.sort.md new file mode 100644 index 0000000000..7eb1540af3 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.sort.md @@ -0,0 +1,53 @@ +--- +title: zl.sort + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.stack.md b/Resources/Documentation/cyclone/zl.stack.md new file mode 100644 index 0000000000..5105699072 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.stack.md @@ -0,0 +1,53 @@ +--- +title: zl.stack + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.stream.md b/Resources/Documentation/cyclone/zl.stream.md new file mode 100644 index 0000000000..a3c377f6ce --- /dev/null +++ b/Resources/Documentation/cyclone/zl.stream.md @@ -0,0 +1,53 @@ +--- +title: zl.stream + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.sub.md b/Resources/Documentation/cyclone/zl.sub.md new file mode 100644 index 0000000000..abc7d73cec --- /dev/null +++ b/Resources/Documentation/cyclone/zl.sub.md @@ -0,0 +1,53 @@ +--- +title: zl.sub + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.sum.md b/Resources/Documentation/cyclone/zl.sum.md new file mode 100644 index 0000000000..5bdf6069da --- /dev/null +++ b/Resources/Documentation/cyclone/zl.sum.md @@ -0,0 +1,53 @@ +--- +title: zl.sum + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.swap.md b/Resources/Documentation/cyclone/zl.swap.md new file mode 100644 index 0000000000..28fc7a4260 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.swap.md @@ -0,0 +1,53 @@ +--- +title: zl.swap + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.thin.md b/Resources/Documentation/cyclone/zl.thin.md new file mode 100644 index 0000000000..878e731f28 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.thin.md @@ -0,0 +1,53 @@ +--- +title: zl.thin + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.union.md b/Resources/Documentation/cyclone/zl.union.md new file mode 100644 index 0000000000..29c5525d87 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.union.md @@ -0,0 +1,53 @@ +--- +title: zl.union + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). diff --git a/Resources/Documentation/cyclone/zl.unique.md b/Resources/Documentation/cyclone/zl.unique.md new file mode 100644 index 0000000000..f8ec4839e1 --- /dev/null +++ b/Resources/Documentation/cyclone/zl.unique.md @@ -0,0 +1,53 @@ +--- +title: zl.unique + +description: list processor + +categories: + - object + +pdcategory: cyclone, Data Management + +arguments: +- type: float + description: maximum list size (optional, 1 - 32767) + default: 256 +- type: symbol + description: mode + default: + +inlets: + 1st: + - type: bang + description: mode dependent operation on current data see + - type: anything + description: one or more element messages to be processed + 2nd: + - type: anything + description: depends on the mode, see [pd examples] + +outlets: + 1st: + - type: anything + description: output according to the mode: see details in [pd examples] + 2nd: + - type: anything + description: output according to the mode: see details in [pd examples] + +flags: + - name: @zlmaxsize + description: max list size (1 - 32767) + default: 256 + +methods: + - type: mode + description: sets the mode (change, compare, delace, ecils, group, indexmap, iter, join, lace, len, lookup, median, mth, nth, queue, reg, rev, rot, sect, scramble, slice, sort, stack, stream, sub, sum, swap, thin, union or unique) + - type: zlclear + description: clears mode's arguments and received data in both inlets + - type: zlmaxsize + description: sets the maximum list size (1 - 32767, default 256) + +draft: true +--- + +[zl] processes messages with one or more elements ("list messages' or "anything") according to a mode (set via argument/message). From fbcb8af0b9fd0bb42b34fb7ee33a119be3979b62 Mon Sep 17 00:00:00 2001 From: Alex Mitchell Date: Tue, 6 Feb 2024 17:14:50 +1030 Subject: [PATCH 0188/1030] Fix line drawing for overlay menu groups that have more than 2 items --- Source/Dialogs/OverlayDisplaySettings.h | 74 +++++++++++++++---------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/Source/Dialogs/OverlayDisplaySettings.h b/Source/Dialogs/OverlayDisplaySettings.h index 1ef325960e..81062457d7 100644 --- a/Source/Dialogs/OverlayDisplaySettings.h +++ b/Source/Dialogs/OverlayDisplaySettings.h @@ -29,6 +29,7 @@ class OverlayDisplaySettings : public Component { Overlay group; public: + OverlaySelector(ValueTree const& settings, Overlay groupType, String nameOfSetting, String nameOfGroup, String toolTipString) : groupName(std::move(nameOfGroup)) , settingName(std::move(nameOfSetting)) @@ -136,17 +137,24 @@ class OverlayDisplaySettings : public Component { connectionLabel.setFont(Fonts::getSemiBoldFont().withHeight(14)); addAndMakeVisible(connectionLabel); - buttonGroups.add(new OverlaySelector(overlayTree, Origin, "origin", "Origin", "Origin point of canvas")); - buttonGroups.add(new OverlaySelector(overlayTree, Border, "border", "Border", "Plugin / window workspace size")); - buttonGroups.add(new OverlaySelector(overlayTree, Index, "index", "Index", "Object index in patch")); - // buttonGroups.add(new OverlaySelector(overlayTree, Coordinate, "coordinate", "Coordinate", "Object coordinate in patch")); - buttonGroups.add(new OverlaySelector(overlayTree, ActivationState, "activation_state", "Activity", "Object activity")); - buttonGroups.add(new OverlaySelector(overlayTree, Direction, "direction", "Direction", "Direction of connections")); - buttonGroups.add(new OverlaySelector(overlayTree, Order, "order", "Order", "Trigger order of multiple outlets")); - buttonGroups.add(new OverlaySelector(overlayTree, Behind, "behind", "Behind", "Connection cables behind objects")); - - for (auto* buttonGroup : buttonGroups) { - addAndMakeVisible(buttonGroup); + canvas.add(new OverlaySelector(overlayTree, Origin, "origin", "Origin", "Origin point of canvas")); + canvas.add(new OverlaySelector(overlayTree, Border, "border", "Border", "Plugin / window workspace size")); + + object.add(new OverlaySelector(overlayTree, Index, "index", "Index", "Object index in patch")); + object.add(new OverlaySelector(overlayTree, ActivationState, "activation_state", "Activity", "Object activity")); + + connection.add(new OverlaySelector(overlayTree, Direction, "direction", "Direction", "Direction of connections")); + connection.add(new OverlaySelector(overlayTree, Order, "order", "Order", "Trigger order of multiple outlets")); + connection.add(new OverlaySelector(overlayTree, Behind, "behind", "Behind", "Connection cables behind objects")); + + groups.add(&canvas); + groups.add(&object); + groups.add(&connection); + + for (auto& group : groups) { + for (auto& item : *group) { + addAndMakeVisible(item); + } } setSize(200, 505); } @@ -160,38 +168,42 @@ class OverlayDisplaySettings : public Component { auto const spacing = 2; canvasLabel.setBounds(bounds.removeFromTop(labelHeight)); - buttonGroups[OverlayOrigin]->setBounds(bounds.removeFromTop(itemHeight)); - buttonGroups[OverlayBorder]->setBounds(bounds.removeFromTop(itemHeight)); + for (auto& item : canvas) { + item->setBounds(bounds.removeFromTop(itemHeight)); + } bounds.removeFromTop(spacing); objectLabel.setBounds(bounds.removeFromTop(labelHeight)); - buttonGroups[OverlayIndex]->setBounds(bounds.removeFromTop(itemHeight)); - - // doesn't exist yet - // buttonGroups[OverlayCoordinate].setBounds(bounds.removeFromTop(28)); - buttonGroups[OverlayActivationState]->setBounds(bounds.removeFromTop(itemHeight)); + for (auto& item : object) { + item->setBounds(bounds.removeFromTop(itemHeight)); + } bounds.removeFromTop(spacing); connectionLabel.setBounds(bounds.removeFromTop(labelHeight)); - buttonGroups[OverlayDirection]->setBounds(bounds.removeFromTop(itemHeight)); - buttonGroups[OverlayOrder]->setBounds(bounds.removeFromTop(itemHeight)); - buttonGroups[OverlayConnectionsBehind]->setBounds(bounds.removeFromTop(itemHeight)); + for (auto& item : connection) { + item->setBounds(bounds.removeFromTop(itemHeight)); + } setSize(200, bounds.getY() + 5); } void paint(Graphics& g) override { - auto firstPanelBounds = buttonGroups[OverlayOrigin]->getBounds().getUnion(buttonGroups[OverlayBorder]->getBounds()); - auto secondPanelBounds = buttonGroups[OverlayIndex]->getBounds().getUnion(buttonGroups[OverlayActivationState]->getBounds()); - auto thirdPanelBounds = buttonGroups[OverlayDirection]->getBounds().getUnion(buttonGroups[OverlayConnectionsBehind]->getBounds()); + for (auto& group : groups) { + auto groupBounds = group->getFirst()->getBounds().getUnion(group->getLast()->getBounds()); - for (auto& bounds : std::vector> { firstPanelBounds, secondPanelBounds, thirdPanelBounds }) { + // draw background rectangle g.setColour(findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.035f)); - g.fillRoundedRectangle(bounds.toFloat(), Corners::largeCornerRadius); + g.fillRoundedRectangle(groupBounds.toFloat(), Corners::largeCornerRadius); + // draw outline rectangle g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); - g.drawRoundedRectangle(bounds.toFloat(), Corners::largeCornerRadius, 1.0f); - g.drawHorizontalLine(bounds.getCentreY(), bounds.getX(), bounds.getRight()); + g.drawRoundedRectangle(groupBounds.toFloat(), Corners::largeCornerRadius, 1.0f); + + // draw lines between items + for (auto& item : *group){ + if ((group->size() >= 2) && (item != group->getLast())) + g.drawHorizontalLine(item->getBottom(), groupBounds.getX(), groupBounds.getRight()); + } } } @@ -224,7 +236,11 @@ class OverlayDisplaySettings : public Component { AltDisplay }; - OwnedArray buttonGroups; + Array*> groups; + + OwnedArray canvas; + OwnedArray object; + OwnedArray connection; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OverlayDisplaySettings) }; From 54377ff22ce2a22d28ed5556a29314b407f36475 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 6 Feb 2024 12:33:09 +0100 Subject: [PATCH 0189/1030] Better alternative if std::filesystem is unavailable --- .gitmodules | 9 +- Libraries/cpath/LICENSE | 21 - Libraries/cpath/README.md | 128 -- Libraries/cpath/cpath.h | 2506 ----------------------------------- Libraries/ghc_filesystem | 1 + Libraries/tinydir/tinydir.h | 838 ------------ Source/Utility/OSUtils.cpp | 58 +- 7 files changed, 13 insertions(+), 3548 deletions(-) delete mode 100644 Libraries/cpath/LICENSE delete mode 100644 Libraries/cpath/README.md delete mode 100644 Libraries/cpath/cpath.h create mode 160000 Libraries/ghc_filesystem delete mode 100644 Libraries/tinydir/tinydir.h diff --git a/.gitmodules b/.gitmodules index c2296b90e3..7bd4ec1cb3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,12 +16,6 @@ [submodule "Libraries/clap-juce-extensions"] path = Libraries/clap-juce-extensions url = https://github.com/free-audio/clap-juce-extensions.git -[submodule "Libraries/filesystem"] - path = Libraries/filesystem - url = https://github.com/boostorg/filesystem.git -[submodule "Libraries/cpath"] - path = Libraries/cpath - url = https://github.com/BraedonWooding/cpath [submodule "Libraries/pd-else"] path = Libraries/pd-else url = https://github.com/timothyschoen/pd-else.git @@ -42,3 +36,6 @@ [submodule "Libraries/Gem"] path = Libraries/Gem url = https://github.com/plugdata-team/plugdata-gem.git +[submodule "Libraries/ghc_filesystem"] + path = Libraries/ghc_filesystem + url = https://github.com/gulrak/filesystem.git diff --git a/Libraries/cpath/LICENSE b/Libraries/cpath/LICENSE deleted file mode 100644 index 0fd3795b6c..0000000000 --- a/Libraries/cpath/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Braedon - -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. diff --git a/Libraries/cpath/README.md b/Libraries/cpath/README.md deleted file mode 100644 index 46ab6e6044..0000000000 --- a/Libraries/cpath/README.md +++ /dev/null @@ -1,128 +0,0 @@ -# CPath - -![](https://github.com/BraedonWooding/cpath/workflows/CI/badge.svg) - -> Made By Braedon Wooding - -> Fast (see benchmarks at the bottom), efficient (optimised towards reducing dynamic allocations), and simple - -A simple and nice looking cross platform C FileSystem. I'll put up some documentation and tests up soon. - -It worked out really nicely from my basic idea in my head. Check out the `example.c` file if you want some basic idea of how it works. - -## Why - -Because I wanted a filesystem for my IMGUI Widget library but I didn't want to only support C++17 so I needed a preferably early standardised C++ (or C) library. While there existed plenty of variants their APIs weren't particularly nice or they didn't try to reduce includes or various other issues. The key thing for me was wanting to just have a single while loop function to both check if there is a next one and then assign the next one rather than having to split it into potentially 3! Which some had (i.e. a hasNext, a getFile, and a moveNext). - -I also felt that the code was quite obfuscated for some and it felt like they overused the #ifdefs to try to get as much similar as possible rather than trying to just make it more readable at the cost of a small amount of local duplication. - -I chose to make this very young i.e. C89/C99 ish since I was already going quite far with making it work with all versions of C++. - -## What this aims to be and what it doesn't aim to be - -- A simple and straightforward lite filesystem. -- The ability to create abstract paths and canonicalise them into real ones -- Very lite cpp bindings for C++ pre 17 (does include operators however) -- Meant to be highly extensible by the user so everything is exposed -- NOT make any overarching architectural designs that could pollute the user's paradigm -- NOT a replacement for a more heavy duty library like `std::filesystem` -- NOT to dictate anything to do with opening/handling files -- NOT to dictate anything to do with stuff like watching files or creating files - -## Credits - -I would like to put a thanks out to a lot of other similar libraries for giving me some source to compare against to make sure I was using the APIs correctly and some ideas for how to structure my API. - -## Comparisons against other libraries - -> If you want performance comparisons check out the benchmarks, TLDR though is that CPath is often around 3x faster than other libraries and even faster than `find`. - -CPath offers a variety of features such as; - -- The ability to emplace directories and save the old directory to be able to restore it later on - - This allows you to traverse it without having to use recursion or a custom stack (via a linked list or similar) allowing infinite traversal (within limits of RAM) - - While TinyDir offers emplacing it won't store the old one and you can't restore it this makes recursing using it limited and requires some external stack or queue. - - Cute files offers no such feature -- The ability to cache files and refer to them by number and then custom sort them - - Cute files offers no such feature - - TinyDir offers the ability to cache them but not custom sort you also can't refresh the cache -- Fully C++ Bindings in a familiar style including operators for paths -- The ability to concatenate paths together and compare them in an easy way - - Paths seem to be fully exempt from the other libraries except as just a 'string' -- Really efficient 'open file' (that is get information about a file from a path) - - Only requires a single system call! - - TinyDir has a similar function but it requires opening the parent directory and finding it from the iterator, this is very prone to races and is significantly slower requiring a lot more syscalls - - Up to O(n) minimum system calls - - Cute files offers no such feature -- Extensive tests for all features - - Cute files has a lot of functions marked with 'not tested' making it potentially highly unstable - - TinyDir has few tests that test edge cases or performance but all around good coverage -- You can output file sizes using multiple different byte size representations - - intervals of 1024 IEC (KiB, GiB, ...), JEDEC (KB, GB, ...) - - and intervals of 1000 both in normal (kB, GB, ...) and upper/lower - - you can also change the word `bytes` and force it to be always a word or just `B` independent of above. - - Both tinydir and cute files offers no feature like this -- In my opinion it also looks much nicer (especially cpp bindings) you can use a single function in a while loop to iterate and its very nice -- There are some serious benchmarking work going on to make sure that we don't regress to a slower speed over time! - -CPath however has a few 'cons'; - -- It is much larger at around ~2.5k lines compared to TinyDir's 800 and Cute Files 500 - - Cute files is much more bare bones (not a bad thing!) and results in smaller binaries - - You can disable the CPP bindings in CPath if you want to reduce the footprint by about 600 lines - - Just `#define NO_CPP_BINDINGS` before include -- It requires some constant tables for it's suffix printing - - Only some small constant strings and I presume since the tables are pretty similar they'll be some optimisation or something. -- It doesn't load `stat` automatically this is mainly for optimisation purposes as it is often not needed unless you need time information or size. It'll load it automatically as needed if you use the functions to get time / size and you can load it yourself using `cpathGetFileInfo` (or `File::GetFileInfo` in cpp) if you wish. I wouldn't call this a con but it is something that could be an inconvenience - - Both tinydir and cute files always load stats every time you get a file in unix - - TinyDir also loads it in windows every time you get a file - - Cutefiles also loads it again every time it is used for time/size - -## Benchmarks - -> Model Name: MacBook Air - -> Model Identifier: MacBookAir7,2 - -> Processor Name: Intel Core i5 - -> Processor Speed: 1.6 GHz - -> Number of Processors: 1 - -> Total Number of Cores: 2 - -> L2 Cache (per Core): 256 KB - -> L3 Cache: 3 MB - -> Memory: 8 GB - -These were retrieved from the test file. I sorted them by time and did a small amount of formatting. I'll always provide a picture with each one for the purposes of comparison :). - -> Note: If you feel that I somehow wrote these scripts inefficiently please submit a PR with fixes and I'll run the benchmarks. Eventually we'll run these benchmarks on multiple machines (eventually) but for now it's easier to just not. - -### Recursive vs Stack through a large directory system - -> This test takes a while to run since creating the files can take a while and we are running a 100 tests to get a reasonable average. - -| Test | User | System | Wall | -| ------------------------ | ------ | ------ | ------ | -| CPath (Recursion in C) | 0.020s | 0.062s | 0.082s | -| CPath (Emplace in C) | 0.020s | 0.063s | 0.083s | -| CPath (Recursive in cpp) | 0.020s | 0.062s | 0.082s | -| CPath (Emplace in cpp) | 0.020s | 0.063s | 0.083s | -| find | 0.021s | 0.126s | 0.147s | -| Python (os.walk) | 0.156s | 0.081s | 0.237s | -| Cute Files | 0.041s | 0.243s | 0.284s | -| TinyDir | 0.050s | 0.244s | 0.294s | -| tree | 0.369s | 0.256s | 0.626s | - -Observations - -- This is done on `-O3` BUT has no difference (from what I can see) for `-O1` and `-O2` the only difference is `-O0` which is consistently around 5% slower. -- Yes this does show that CPath is faster than `find` which I'm quite proud of - - The user time is the same which is not that surprising since they both are 'well' written in C - - The system time is different indicating I do less syscalls -- Interestingly enough python beats both of the other libraries, I guess this comes to show that writing good code is all about optimising the bottle necks! Even though the user time is around 4-5x faster (still not amazing since mine runs about 8-10x faster) it is absolutely hammered by the system time being around 3x slower there. And since that is a bottle neck it results in the whole system running signifincantly slower. -- All methods are practically identical in performance for CPath. diff --git a/Libraries/cpath/cpath.h b/Libraries/cpath/cpath.h deleted file mode 100644 index d1b8398121..0000000000 --- a/Libraries/cpath/cpath.h +++ /dev/null @@ -1,2506 +0,0 @@ -/* -MIT License - -Copyright (c) 2019 Braedon Wooding - -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. -*/ - -#ifndef CPATH_H -#define CPATH_H - -#ifdef __cplusplus -extern "C" { -#endif - -/* - Note this library also works with C++ via a C++ provided binding - which is available for free just by including this on a C++ compiler - or with __cplusplus defined - - If you want: - - Unicode just add a #define CPATH_UNICODE or UNICODE or _UNICODE - - Custom Allocators just #define: - - CPATH_MALLOC and CPATH_FREE (make sure to define both) -*/ - -/* - Note on why this library offers no guarantees on reentrant systems - or no TOUTOC (and other race conditions): - - Often supporting this requires breaking a ton of compatability - and often the behaviour of each of the individual commands differs way - too much to make it consistent. Even when fully following it we can't - guarantee that you won't just misuse it and still cause the race conditions - - The majority (99%) of cases simply just don't care about it how often - are your files being written over... - - We have extra safety on things like opening directories and recursive - solutions to make sure that things like that don't happen. - - How do you want to handle it properly? You may want to just keep going - or you may want some way to unwind what you previously did, or just take - a snapshot of the system... It is too varied for us to offer a generalised - way. - - The majority of libraries don't offer it or they offer it to an extremely - limited number of commands with the others not having it, in my opinion - this is worse than just not offering it. Atleast we are consistent with - our guarantees - - File Systems are a mess and already are basically a gigantic global mess - trying to guarantee any sort of safety is not only a complexity mess but - also can give the wrong idea. - - You should be able to detect hard failures due to the change in a folder - and simply just re-call. Of course this could happen repeatedly but - come on... In reality it will occur once in a blue moon. - - Reloading files is easy it is just cpathLoadFiles -*/ - -/* - @TODO: - - Windows has lifted their max path so we could use the new functions - I don't think all functions have an alternative and it could be more messy - still look into it eventually. -*/ - -// custom allocators -#if !defined CPATH_MALLOC && !defined CPATH_FREE -#define CPATH_MALLOC(size) malloc(size) -#define CPATH_FREE(ptr) free(ptr) -#elif (defined CPATH_MALLOC && !defined CPATH_FREE) || \ - (defined CPATH_FREE && !defined CPATH_MALLOC) -#error "Can't define only free or only malloc have to define both or neither" -#endif - -// Support unicode -#if defined CPATH_UNICODE || (!defined UNICODE && defined _UNICODE) -#define UNICODE -#endif - -#if defined CPATH_UNICODE || (defined UNICODE && !defined _UNICODE) -#define _UNICODE -#endif - -#if !defined CPATH_UNICODE && (defined UNICODE || defined _UNICODE) -#define CPATH_UNICODE -#endif - -#ifdef _MSC_VER -#define _CPATH_FUNC_ static __inline -#elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L -#define _CPATH_FUNC_ static __inline__ -#else -#define _CPATH_FUNC_ static inline -#endif - -/* == #includes == */ - -#if defined _MSC_VER || defined __MINGW32__ -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include -#endif - -#ifdef _MSC_VER -#include -// Ignore all warnings to do with std functions not being 100% safe -// they are fine -#pragma warning(push) -#pragma warning (disable : 4996) -#else -#include -#include -#include -#include -#endif - -// NOTE: This has to appear before defined BSD -// since sys/param.h defines BSD given environment -#if defined __unix__ || (defined __APPLE__ && defined __MACH__) -#include -#endif - -#if defined __linux__ || defined BSD -#include -#endif - -#ifdef __MINGW32__ -#include -#endif - -#include -#include -#include -#include -#include -#include - -// Linux has a max of 255 (+1 for \0) I couldn't find a max on windows -// But since 260 > 256 it is a reasonable value that should be crossplatform -#define CPATH_MAX_FILENAME_LEN (256) - -/* Windows Unicode Support */ -#if defined _MSC_VER || defined __MINGW32__ -#define CPATH_STR(str) _TEXT(str) -#define cpath_str_length _tcslen -#define cpath_str_copy _tcscpy -#define cpath_strn_copy _tcsncpy -#define cpath_str_find_last_char _tcsrchr -#define cpath_str_compare _tcscmp -#define cpath_str_compare_safe _tcsncmp -#define cpath_fopen _tfopen -#define cpath_sprintf _stprintf -#define cpath_vsnprintf _vsntprintf -#else -#define CPATH_STR(str) str -#define cpath_str_length strlen -#define cpath_str_copy strcpy -#define cpath_strn_copy strncpy -#define cpath_str_find_last_char strrchr -#define cpath_str_compare strcmp -#define cpath_str_compare_safe strncmp -#define cpath_fopen fopen -#define cpath_sprintf sprintf -#define cpath_vsnprintf vsnprintf -#endif - -#if defined _MSC_VER || defined __MINGW32__ -#define CPATH_MAX_PATH_LEN MAX_PATH -#elif defined __linux__ || defined BSD - -// @TODO: Do we add one? for \0 -// I have found conflicting info -// we definitely don't for windows -#ifdef PATH_MAX -#define CPATH_MAX_PATH_LEN PATH_MAX -#endif - -#endif - -// Found this value online in a few other libraries -// so I'll just continue this tradition/standard despite not knowing why -// @A: This seems to be the max on Linux so I suppose its a reasonable value -#ifndef CPATH_MAX_PATH_LEN -#define CPATH_MAX_PATH_LEN (4096) -#endif - -// on windows we need extra space for the \* mask that is required -// yes this is very weird... Maybe we can just chuck it onto the max path? -// I don't think we can because it seems that it is independent of the max path -// i.e. if you can have a max path of 256 characters this includes the extra -// in this case we could just subtract it from the max path -// but then again we may not always want to apply the mask (and we don't) -// so that isn't a great solution either... -// @TODO: Figure this stuff out. -#ifdef _MSC_VER -#define CPATH_PATH_EXTRA_CHARS (2) -#else -#define CPATH_PATH_EXTRA_CHARS (0) -#endif - -/* - NOTE: This isn't the maximum number of drives (that would be 26) - This is the length of a drive prepath i.e. D:\ which is clearly 3 - - No such max exists on other systems to the best of my knowledge - - By defining it like (static_assert(0, msg), 1) we make sure that we don't - have a compiler syntax error in it's typical usage we prefer having - the error from having the static assert rather than the error for having - an incorrect token. - @TODO: Do we actually need this??? -*/ -#if defined _MSC_VER -#define FILE_IS(f, flag) !!(f.dwFileAttributes & FILE_ATTRIBUTE_##flag) -#define FILE_IS_NOT(f, flag) !(f.dwFileAttributes & FILE_ATTRIBUTE_##flag) -#endif - -#if defined _MSC_VER || defined __MINGW32__ -#define CPATH_MAX_DRIVE_LEN (3) -#elif __cpp_static_assert -#define CPATH_MAX_DRIVE_LEN \ - (static_assert(0, "You shouldn't use max drive length on non windows"), 1) -#elif __STDC_VERSION__ >= 201112L -#define CPATH_MAX_DRIVE_LEN \ - (_Static_assert(0, "You shouldn't use max drive length on non windows"), 1) -#else -#define CPATH_MAX_DRIVE_LEN \ - ("You shouldn't use max drive length on non windows", ) -#endif - -#if defined _WIN32 - #include - #define _cpath_getcwd _getcwd -#else - #include // for getcwd() - #define _cpath_getcwd getcwd -#endif - -#if !defined _MSC_VER -#if defined __MINGW32__ && defined _UNICODE -#define _cpath_opendir _wopendir -#define _cpath_readdir _wreaddir -#define _cpath_closedir _wclosedir -#else -#define _cpath_opendir opendir -#define _cpath_readdir readdir -#define _cpath_closedir closedir -#endif -#endif - -#if defined CPATH_FORCE_CONVERSION_SYSTEM -#if defined _MSC_VER || defined __MINGW32__ -#define CPATH_SEP CPATH_STR('\\') -#define CPATH_OTHER_SEP CPATH_STR('/') -#else -#define CPATH_SEP CPATH_STR('/') -#define CPATH_OTHER_SEP CPATH_STR('\\') -#endif -#else -#define CPATH_SEP CPATH_STR('/') -#define CPATH_OTHER_SEP CPATH_STR('\\') -#endif - -#if !defined CPATH_NO_CPP_BINDINGS && defined __cplusplus - namespace cpath { namespace internals { -#endif - -// @NOTE: gdb/lldb can't print our strings -// for some reason they don't understand the typedefs -// however they will understand a #define cause they'll -// just see it as a char symbol. -// @BUG: @TODO: Make it so it can be a typedef... -// Currently I just have decided to make it default to this -// Worse case we introduce a #define -#if defined _MSC_VER || defined __MINGW32__ -// todo -typedef int cpath_offset_t; -typedef int cpath_time_t; -typedef TCHAR cpath_char_t; -#else -typedef char cpath_char_t; -typedef off_t cpath_offset_t; -typedef time_t cpath_time_t; -#endif - -#if !defined _MSC_VER -#if defined __MINGW32__ && defined _UNICODE -typedef _WDIR cpath_dirdata_t; -typedef struct _wdirent cpath_dirent_t; -#else -typedef DIR cpath_dirdata_t; -typedef struct dirent cpath_dirent_t; -#endif -#endif - -typedef cpath_char_t *cpath_str; -typedef int(*cpath_err_handler)(); -typedef int(*cpath_cmp)(const void*, const void*); -typedef struct cpath_t { - cpath_char_t buf[CPATH_MAX_PATH_LEN]; - size_t len; -} cpath; - -typedef struct cpath_file_t { - int isDir; - int isReg; - int isSym; - -#if !defined _MSC_VER -#if defined __MINGW32__ - struct _stat stat; -#else - struct stat stat; -#endif -#endif - - int statLoaded; - - cpath path; - cpath_char_t name[CPATH_MAX_FILENAME_LEN]; - cpath_str extension; -} cpath_file; - -typedef struct cpath_dir_t { - cpath_file *files; - size_t size; - -#ifdef _MSC_VER - HANDLE handle; - WIN32_FIND_DATA findData; -#else - cpath_dirdata_t *dir; - cpath_dirent_t *dirent; -#endif - - // This is set whenever you do an emplace - // This allows you to revert an emplace - struct cpath_dir_t *parent; - - int hasNext; - - cpath path; -} cpath_dir; - -// This allows a forced type -typedef uint8_t CPathByteRep; -enum CPathByteRep_ { - BYTE_REP_JEDEC = 0, // KB = 1024, ... - BYTE_REP_DECIMAL = 1, // 1000 interval segments, B, kB, MB, GB, ... - BYTE_REP_IEC = 2, // KiB = 1024, ... - BYTE_REP_DECIMAL_UPPER = 3, // 1000 interval segments but B, KB, MB, GB, ... - BYTE_REP_DECIMAL_LOWER = 4, // 1000 interval segments but b, kb, mb, gb, ... - - // representations - BYTE_REP_LONG = 0b10000000, // Represent as words i.e. kibibytes - BYTE_REP_BYTE_WORD = 0b01000000, // Just B as Byte (only bytes) -}; - -typedef void(*cpath_traverse_it)( - cpath_file *file, cpath_dir *parent, int depth, - void *data -); - -/* == Declarations == */ - -/* == Path == */ - -/* - A clear path is always just '.' - - When constructing a path it will always ignore extra / or \ at the end - and multiple in a row. - - i.e. C://a\b\\c\ is just C:/a/b/c/ - - If you wish to force conversion to the system specific one - (/ on posix \ on windows) then just add - #define CPATH_FORCE_CONVERSION_SYSTEM - i.e. on Windows it will be C:\a\b\c on Posix it will be C:/a/b/c -*/ - -/* - Does a strcpy but converts all separators and ignores multiple in a row - Returns how many characters were copied. -*/ -_CPATH_FUNC_ -size_t cpathStrCpyConv(cpath_str dest, size_t len, const cpath_char_t *src); - -/* - Trim all trailing / or \ from a path -*/ -_CPATH_FUNC_ -void cpathTrim(cpath *path); - -/* - Construct a path from a utf8 string -*/ -_CPATH_FUNC_ -cpath cpathFromUtf8(const char *str); - -/* - Copy a path -*/ -_CPATH_FUNC_ -void cpathCopy(cpath *out, const cpath *in); - -/* - Construct a path from a system typed string. -*/ -_CPATH_FUNC_ -int cpathFromStr(cpath *out, const cpath_char_t *str); - -/* - Concatenate a path with a literal -*/ -#define CPATH_CONCAT_LIT(path, other) cpathConcatStr(path, CPATH_STR(other)) - -/* - Appends to a path with a literal, wont add a / -*/ -#define CPATH_APPEND_LIT(path, other) cpathAppendStr(path, CPATH_STR(other)) - -/* - Append to the cpath buffer. - NOTE: Won't add a / - There is no concat version since it would be too inefficient - and thus encourage bad habits. Just add a / everytime you call this -*/ -_CPATH_FUNC_ -void cpathAppendSprintf(cpath *out, const cpath_char_t *fmt, ...); - -/* - Appends to the path (not adding /) -*/ -_CPATH_FUNC_ -int cpathAppendStrn(cpath *out, const cpath_char_t *other, size_t len); - -/* - Appends to the path (not adding /) -*/ -_CPATH_FUNC_ -int cpathAppendStr(cpath *out, const cpath_char_t *other); - -/* - Appends to the path (not adding /) -*/ -_CPATH_FUNC_ -int cpathAppend(cpath *out, const cpath *other); - -/* - Concatenate a path (adds / for you) from a system typed string -*/ -_CPATH_FUNC_ -int cpathConcatStrn(cpath *out, const cpath_char_t *other, size_t len); - -/* - Concatenate a path (adds / for you) from a system typed string -*/ -_CPATH_FUNC_ -int cpathConcatStr(cpath *out, const cpath_char_t *other); - -/* - Concat a path with another. -*/ -_CPATH_FUNC_ -int cpathConcat(cpath *out, const cpath *other); - -/* - Does the path exist. -*/ -_CPATH_FUNC_ -int cpathExists(const cpath *path); - -/* - Attempts to canonicalise without system calls - Will fail in cases where it can't predict the path - i.e. C:/a/../b will be just b -*/ -_CPATH_FUNC_ -int cpathCanonicaliseNoSysCall(cpath *out, cpath *path); - -/* - Resolves the path. Involves system calls. -*/ -_CPATH_FUNC_ -int cpathCanonicalise(cpath *out, cpath *path); - -/* - Tries to canonicalise without any system calls - then resorts to the system call version if it fails -*/ -_CPATH_FUNC_ -int cpathCanonicaliseAvoidSysCall(cpath *out, cpath *path); - -/* - Clears a given path. -*/ -_CPATH_FUNC_ -void cpathClear(cpath *path); - -/* - Iterate through every component. Doesn't include the \ - i.e. \usr\local\bin\myfile.so will be '' 'usr' 'local' 'bin' 'myfile.so' - - If you wish to iterate from the beginning set index to 0. Else set index - to whatever index you wish to iterate from. It'll restore the previous - segments as it goes meaning on completion the path will be fully restored. - - NOTE: Modifies the path buffer so you shouldn't use the path for anything - Until this returns nullptr (indicating no more path segments) - OR you use cpathItRefRestore() note that on the last path segment - it obviously won't have to add an '\0' so it'll also work if you bail - out then. It is hard to detect that though. - - You can strdup it yourself or do a memcpy if you care about editing it -*/ -_CPATH_FUNC_ -const cpath_char_t *cpathItRef(cpath *path, int *index); - -/* - Restores the path to a usable state. - ONLY needs to be called if the path is wanted to be usable and you haven't - iterated through all the references. - If the index isn't valid it'll cycle through the path fixing it up. - This is just for the sake of it, it'll make things easier and less prone - to breaking. - - Do note that this will update index so that you can use it again with - it ref to get the resumed behaviour you would expect (effectively just a ++) -*/ -_CPATH_FUNC_ -void cpathItRefRestore(cpath *path, int *index); - -/* - Go up a directory effectively going back by one / - i.e. C:/D/E/F/g.c => C:/D/E/F and so on... - Returns true if it succeeded - Could possibly involve a canonicalise if the path is a symlink - i.e. ./ has to be resolved and so does ~/ -*/ -_CPATH_FUNC_ -int cpathUpDir(cpath *path); - -/* - Converts all separators to the given separator - Note: will only convert / and \ -*/ -_CPATH_FUNC_ -void cpathConvertSepCustom(cpath *path, cpath_char_t sep); - -/* - Converts all seperators to the default one. -*/ -_CPATH_FUNC_ -void cpathConvertSep(cpath *path) { cpathConvertSepCustom(path, CPATH_SEP); } - -/* == File System == */ - -/* - Get the current working directory allocating the space -*/ -_CPATH_FUNC_ -cpath_char_t *cpathGetCwdAlloc(); - -/* - Get the current working directory passing in a buffer -*/ -_CPATH_FUNC_ -cpath_char_t *cpathGetCwdBuf(cpath_char_t *buf, size_t size); - -/* - Write the cwd to a path buffer. -*/ -_CPATH_FUNC_ -void cpathWriteCwd(cpath *path); - -/* - Get the cwd as a path. -*/ -_CPATH_FUNC_ -cpath cpathGetCwd(); - -/* - Opens a directory placing information into the given directory buffer. -*/ -_CPATH_FUNC_ -int cpathOpenDir(cpath_dir *dir, const cpath *path); - -/* - Restarts the given directory iterator. -*/ -_CPATH_FUNC_ -int cpathRestartDir(cpath_dir *dir); - -/* - Clear directory data. - If closing parents then will recurse through all previous emplaces. -*/ -_CPATH_FUNC_ -void cpathCloseDir(cpath_dir *dir); - -/* - Move to the next file in the iterator. -*/ -_CPATH_FUNC_ -int cpathMoveNextFile(cpath_dir *dir); - -/* - Load stat. Sets statLoaded. -*/ -_CPATH_FUNC_ -int cpathGetFileInfo(cpath_file *file); - -/* - Load file flags such as isDir, isReg, isSym - Attempts to not use stat since that is slow -*/ -_CPATH_FUNC_ -int cpathLoadFlags(cpath_dir *dir, cpath_file *file, void *data); - -/* - Peeks the next file but doesn't move the iterator along. -*/ -_CPATH_FUNC_ -int cpathPeekNextFile(cpath_dir *dir, cpath_file *file); - -/* - Get the next file inside the directory, acts like an iterator. - i.e. while (cpathGetNextFile(...)) can use dir->hasNext to verify - that there are indeed a file but it is safe! - - File may be nullptr if you don't wish to actually get a copy -*/ -_CPATH_FUNC_ -int cpathGetNextFile(cpath_dir *dir, cpath_file *file); - -/* - Is the file . or .. -*/ -_CPATH_FUNC_ -int cpathFileIsSpecialHardLink(const cpath_file *file); - -/* - Preload all files in a directory. Will be called automatically in some - function calls such as cpathGetFile() -*/ -_CPATH_FUNC_ -int cpathLoadAllFiles(cpath_dir *dir); - -/* - More of a helper function, checks if we have space for n - and will preload any files if required. -*/ -_CPATH_FUNC_ -int cpathCheckGetN(cpath_dir *dir, size_t n); - -/* - Get the nth file inside the directory, this is independent of the iterator - above. - - Note calling this will require the reading of the entire directory. - if you want to prevent all these allocations then I recommend using - getNextFile(...) -*/ -_CPATH_FUNC_ -int cpathGetFile(cpath_dir *dir, cpath_file *file, size_t n); - -/* - Get a const reference to a file, this is more performant than GetFile - but you can't modify anything about the object. -*/ -_CPATH_FUNC_ -int cpathGetFileConst(cpath_dir *dir, const cpath_file **file, size_t n); - -/* - Opens the next sub directory into this - Saves the old directory into the parent if given saveDir -*/ -_CPATH_FUNC_ -int cpathOpenSubFileEmplace(cpath_dir *dir, const cpath_file *file, int saveDir); - -/* - Opens the n'th sub directory into this directory - Saves the old directory into the parent if given saveDir -*/ -_CPATH_FUNC_ -int cpathOpenSubDirEmplace(cpath_dir *dir, size_t n, int saveDir); - -/* - Opens the n'th sub directory into other given directory -*/ -_CPATH_FUNC_ -int cpathOpenSubDir(cpath_dir *out, cpath_dir *dir, size_t n); - -/* - Opens the next sub directory into other given directory -*/ -_CPATH_FUNC_ -int cpathOpenNextSubDir(cpath_dir *out, cpath_dir *dir); - -/* - Peeks the sub directory. -*/ -_CPATH_FUNC_ -int cpathOpenNextSubDir(cpath_dir *out, cpath_dir *dir); - -/* - Opens the next sub directory into this - Saves the old directory into the parent if given saveDir -*/ -_CPATH_FUNC_ -int cpathOpenNextSubDirEmplace(cpath_dir *dir, int saveDir); - -/* - Peeks the sub directory -*/ -_CPATH_FUNC_ -int cpathOpenCurrentSubDirEmplace(cpath_dir *dir, int saveDir); - -/* - Revert an emplace and go back to the parent. - Note: This can occur multiple times. - Returns true if it went back to the parent. - - This form is for a pointer variant where the storage - of the first directory is external. -*/ -_CPATH_FUNC_ -int cpathRevertEmplace(cpath_dir **dir); - -/* - Revert an emplace and go back to the parent. - Note: This can occur multiple times. - Returns true if it went back to the parent. - - This form is for a directory that is stored - internally to the function. -*/ -_CPATH_FUNC_ -int cpathRevertEmplaceCopy(cpath_dir *dir); - -/* - Opens the given path as a file. -*/ -_CPATH_FUNC_ -int cpathOpenFile(cpath_file *file, const cpath *path); - -/* - Converts a given file to a directory. - Note: Fails to convert if the file isn't a directory. -*/ -_CPATH_FUNC_ -int cpathFileToDir(cpath_dir *dir, const cpath_file *file); - -/* - Get the extension for a file. - Note: You can't access a file's extension BEFORE you call this - It will be nullptr prior. - Returns nullptr if no extension or if it is a directory. -*/ -_CPATH_FUNC_ -cpath_str cpathGetExtension(cpath_file *file); - -/* - Get the time of last access -*/ -_CPATH_FUNC_ -cpath_time_t cpathGetLastAccess(cpath_file *file); - -/* - Get the time of last modification -*/ -_CPATH_FUNC_ -cpath_time_t cpathGetLastModification(cpath_file *file); - -/* - Get the file size in bytes of this file. -*/ -_CPATH_FUNC_ -cpath_offset_t cpathGetFileSize(cpath_file *file); - -/* - Standard compare function for files -*/ -_CPATH_FUNC_ -void cpathSort(cpath_dir *dir, cpath_cmp cmp); - -/* - Get the file size in decimal form -*/ -_CPATH_FUNC_ -double cpathGetFileSizeDec(cpath_file *file, int interval); - -/* - Get the file size prefix -*/ -_CPATH_FUNC_ -const cpath_char_t *cpathGetFileSizeSuffix(cpath_file *file, CPathByteRep rep); - -/* - Create the given directory -*/ -_CPATH_FUNC_ -int cpathMkdir(const cpath *path); - -/* - Open a given file -*/ -_CPATH_FUNC_ -FILE *cpathOpen(const cpath *path, const cpath_char_t *mode); - -/* == Definitions == */ - -/* == Path == */ - -_CPATH_FUNC_ -size_t cpathStrCpyConv(cpath_str dest, size_t len, const cpath_char_t *src) { - int lastWasSep = 0; - cpath_str startDest = dest; - for (size_t i = 0; i < len + 1; i++) { - int isSep = src[i] == CPATH_OTHER_SEP || src[i] == CPATH_SEP; - - if (isSep && !lastWasSep) { - *dest++ = CPATH_SEP; - } else if (!isSep || !lastWasSep) { - *dest++ = src[i]; - } - lastWasSep = isSep; - } - return dest - startDest - 1; -} - -_CPATH_FUNC_ -void cpathTrim(cpath *path) { - /* trim all the terminating / and \ */ - /* We don't want to trim // into empty string - We will trim it to just / - */ - while (path->len > 1 && (path->buf[path->len - 1] == CPATH_SEP || - path->buf[path->len - 1] == CPATH_OTHER_SEP)) { - path->len--; - } - path->buf[path->len] = CPATH_STR('\0'); -} - -_CPATH_FUNC_ -cpath cpathFromUtf8(const char *str) { - cpath path; - if (str[0] == CPATH_STR('\0')) { - // empty string which is just '.' - path.len = 1; - path.buf[0] = CPATH_STR('.'); - path.buf[1] = CPATH_STR('\0'); - return path; - } - - path.len = 0; - path.buf[0] = CPATH_STR('\0'); - size_t len = cpath_str_length(str); - // NOTE: max path len includes the \0 where as str length doesn't! - if (len >= CPATH_MAX_PATH_LEN) { - errno = ENAMETOOLONG; - return path; - } -#if defined CPATH_UNICODE && defined _MSC_VER - mbstowcs_s(&path.len, path.buf, len + 1, str, CPATH_MAX_PATH_LEN); - cpathConvertSep(&path); -#else - path.len = cpathStrCpyConv(path.buf, len, str); -#endif - cpathTrim(&path); - return path; -} - -_CPATH_FUNC_ -void cpathCopy(cpath *out, const cpath *in) { - cpath_str_copy(out->buf, in->buf); - out->len = in->len; -} - -_CPATH_FUNC_ -int cpathFromStr(cpath *out, const cpath_char_t *str) { - size_t len = cpath_str_length(str); - if (len >= CPATH_MAX_PATH_LEN) return 0; - if (len == 0) { - out->len = 1; - out->buf[0] = CPATH_STR('.'); - out->buf[1] = CPATH_STR('\0'); - return 1; - } - - out->len = cpathStrCpyConv(out->buf, len, str); - cpathTrim(out); - return 1; -} - -_CPATH_FUNC_ -void cpathAppendSprintf(cpath *out, const cpath_char_t *fmt, ...) { - va_list list; - va_start(list, fmt); - out->len += cpath_vsnprintf(out->buf + out->len, - CPATH_MAX_PATH_LEN - out->len, fmt, list); - va_end(list); -} - -_CPATH_FUNC_ -int cpathAppendStrn(cpath *out, const cpath_char_t *other, size_t len) { - if (len + out->len >= CPATH_MAX_PATH_LEN) { - // path too long, >= cause max path includes CPATH_STR('\0') - errno = ENAMETOOLONG; - return 0; - } - - // This is more efficient than a strcat since it doesn't have to get - // the length again it also handles all the conversions well and doesn't - // have to retraverse the out->buf which we know is fine. - out->len += cpathStrCpyConv(out->buf + out->len, len, other); - cpathTrim(out); - return 1; -} - -_CPATH_FUNC_ -int cpathAppendStr(cpath *out, const cpath_char_t *other) { - return cpathAppendStrn(out, other, cpath_str_length(other)); -} - -_CPATH_FUNC_ -int cpathAppend(cpath *out, const cpath *other) { - return cpathAppendStrn(out, other->buf, other->len); -} - -_CPATH_FUNC_ -int cpathConcatStrn(cpath *out, const cpath_char_t *str, size_t len) { - if (len + out->len >= CPATH_MAX_PATH_LEN) { - // path too long, >= cause max path includes CPATH_STR('\0') - errno = ENAMETOOLONG; - return 0; - } - - if (str[0] != CPATH_SEP && out->buf[out->len - 1] != CPATH_SEP && - str[0] != CPATH_OTHER_SEP && out->buf[out->len - 1] != CPATH_OTHER_SEP) { - out->buf[out->len++] = CPATH_SEP; - out->buf[out->len] = CPATH_STR('\0'); - } - - // This is more efficient than a strcat since it doesn't have to get - // the length again it also handles all the conversions well and doesn't - // have to retraverse the out->buf which we know is fine. - out->len += cpathStrCpyConv(out->buf + out->len, len, str); - cpathTrim(out); - return 1; -} - -_CPATH_FUNC_ -int cpathConcatStr(cpath *out, const cpath_char_t *other) { - return cpathConcatStrn(out, other, cpath_str_length(other)); -} - -_CPATH_FUNC_ -int cpathConcat(cpath *out, const cpath *other) { - return cpathConcatStrn(out, other->buf, other->len); -} - -_CPATH_FUNC_ -int cpathExists(const cpath *path) { -#if defined _MSC_VER || defined __MINGW32__ - DWORD res = GetFileAttributes(path->buf); - return res != INVALID_FILE_ATTRIBUTES; -#else - /* - We can either try to use stat or just access, access is more efficient - when not checking permissions. - - struct stat tmp; - return stat(path->buf, &tmp) == 0; - */ - return access(path->buf, F_OK) != -1; -#endif -} - -_CPATH_FUNC_ -int cpathCanonicaliseNoSysCall(cpath *out, cpath *path) { - /* - NOTE: This should work even if out == path - It just has been written that way. - */ - if (path == nullptr) { - errno = EINVAL; - return 0; - } - - out->len = 0; - cpath_char_t *chr = &path->buf[0]; - - while (*chr != CPATH_STR('\0')) { - if (*chr == CPATH_STR('.')) { - if (chr[1] == CPATH_STR('.')) { - if (out->len == 0 || (out->len == 1 && - (out->buf[0] == CPATH_SEP || out->buf[0] == CPATH_OTHER_SEP))) { - // no directory to go back based on string alone - errno = ENOENT; - return 0; - } - // remove last directory ignoring the last / since that is part of .. - out->len--; - while (out->len > 0 && out->buf[out->len - 1] != CPATH_SEP && - out->buf[out->len - 1] != CPATH_OTHER_SEP) { - out->len--; - } - // skip twice - chr++; - chr++; - } else if (chr[1] == CPATH_SEP || chr[1] == CPATH_STR('\0') || - chr[1] == CPATH_OTHER_SEP) { - // skip - chr++; - } else { - out->buf[out->len++] = *chr; - } - } else { - out->buf[out->len++] = *chr; - } - chr++; - } - - out->buf[out->len] = CPATH_STR('\0'); - cpathTrim(out); - if (out->len == 0) { - out->buf[0] = CPATH_STR('.'); - out->buf[1] = CPATH_STR('\0'); - out->len = 1; - } - - return 1; -} - -_CPATH_FUNC_ -int cpathCanonicalise(cpath *out, cpath *path) { - cpath tmp; - if (out == path) { - // support non-restrict method - cpathCopy(&tmp, path); - path = &tmp; - } - -#if defined _MSC_VER - DWORD size = GetFullPathName(path->buf, CPATH_MAX_PATH_LEN, out->buf, nullptr); - if (size == 0) { - // figure out errno - return 0; - } else { - path->len = size; - return 1; - } -#else - char *res = realpath(path->buf, out->buf); - if (res != nullptr) { - out->len = cpath_str_length(out->buf); - } - return res != nullptr; -#endif -} - -_CPATH_FUNC_ -int cpathCanonicaliseAvoidSysCall(cpath *out, cpath *path) { - if (!cpathCanonicaliseNoSysCall(out, path)) { - return cpathCanonicalise(out, path); - } - return 1; -} - -_CPATH_FUNC_ -void cpathClear(cpath *path) { - path->buf[0] = CPATH_STR('.'); - path->buf[1] = CPATH_STR('\0'); - path->len = 1; -} - -_CPATH_FUNC_ -const cpath_char_t *cpathItRef(cpath *path, int *index) { - if (path == nullptr || index == nullptr || *index >= path->len || *index < 0) { - return nullptr; - } - - if (path->buf[*index] == CPATH_STR('\0')) { - path->buf[*index] = CPATH_SEP; - (*index)++; - } - int old_pos = *index; - - // find next '/' - while (*index < path->len && path->buf[*index] != CPATH_SEP && - path->buf[*index] != CPATH_OTHER_SEP) { - (*index)++; - } - - path->buf[*index] = '\0'; - - return &path->buf[old_pos]; -} - -_CPATH_FUNC_ -void cpathItRefRestore(cpath *path, int *index) { - if (path == nullptr) return; - - if (index == nullptr || *index < 0 || *index >= path->len || - path->buf[*index] != CPATH_STR('\0')) { - // we have to loop - for (int i = 0; i < path->len; i++) { - if (path->buf[i] == CPATH_STR('\0')) path->buf[i] = CPATH_SEP; - } - // set index to the length since we have no real way to determine - // where abouts it its (that is if it isn't nullptr) - if (index != nullptr) *index = path->len; - } else { - path->buf[*index] = CPATH_SEP; - (*index)++; - } -} - -_CPATH_FUNC_ -int cpathUpDir(cpath *path) { - if (path->len == 0) { - // shouldn't have a path with this length - errno = EINVAL; - return 0; - } - - // We have to find the last component and strip it - int oldLen = path->len; - while (path->len > 1 && path->buf[path->len - 1] != CPATH_SEP && - path->buf[path->len - 1] != CPATH_SEP) { - path->len--; - } - if (path->len == 1) { - // we failed to quickly go up a dir - // so we will have to restore len and try another method - // this is better than the avoid method because - // this will allow you to go up a directory if the path == PATH_MAX - path->len = oldLen; - if (!CPATH_CONCAT_LIT(path, "..")) { - return 0; - } - return cpathCanonicalise(path, path); - } - - // we have found a '/' so we just set it to '\0' - path->buf[path->len - 1] = '\0'; - path->len--; - return 1; -} - -_CPATH_FUNC_ -void cpathConvertSepCustom(cpath *path, cpath_char_t sep) { - int lastSep = 0; - cpath_str cur = path->buf; - for (int i = 0; i < path->len + 1; i++) { - int isSep = path->buf[i] == CPATH_SEP || path->buf[i] == CPATH_OTHER_SEP; - if (isSep && !lastSep) { - *cur++ = CPATH_SEP; - } else if (!isSep || !lastSep) { - *cur++ = path->buf[i]; - } - lastSep = isSep; - } -} - -/* == File System == */ - -/* - Get the current working directory allocating the space -*/ -_CPATH_FUNC_ -cpath_char_t *cpathGetCwdAlloc() { - cpath_char_t *buf; - buf = (cpath_char_t*)CPATH_MALLOC(sizeof(cpath_char_t) * CPATH_MAX_PATH_LEN); - return cpathGetCwdBuf(buf, CPATH_MAX_PATH_LEN); -} - -_CPATH_FUNC_ -cpath_char_t *cpathGetCwdBuf(cpath_char_t *buf, size_t size) { - return _cpath_getcwd(buf, size); -} - -_CPATH_FUNC_ -void cpathWriteCwd(cpath *path) { - cpathGetCwdBuf(path->buf, CPATH_MAX_PATH_LEN); - path->len = cpath_str_length(path->buf); - cpathTrim(path); -} - -_CPATH_FUNC_ -cpath cpathGetCwd() { - cpath path; - cpathWriteCwd(&path); - cpathTrim(&path); - return path; -} - -_CPATH_FUNC_ -int cpathOpenDir(cpath_dir *dir, const cpath *path) { - if (dir == nullptr || path == nullptr || path->len == 0) { - // empty strings are invalid arguments - errno = EINVAL; - return 0; - } - - if (path->len + CPATH_PATH_EXTRA_CHARS >= CPATH_MAX_PATH_LEN) { - errno = ENAMETOOLONG; - return 0; - } - - dir->files = nullptr; - -#if defined _MSC_VER - dir->handle = INVALID_HANDLE_VALUE; -#else - dir->dir = nullptr; -#endif - dir->path.buf[path->len] = CPATH_STR('\0'); - dir->parent = nullptr; - dir->files = nullptr; -#if defined _MSC_VER - dir->handle = INVALID_HANDLE_VALUE; -#else - dir->dir = nullptr; - dir->dirent = nullptr; -#endif - - cpathCopy(&dir->path, path); - return cpathRestartDir(dir); -} - -_CPATH_FUNC_ -int cpathRestartDir(cpath_dir *dir) { - // @TODO: I think there is a faster way if the handles exist - if (dir == nullptr) { - errno = EINVAL; - return 0; - } - - dir->hasNext = 1; - dir->size = -1; - if (dir->files != nullptr) CPATH_FREE(dir->files); - dir->files = nullptr; - -#if defined _MSC_VER - if (dir->handle != INVALID_HANDLE_VALUE) FindClose(dir->handle); - dir->handle = INVALID_HANDLE_VALUE; -#else - if (dir->dir != nullptr) _cpath_closedir(dir->dir); - dir->dir = nullptr; - dir->dirent = nullptr; -#endif - - // Ignore parent, just restart this dir - -#if defined _MSC_VER - cpath_char_t pathBuf[CPATH_MAX_PATH_LEN]; - cpath_str_copy(pathBuf, dir->path.buf); - strcat(pathBuf, CPATH_STR("\\*")); - -#if (defined WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) - dir->handle = FindFirstFileEx(pathBuf, FindExInfoStandard, &dir->findData, - FindExSearchNameMatch, nullptr, 0); -#else - dir->handle = FindFirstFile(pathBuf, &dir->findData); -#endif - - if (dir->handle == INVALID_HANDLE_VALUE) { - errno = ENOENT; - // free associate memory and exit - cpathCloseDir(dir); - return 0; - } - -#else - - dir->dir = _cpath_opendir(dir->path.buf); - if (dir->dir == nullptr) { - cpathCloseDir(dir); - return 0; - } - dir->dirent = _cpath_readdir(dir->dir); - // empty directory - if (dir->dirent == nullptr) dir->hasNext = 0; - -#endif - - return 1; -} - -_CPATH_FUNC_ -void cpathCloseDir(cpath_dir *dir) { - if (dir == nullptr) return; - - dir->hasNext = 1; - dir->size = -1; - if (dir->files != nullptr) CPATH_FREE(dir->files); - dir->files = nullptr; - -#if defined _MSC_VER - if (dir->handle != INVALID_HANDLE_VALUE) FindClose(dir->handle); - dir->handle = INVALID_HANDLE_VALUE; -#else - if (dir->dir != nullptr) _cpath_closedir(dir->dir); - dir->dir = nullptr; - dir->dirent = nullptr; -#endif - - cpathClear(&dir->path); - if (dir->parent != nullptr) { - cpathCloseDir(dir->parent); - CPATH_FREE(dir->parent); - dir->parent = nullptr; - } -} - -_CPATH_FUNC_ -int cpathMoveNextFile(cpath_dir *dir) { - if (dir == nullptr) { - errno = EINVAL; - return 0; - } - if (!dir->hasNext) { - return 0; - } - -#if defined _MSC_VER - if (FindNextFile(dir->handle, &dir->findData) == 0) { - dir->hasNext = 0; - if (GetLastError() != ERROR_SUCCESS && - GetLastError() != ERROR_NO_MORE_FILES) { - cpathCloseDir(dir); - errno = EIO; - return 0; - } - } -#else - dir->dirent = _cpath_readdir(dir->dir); - if (dir->dirent == nullptr) { - dir->hasNext = 0; - } -#endif - - return 1; -} - -_CPATH_FUNC_ -int cpathGetFileInfo(cpath_file *file) { - if (file->statLoaded) { - return 1; - } -#if !defined _MSC_VER -#if defined __MINGW32__ - if (_tstat(file->path.buf, &file->stat) == -1) { - return 0; - } -#elif defined _BSD_SOURCE || defined _DEFAULT_SOURCE \ - || (defined _XOPEN_SOURCE && _XOPEN_SOURCE >= 500) \ - || (defined _POSIX_C_SOURCE && _POSIX_C_SOURCE >= 200112L) - if (lstat(file->path.buf, &file->stat) == -1) { - return 0; - } -#else - if (stat(file->path.buf, &file->stat) == -1) { - return 0; - } -#endif -#endif - file->statLoaded = 1; - return 1; -} - -_CPATH_FUNC_ -int cpathLoadFlags(cpath_dir *dir, cpath_file *file, void *data) { -#if defined _MSC_VER - WIN32_FIND_DATA find = *((WIN32_FIND_DATA*) data); - file->isDir = FILE_IS(find, DIRECTORY); - if (FILE_IS(find, NORMAL)) { - file->isReg = 1; - } else if (FILE_IS_NOT(find, DEVICE) && FILE_IS_NOT(find, DIRECTORY) && - FILE_IS_NOT(find, ENCRYPTED) && FILE_IS_NOT(find, OFFLINE) && -#ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM - FILE_IS_NOT(find, INTEGRITY_STREAM) && -#endif -#ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA - FILE_IS_NOT(find, NO_SCRUB_DATA) && -#endif - FILE_IS_NOT(find, TEMPORARY)) { - file->isReg = 1; - } else { - file->isReg = 0; - } - file->isSym = FILE_IS(find, REPARSE_POINT); -#else - if (dir->dirent == nullptr || dir->dirent->d_type == DT_UNKNOWN) { - if (!cpathGetFileInfo(file)) { - return 0; - } - - file->isDir = S_ISDIR(file->stat.st_mode); - file->isReg = S_ISREG(file->stat.st_mode); - file->isSym = S_ISLNK(file->stat.st_mode); - } else { - file->isDir = dir->dirent->d_type == DT_DIR; - file->isReg = dir->dirent->d_type == DT_REG; - file->isSym = dir->dirent->d_type == DT_LNK; - file->statLoaded = 0; - } -#endif - return 1; -} - -_CPATH_FUNC_ -int cpathPeekNextFile(cpath_dir *dir, cpath_file *file) { - if (file == nullptr || dir == nullptr) { - errno = EINVAL; - return 0; - } - - file->statLoaded = 0; - // load current file into file - const cpath_char_t *filename; - size_t filenameLen; -#if defined _MSC_VER - if (dir->handle == INVALID_HANDLE_VALUE) { - return 0; - } - filename = dir->findData.cFileName; - filenameLen = cpath_str_length(filename); -#else - if (dir->dirent == nullptr) { - return 0; - } - filename = dir->dirent->d_name; - // TODO: On MACOS there is a d_namlen but not on linux - filenameLen = strlen(dir->dirent->d_name); -#endif - size_t totalLen = dir->path.len + filenameLen; - if (totalLen + 1 + CPATH_PATH_EXTRA_CHARS >= CPATH_MAX_PATH_LEN || - filenameLen >= CPATH_MAX_FILENAME_LEN) { - errno = ENAMETOOLONG; - return 0; - } - - cpath_str_copy(file->name, filename); - cpathCopy(&file->path, &dir->path); - if (!CPATH_CONCAT_LIT(&file->path, "/") || - !cpathConcatStr(&file->path, filename)) { - return 0; - } -#ifndef CPATH_NO_AUTOLOAD_EXT - cpathGetExtension(file); -#endif -#if defined _MSC_VER - if (!cpathLoadFlags(dir, file, (WIN32_FIND_DATA*)(&dir->findData))) return 0; -#else - if (!cpathLoadFlags(dir, file, nullptr)) return 0; -#endif -#ifdef CPATH_AUTOLOAD_STAT - if (!cpathGetFileInfo(file)) return 0; -#endif - return 1; -} - -_CPATH_FUNC_ -int cpathGetNextFile(cpath_dir *dir, cpath_file *file) { - if (file != nullptr) { - if (!cpathPeekNextFile(dir, file)) { - return 0; - } - } - - errno = 0; - // @TODO: Bug I think - // I think we want to remove the condition that dir->hasNext - // Sicne cpath checks that. - if (dir->hasNext && !cpathMoveNextFile(dir) && errno != 0) { - return 0; - } - - return 1; -} - -_CPATH_FUNC_ -int cpathFileIsSpecialHardLink(const cpath_file *file) { - return file->name[0] == CPATH_STR('.') && (file->name[1] == CPATH_STR('\0') || - (file->name[1] == CPATH_STR('.') && file->name[2] == CPATH_STR('\0'))); -} - -_CPATH_FUNC_ -int cpathLoadAllFiles(cpath_dir *dir) { - if (dir == nullptr) { - errno = EINVAL; - return 0; - } - - if (dir->files != nullptr) CPATH_FREE(dir->files); - - // @Ugly: - /* - The problem is that we could go through all files to get a count - This is kinda ugly but is linear and ensures we don't make allocations - The other method is to use a 'vector' however the copies are really - expensive since we have huge objects which is why I chose the count method - */ - size_t count = 0; - errno = 0; - while (cpathMoveNextFile(dir)) { - count++; - } - - // we can have 0 files - if (count == 0) { - dir->size = 0; - return 1; - } - - // loading failed for some reason or we failed to restart the dir iterator - if (errno != 0 || !cpathRestartDir(dir)) { - // we won't close the directory though! - // we'll just pretend we have no files! - dir->size = 0; - return 0; - } - - dir->size = count; - dir->files = (cpath_file*) CPATH_MALLOC(sizeof(cpath_file) * count); - if (dir->files == nullptr) { - // we won't close the directory just error out - dir->size = 0; - return 0; - } - - errno = 0; - int i = 0; - // also make sure that we don't overflow the array - // because size requirements changed i.e. race condition - // we could fix this by resizing but that's super expensive - // maybe this is time for a linked list or just to dynamically allocate - // all file nodes and waste the extra 8 bytes per (and cache locality) - while (i < dir->size && cpathGetNextFile(dir, &dir->files[i])) { - i++; - } - - if (i < dir->size) { - // we stopped early due to error I'm still not going to crazily error out - // because it may just be race condition and I'll be a bit lazy - // @TODO: check if it was just race or if a read failed - dir->size = i; - } - - return 1; -} - -_CPATH_FUNC_ -int cpathCheckGetN(cpath_dir *dir, size_t n) { - if (dir == nullptr) { - errno = EINVAL; - return 0; - } - - if (dir->size == -1) { - // we haven't loaded - cpathLoadAllFiles(dir); - } - - if (n >= dir->size) { - errno = ENOENT; - return 0; - } - return 1; -} - -_CPATH_FUNC_ -int cpathGetFile(cpath_dir *dir, cpath_file *file, size_t n) { - if (file == nullptr) { - errno = EINVAL; - return 0; - } - if (!cpathCheckGetN(dir, n)) return 0; - - memcpy(file, &dir->files[n], sizeof(cpath_file)); - return 1; -} - -_CPATH_FUNC_ -int cpathGetFileConst(cpath_dir *dir, const cpath_file **file, size_t n) { - if (dir == nullptr || file == nullptr) { - errno = EINVAL; - return 0; - } - - if (dir->size == -1) { - // we haven't loaded - cpathLoadAllFiles(dir); - } - - if (n >= dir->size) { - errno = ENOENT; - return 0; - } - - *file = &dir->files[n]; - return 1; -} - -_CPATH_FUNC_ -int cpathOpenSubFileEmplace(cpath_dir *dir, const cpath_file *file, - int saveDir) { - cpath_dir *saved = nullptr; - if (saveDir) { - // save the old one - saved = (cpath_dir*)CPATH_MALLOC(sizeof(cpath_dir)); - if (saved != nullptr) memcpy(saved, dir, sizeof(cpath_dir)); - } - - if (!cpathFileToDir(dir, file)) { - if (saved != nullptr) CPATH_FREE(saved); - return 0; - } - - dir->parent = saved; - - return 1; -} - -_CPATH_FUNC_ -int cpathOpenSubDirEmplace(cpath_dir *dir, size_t n, int saveDir) { - if (dir == nullptr) { - errno = EINVAL; - return 0; - } - if (!cpathCheckGetN(dir, n)) return 0; - - const cpath_file *file; - if (!cpathGetFileConst(dir, &file, n) || - !file->isDir || !cpathOpenSubFileEmplace(dir, file, saveDir)) { - if (!file->isDir) errno = EINVAL; - return 0; - } - - return 1; -} - -_CPATH_FUNC_ -int cpathOpenSubDir(cpath_dir *out, cpath_dir *dir, size_t n) { - if (dir == nullptr || out == nullptr) { - errno = EINVAL; - return 0; - } - if (!cpathCheckGetN(dir, n)) return 0; - - const cpath_file *file; - if (!cpathGetFileConst(dir, &file, n) || - !file->isDir || !cpathFileToDir(out, file)) { - if (!file->isDir) errno = EINVAL; - return 0; - } - return 1; -} - -_CPATH_FUNC_ -int cpathOpenNextSubDir(cpath_dir *out, cpath_dir *dir) { - if (dir == nullptr || out == nullptr) { - errno = EINVAL; - return 0; - } - - cpath_file file; - if (!cpathGetNextFile(dir, &file) || - !file.isDir || !cpathFileToDir(out, &file)) { - if (!file.isDir) errno = EINVAL; - return 0; - } - return 1; -} - -_CPATH_FUNC_ -int cpathOpenCurrentSubDir(cpath_dir *out, cpath_dir *dir) { - if (dir == nullptr || out == nullptr) { - errno = EINVAL; - return 0; - } - - cpath_file file; - if (!cpathPeekNextFile(dir, &file) || - !file.isDir || !cpathFileToDir(out, &file)) { - if (!file.isDir) errno = EINVAL; - return 0; - } - return 1; -} - -_CPATH_FUNC_ -int cpathOpenNextSubDirEmplace(cpath_dir *dir, int saveDir) { - if (dir == nullptr) { - errno = EINVAL; - return 0; - } - - cpath_file file; - if (!cpathGetNextFile(dir, &file) || - !file.isDir || !cpathOpenSubFileEmplace(dir, &file, saveDir)) { - if (!file.isDir) errno = EINVAL; - return 0; - } - return 1; -} - -_CPATH_FUNC_ -int cpathOpenCurrentSubDirEmplace(cpath_dir *dir, int saveDir) { - if (dir == nullptr) { - errno = EINVAL; - return 0; - } - - cpath_file file; - if (!cpathPeekNextFile(dir, &file) || - !file.isDir || !cpathOpenSubFileEmplace(dir, &file, saveDir)) { - if (!file.isDir) errno = EINVAL; - return 0; - } - return 1; -} - -_CPATH_FUNC_ -int cpathRevertEmplace(cpath_dir **dir) { - if (dir == nullptr || *dir == nullptr) return 0; - cpath_dir *tmp = (*dir)->parent; - (*dir)->parent = nullptr; - cpathCloseDir(*dir); - *dir = tmp; - return *dir != nullptr; -} - -_CPATH_FUNC_ -int cpathRevertEmplaceCopy(cpath_dir *dir) { - if (dir == nullptr) return 0; - cpath_dir *tmp = dir->parent; - dir->parent = nullptr; - cpathCloseDir(dir); - if (tmp != nullptr) { - memcpy(dir, tmp, sizeof(cpath_dir)); - } - return tmp != nullptr; -} - -_CPATH_FUNC_ -int cpathOpenFile(cpath_file *file, const cpath *path) { - // We want to efficiently open this file so unlike most libraries - // we won't use a directory search we will just find the given file - // or directly use stuff like dirname/basename! - cpath_dir dir; - - if (file == nullptr || path == nullptr || path->len == 0) { - errno = EINVAL; - return 0; - } - if (path->len >= CPATH_MAX_PATH_LEN) { - errno = ENAMETOOLONG; - return 0; - } - - void* data = nullptr; - - cpathCopy(&file->path, path); - -#if defined _MSC_VER - void *handle = nullptr; - WIN32_FIND_DATA findData; - -#if (defined WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) - handle = FindFirstFileEx(path->buf, FindExInfoStandard, &findData, - FindExSearchNameMatch, nullptr, 0); -#else - handle = FindFirstFile(path->buf, &findData); -#endif - - if (handle == INVALID_HANDLE_VALUE) { - errno = ENOENT; - return 0; - } - - data = &findData; - cpath_str_copy(file->name, findData.cFileName); -#else - // copy the name then strip to just basename - // this is very ewwwww, honestly we probably would be better - // to just do this ourselves since ugh - cpath_strn_copy(file->name, path->buf, path->len); - char *tmp = basename(file->name); - // some systems allocate it seems.. ugh - if (tmp != file->name) { - cpath_str_copy(file->name, tmp); - CPATH_FREE(tmp); - } - // @TODO: make sure tmp isn't too long -#endif - - file->statLoaded = 0; - int res = cpathLoadFlags(&dir, file, data); -#if defined _MSC_VER - FindClose((HANDLE)handle); -#else - dir.dirent = nullptr; -#endif - - return res; -} - -_CPATH_FUNC_ -int cpathFileToDir(cpath_dir *dir, const cpath_file *file) { - if (dir == nullptr || file == nullptr || !file->isDir) { - errno = EINVAL; - return 0; - } - return cpathOpenDir(dir, &file->path); -} - -_CPATH_FUNC_ -cpath_str cpathGetExtension(cpath_file *file) { - if (file->extension != nullptr) return file->extension; - - cpath_char_t *dot = cpath_str_find_last_char(file->name, CPATH_STR('.')); - if (dot != nullptr) { - // extension - file->extension = dot + 1; - } else { - // no extension so set to CPATH_STR('\0') - file->extension = &file->name[cpath_str_length(file->name)]; - } - return file->extension; -} - -_CPATH_FUNC_ -cpath_time_t cpathGetLastAccess(cpath_file *file) { - if (!file->statLoaded) cpathGetFileInfo(file); - -#if defined _MSC_VER || defined __MINGW32__ - // Idk todo -#else - return file->stat.st_atime; -#endif -} - -_CPATH_FUNC_ -cpath_time_t cpathGetLastModification(cpath_file *file) { - if (!file->statLoaded) cpathGetFileInfo(file); - -#if defined _MSC_VER || defined __MINGW32__ - // Idk todo -#else - return file->stat.st_mtime; -#endif -} - -_CPATH_FUNC_ -cpath_offset_t cpathGetFileSize(cpath_file *file) { - if (!file->statLoaded) cpathGetFileInfo(file); - -#if defined _MSC_VER || defined __MINGW32__ - // Idk todo -#else - return file->stat.st_size; -#endif -} - -_CPATH_FUNC_ -void cpathSort(cpath_dir *dir, cpath_cmp cmp) { - if (dir->files == nullptr) { - if (dir->size == -1) { - // bad, this means you try to sorted a directory before you preloaded - // in this case we'll just preload the files. This is just to be nice - cpathLoadAllFiles(dir); - } - - // this is fine just means no files in directory - if (dir->size == 0) { - return; - } - } - qsort(dir->files, dir->size, sizeof(struct cpath_dir_t), cmp); -} - -static const cpath_char_t *prefixTableDecimal[] = { - CPATH_STR("kB"), CPATH_STR("MB"), CPATH_STR("GB"), CPATH_STR("TB"), - CPATH_STR("PB"), CPATH_STR("EB"), CPATH_STR("ZB"), CPATH_STR("YB"), -}; -static const cpath_char_t *prefixTableDecimalUpper[] = { - CPATH_STR("KB"), CPATH_STR("MB"), CPATH_STR("GB"), CPATH_STR("TB"), - CPATH_STR("PB"), CPATH_STR("EB"), CPATH_STR("ZB"), CPATH_STR("YB"), -}; -static const cpath_char_t *prefixTableDecimalLower[] = { - CPATH_STR("kb"), CPATH_STR("mb"), CPATH_STR("gb"), CPATH_STR("tb"), - CPATH_STR("pb"), CPATH_STR("eb"), CPATH_STR("zb"), CPATH_STR("yb"), -}; -static const cpath_char_t *prefixTableIEC[] = { - CPATH_STR("KiB"), CPATH_STR("MiB"), CPATH_STR("GiB"), CPATH_STR("TiB"), - CPATH_STR("PiB"), CPATH_STR("EiB"), CPATH_STR("ZiB"), CPATH_STR("YiB"), -}; -// identical to decimal upper but kept separate for the sake of readability -static const cpath_char_t *prefixTableJEDEC[] = { - CPATH_STR("KB"), CPATH_STR("MB"), CPATH_STR("GB"), CPATH_STR("TB"), - CPATH_STR("PB"), CPATH_STR("EB"), CPATH_STR("ZB"), CPATH_STR("YB"), -}; - -_CPATH_FUNC_ -double cpathGetFileSizeDec(cpath_file *file, int intervalSize) { - double size = cpathGetFileSize(file); - int steps = 0; - while (size >= intervalSize / 2 && steps < 8) { - size /= intervalSize; - steps++; - } - return size; -} - -_CPATH_FUNC_ -const cpath_char_t *cpathGetFileSizeSuffix(cpath_file *file, CPathByteRep rep) { - cpath_offset_t size = cpathGetFileSize(file); - int word = (rep & BYTE_REP_LONG) == BYTE_REP_LONG; - int byte_word = (rep & BYTE_REP_BYTE_WORD) == BYTE_REP_BYTE_WORD; - // disable both them to make comparing easier - rep &= ~BYTE_REP_LONG; - rep &= ~BYTE_REP_BYTE_WORD; - int interval = rep == BYTE_REP_IEC || rep == BYTE_REP_JEDEC ? 1024 : 1000; - - if (size < interval / 2) { - // then we just have a byte case - if (word || byte_word) { - return rep != BYTE_REP_DECIMAL_LOWER ? CPATH_STR("Bytes") - : CPATH_STR("bytes"); - } else { - return rep != BYTE_REP_DECIMAL_LOWER ? CPATH_STR("B") - : CPATH_STR("b"); - } - } - - int steps = 0; - double size_flt = size; - while (size_flt >= interval / 2 && steps < 8) { - size_flt /= (double)interval; - steps++; - } - - switch (rep) { - case BYTE_REP_IEC: return prefixTableIEC[steps - 1]; - case BYTE_REP_JEDEC: return prefixTableJEDEC[steps - 1]; - case BYTE_REP_DECIMAL: return prefixTableDecimal[steps - 1]; - case BYTE_REP_DECIMAL_LOWER: return prefixTableDecimalLower[steps - 1]; - case BYTE_REP_DECIMAL_UPPER: return prefixTableDecimalUpper[steps - 1]; - default: return nullptr; - } -} - -_CPATH_FUNC_ -void cpath_traverse( - cpath_dir *dir, int depth, int visit_subdirs, cpath_err_handler err, - cpath_traverse_it it, void *data -) { - // currently implemented recursively - if (dir == nullptr) { - errno = EINVAL; - if (err != nullptr) err(); - return; - } - cpath_file file; - while (cpathGetNextFile(dir, &file)) { - if (it != nullptr) { - it(&file, dir, depth, data); - } - if (file.isDir && visit_subdirs && !cpathFileIsSpecialHardLink(&file)) { - cpath_dir tmp; - if (!cpathFileToDir(&tmp, &file)) { - if (err) err(); - continue; - } - cpath_traverse(&tmp, depth + 1, visit_subdirs, err, it, data); - cpathCloseDir(&tmp); - } - } -} - -_CPATH_FUNC_ -int cpathMkdir(const cpath *path) { -#if defined _MSC_VER || __MINGW32__ - return CreateDirectory(path->buf, nullptr); -#else - return mkdir(path->buf, 0700) == 0; -#endif -} - -_CPATH_FUNC_ -FILE *cpathOpen(const cpath *path, const cpath_char_t *mode) { - return cpath_fopen(path->buf, mode); -} - -#endif -#ifdef __cplusplus -} -#ifndef CPATH_NO_CPP_BINDINGS - } } - -// cpp bindings -namespace cpath { -// using is a C++11 extension, we want to remain pretty -// compatible to all versions -typedef internals::cpath_time_t Time; -typedef internals::cpath_offset_t Offset; -typedef internals::cpath_char_t RawChar; -typedef internals::cpath RawPath; -typedef internals::cpath_dir RawDir; -typedef internals::cpath_file RawFile; -typedef internals::CPathByteRep ByteRep; - -struct File; -struct Path; - -typedef void(*TraversalIt)( - struct File &file, struct Dir &parent, int depth, void *data -); - -typedef void(*ErrorHandler)(); - -/* - This has to be kept to date with the other representation - Kinda disgusting but seems to be the best way to ensure encapsulation -*/ -const ByteRep BYTE_REP_JEDEC = internals::BYTE_REP_JEDEC; -const ByteRep BYTE_REP_DECIMAL = internals::BYTE_REP_DECIMAL; -const ByteRep BYTE_REP_IEC = internals::BYTE_REP_IEC; -const ByteRep BYTE_REP_DECIMAL_UPPER = internals::BYTE_REP_DECIMAL_UPPER; -const ByteRep BYTE_REP_DECIMAL_LOWER = internals::BYTE_REP_DECIMAL_LOWER; -const ByteRep BYTE_REP_LONG = internals::BYTE_REP_LONG; -const ByteRep BYTE_REP_BYTE_WORD = internals::BYTE_REP_BYTE_WORD; - -#if !defined _MSC_VER -#if defined __MINGW32__ -typedef struct _stat RawStat; -#else -typedef struct stat RawStat; -#endif -#endif - -template -struct Opt { -private: -#if __cplusplus <= 199711L - struct Data { -#else - union Data { -#endif - T raw; - Err err; - - Data(T raw) : raw(raw) {} - Data(Err err) : err(err) {} -} data; - bool ok; - -public: - Opt(T raw) : data(raw), ok(true) {} - Opt(Err err) : data(err), ok(false) {} - Opt() : data(Err()), ok(false) {} - - bool IsOk() const { - return ok; - } - - operator bool() const { - return ok; - } - - Err GetErr() const { - return data.err; - } - - T GetRaw() const { - return data.raw; - } - - T *operator*() { - if (!ok) return nullptr; - return &data.raw; - } - - T *operator->() { - if (!ok) return nullptr; - return &data.raw; - } -}; - -struct Error { - enum Type { - UNKNOWN, - INVALID_ARGUMENTS, - NAME_TOO_LONG, - NO_SUCH_FILE, - IO_ERROR, - }; - - static Type FromErrno() { - switch (errno) { - case EINVAL: return INVALID_ARGUMENTS; - case ENAMETOOLONG: return NAME_TOO_LONG; - case ENOENT: return NO_SUCH_FILE; - case EIO: return IO_ERROR; - default: return UNKNOWN; - } - } - - static void WriteToErrno(Type type) { - switch (type) { - case INVALID_ARGUMENTS: errno = EINVAL; - case NAME_TOO_LONG: errno = ENAMETOOLONG; - case NO_SUCH_FILE: errno = ENOENT; - case IO_ERROR: errno = IO_ERROR; - default: errno = 0; - } - } -}; - -struct Path { -private: - RawPath path; - -public: - inline Path(const char *str) : path(internals::cpathFromUtf8(str)) { } - -#if CPATH_UNICODE - inline Path(RawChar *str) { - internals::cpathFromStr(&path, str); - } -#endif - - inline Path() { - path.len = 1; - path.buf[0] = CPATH_STR('.'); - path.buf[1] = CPATH_STR('\0'); - } - - inline Path(RawPath path) : path(path) {} - - static inline Path GetCwd() { - return Path(internals::cpathGetCwd()); - } - - inline bool Exists() const { - return internals::cpathExists(&path); - } - - inline const RawChar *GetBuffer() const { - return path.buf; - } - - inline const RawPath *GetRawPath() const { - return &path; - } - - inline unsigned long Size() const { - return path.len; - } - - inline friend bool operator==(const Path &p1, const Path &p2) { - if (p1.path.len != p2.path.len) return false; - return !cpath_str_compare_safe(p1.path.buf, p2.path.buf, p1.path.len); - } - - inline friend bool operator!=(const Path &p1, const Path &p2) { - return !(p1 == p2); - } - - inline Path &operator/=(const Path &other) { - internals::cpathConcat(&path, &other.path); - return *this; - } - - inline Path &operator/=(const RawChar *str) { - internals::cpathConcatStr(&path, str); - return *this; - } - -#if CPATH_UNICODE - inline Path &operator/=(const char *str) { - internals::cpathConcatStr(&path, str); - return *this; - } -#endif - - inline friend Path operator/(Path lhs, const Path &rhs) { - lhs /= rhs; - return lhs; - } - - inline friend Path operator/(Path lhs, const char *&rhs) { - lhs /= rhs; - return lhs; - } - -#if CPATH_UNICODE - inline friend Path operator/(Path lhs, const RawChar *&rhs) { - lhs /= rhs; - return lhs; - } -#endif -}; - -struct File { -private: - RawFile file; - bool hasArg; - -public: - inline bool LoadFileInfo() { - return internals::cpathGetFileInfo(&file); - } - - inline bool LoadFlags(struct Dir &dir, void *data); - - inline RawFile *GetRawFile() { - return &file; - } - - inline const RawFile *GetRawFileConst() const { - return &file; - } - - inline File(RawFile file) : file(file), hasArg(true) {} - inline File() : hasArg(false) {} - - inline static Opt OpenFile(const Path &path) { - RawFile file; - if (!internals::cpathOpenFile(&file, path.GetRawPath())) { - return Error::FromErrno(); - } else { - return File(file); - } - } - - inline Opt ToDir() const; - - inline bool IsSpecialHardLink() const { - if (!hasArg) { - errno = EINVAL; - return false; - } - return internals::cpathFileIsSpecialHardLink(&file); - } - - inline bool IsDir() const { - if (!hasArg) { - errno = EINVAL; - return false; - } - return file.isDir; - } - - inline bool IsReg() const { - if (!hasArg) { - errno = EINVAL; - return false; - } - return file.isReg; - } - - inline bool IsSym() const { - if (!hasArg) { - errno = EINVAL; - return false; - } - return file.isSym; - } - - inline const RawChar *Extension() { - if (internals::cpathGetExtension(&file)) { - return file.extension; - } else { - return nullptr; - } - } - - inline Time GetLastAccess() { - return internals::cpathGetLastAccess(&file); - } - - inline Time GetLastModification() { - return internals::cpathGetLastModification(&file); - } - - inline Time GetFileSize() { - return internals::cpathGetFileSize(&file); - } - - inline double GetFileSizeDec(int intervalSize) { - return internals::cpathGetFileSizeDec(&file, intervalSize); - } - - inline const RawChar *GetFileSizeSuffix(ByteRep rep) { - return internals::cpathGetFileSizeSuffix(&file, rep); - } - - inline Path GetPath() const { - return cpath::Path(file.path); - } - - inline const RawChar *Name() { - return file.name; - } - -#if !defined _MSC_VER -#if defined __MINGW32__ - inline RawStat Stat() { - if (!file.statLoaded) internals::cpathGetFileInfo(&file); - return file.stat; - } -#else - inline RawStat Stat() { - if (!file.statLoaded) internals::cpathGetFileInfo(&file); - return file.stat; - } -#endif -#endif -}; - -struct Dir { -private: - RawDir dir; - bool loadedFiles; - -public: - inline Dir(const Path &path) { - loadedFiles = false; - internals::cpathOpenDir(&dir, path.GetRawPath()); - } - inline Dir(RawDir dir) : dir(dir) { - loadedFiles = false; - } - inline Dir() { - loadedFiles = false; - internals::cpathOpenDir(&dir, Path().GetRawPath()); - } - - inline static Opt Open(const File &file) { - RawDir dir; - internals::cpathFileToDir(&dir, file.GetRawFileConst()); - return Dir(dir); - } - - inline void Traverse(TraversalIt it, ErrorHandler err, int visitSubDir, - int depth, void *data) { - while (Opt file = GetNextFile()) { - if (file && it) it(**file, *this, depth, data); - if (!file) { - Error::WriteToErrno(file.GetErr()); - if (err) err(); - continue; - } - - if (file->IsDir() && !file->IsSpecialHardLink()) { - Opt dir = file->ToDir(); - if (!dir) { - Error::WriteToErrno(dir.GetErr()); - if (err) err(); - continue; - } - dir->Traverse(it, err, visitSubDir, depth + 1, data); - dir->Close(); - } - } - } - - inline bool OpenEmplace(const Path &path) { - internals::cpathCloseDir(&dir); - loadedFiles = false; - return internals::cpathOpenDir(&dir, path.GetRawPath()); - } - - inline void Close() { - return internals::cpathCloseDir(&dir); - } - - inline void Sort(internals::cpath_cmp cmp) { - loadedFiles = true; // will load files as required - internals::cpathSort(&dir, cmp); - } - - inline bool MoveNext() { - return internals::cpathMoveNextFile(&dir); - } - - inline RawDir *GetRawDir() { - return &dir; - } - - inline Opt PeekNextFile() { - RawFile file; - if (!internals::cpathPeekNextFile(&dir, &file)) { - return Error::FromErrno(); - } else { - return File(file); - } - } - - inline Opt GetNextFile() { - RawFile file; - file.extension = nullptr; - if (!internals::cpathGetNextFile(&dir, &file)) { - return Error::FromErrno(); - } else { - return File(file); - } - } - - inline bool LoadFiles() { - loadedFiles = true; - return internals::cpathLoadAllFiles(&dir); - } - - inline unsigned long Size() { - if (!loadedFiles && !LoadFiles()) return 0; - return dir.size; - } - - inline Opt GetFile(unsigned long n) { - RawFile file; - if (!internals::cpathGetFile(&dir, &file, n)) { - return Error::FromErrno(); - } else { - return File(file); - } - } - - inline bool OpenSubFileEmplace(const File &file, bool saveDir) { - loadedFiles = false; - return internals::cpathOpenSubFileEmplace(&dir, file.GetRawFileConst(), saveDir); - } - - inline bool OpenSubDirEmplace(unsigned int n, bool saveDir) { - loadedFiles = false; - return internals::cpathOpenSubDirEmplace(&dir, n, saveDir); - } - - inline Opt OpenSubDir(unsigned int n) { - RawDir raw; - if (!internals::cpathOpenSubDir(&raw, &dir, n)) { - return Error::FromErrno(); - } else { - return Dir(raw); - } - } - - inline Dir OpenNextSubDir() { - RawDir raw; - internals::cpathOpenNextSubDir(&raw, &dir); - return Dir(raw); - } - - inline Dir OpenCurrentSubDir() { - RawDir raw; - internals::cpathOpenCurrentSubDir(&raw, &dir); - return Dir(raw); - } - - inline bool OpenNextSubDirEmplace(bool saveDir) { - loadedFiles = false; - return internals::cpathOpenNextSubDirEmplace(&dir, saveDir); - } - - inline bool OpenCurrentSubDirEmplace(bool saveDir) { - loadedFiles = false; - return internals::cpathOpenCurrentSubDirEmplace(&dir, saveDir); - } - - inline bool RevertEmplace() { - return internals::cpathRevertEmplaceCopy(&dir); - } -}; - -bool File::LoadFlags(struct Dir &dir, void *data) { - return internals::cpathLoadFlags(dir.GetRawDir(), &file, data); -} - -Opt File::ToDir() const { - return Dir::Open(*this); -} - -} - -#endif -#endif /* CPath */ - diff --git a/Libraries/ghc_filesystem b/Libraries/ghc_filesystem new file mode 160000 index 0000000000..2fc4b46375 --- /dev/null +++ b/Libraries/ghc_filesystem @@ -0,0 +1 @@ +Subproject commit 2fc4b463759e043476fc0036da094e5877e3dd50 diff --git a/Libraries/tinydir/tinydir.h b/Libraries/tinydir/tinydir.h deleted file mode 100644 index ba20c3e49c..0000000000 --- a/Libraries/tinydir/tinydir.h +++ /dev/null @@ -1,838 +0,0 @@ -/* -Copyright (c) 2013-2021, tinydir authors: -- Cong Xu -- Lautis Sun -- Baudouin Feildel -- Andargor -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#ifndef TINYDIR_H -#define TINYDIR_H - -#ifdef __cplusplus -extern "C" { -#endif - -#if ((defined _UNICODE) && !(defined UNICODE)) -#define UNICODE -#endif - -#if ((defined UNICODE) && !(defined _UNICODE)) -#define _UNICODE -#endif - -#include -#include -#include -#ifdef _MSC_VER -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include -# include -# pragma warning(push) -# pragma warning (disable : 4996) -#else -# include -# include -# include -# include -#endif -#ifdef __MINGW32__ -# include -#endif - - -/* types */ - -/* Windows UNICODE wide character support */ -#if defined _MSC_VER || defined __MINGW32__ -# define _tinydir_char_t TCHAR -# define TINYDIR_STRING(s) _TEXT(s) -# define _tinydir_strlen _tcslen -# define _tinydir_strcpy _tcscpy -# define _tinydir_strcat _tcscat -# define _tinydir_strcmp _tcscmp -# define _tinydir_strrchr _tcsrchr -# define _tinydir_strncmp _tcsncmp -#else -# define _tinydir_char_t char -# define TINYDIR_STRING(s) s -# define _tinydir_strlen strlen -# define _tinydir_strcpy strcpy -# define _tinydir_strcat strcat -# define _tinydir_strcmp strcmp -# define _tinydir_strrchr strrchr -# define _tinydir_strncmp strncmp -#endif - -#if (defined _MSC_VER || defined __MINGW32__) -# include -# define _TINYDIR_PATH_MAX MAX_PATH -#elif defined __linux__ -# include -# ifdef PATH_MAX -# define _TINYDIR_PATH_MAX PATH_MAX -# endif -#elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) -# include -# if defined(BSD) -# include -# ifdef PATH_MAX -# define _TINYDIR_PATH_MAX PATH_MAX -# endif -# endif -#endif - -#ifndef _TINYDIR_PATH_MAX -#define _TINYDIR_PATH_MAX 4096 -#endif - -#ifdef _MSC_VER -/* extra chars for the "\\*" mask */ -# define _TINYDIR_PATH_EXTRA 2 -#else -# define _TINYDIR_PATH_EXTRA 0 -#endif - -#define _TINYDIR_FILENAME_MAX 256 - -#if (defined _MSC_VER || defined __MINGW32__) -#define _TINYDIR_DRIVE_MAX 3 -#endif - -#ifdef _MSC_VER -# define _TINYDIR_FUNC static __inline -#elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L -# define _TINYDIR_FUNC static __inline__ -#elif defined(__cplusplus) -# define _TINYDIR_FUNC static inline -#elif defined(__GNUC__) -/* Suppress unused function warning */ -# define _TINYDIR_FUNC __attribute__((unused)) static -#else -# define _TINYDIR_FUNC static -#endif - -/* readdir_r usage; define TINYDIR_USE_READDIR_R to use it (if supported) */ -#ifdef TINYDIR_USE_READDIR_R - -/* readdir_r is a POSIX-only function, and may not be available under various - * environments/settings, e.g. MinGW. Use readdir fallback */ -#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE ||\ - _POSIX_SOURCE -# define _TINYDIR_HAS_READDIR_R -#endif -#if _POSIX_C_SOURCE >= 200112L -# define _TINYDIR_HAS_FPATHCONF -# include -#endif -#if _BSD_SOURCE || _SVID_SOURCE || \ - (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700) -# define _TINYDIR_HAS_DIRFD -# include -#endif -#if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\ - defined _PC_NAME_MAX -# define _TINYDIR_USE_FPATHCONF -#endif -#if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\ - !(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX) -# define _TINYDIR_USE_READDIR -#endif - -/* Use readdir by default */ -#else -# define _TINYDIR_USE_READDIR -#endif - -/* MINGW32 has two versions of dirent, ASCII and UNICODE*/ -#ifndef _MSC_VER -#if (defined __MINGW32__) && (defined _UNICODE) -#define _TINYDIR_DIR _WDIR -#define _tinydir_dirent _wdirent -#define _tinydir_opendir _wopendir -#define _tinydir_readdir _wreaddir -#define _tinydir_closedir _wclosedir -#else -#define _TINYDIR_DIR DIR -#define _tinydir_dirent dirent -#define _tinydir_opendir opendir -#define _tinydir_readdir readdir -#define _tinydir_closedir closedir -#endif -#endif - -/* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */ -#if defined(_TINYDIR_MALLOC) && defined(_TINYDIR_FREE) -#elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE) -#else -#error "Either define both alloc and free or none of them!" -#endif - -#if !defined(_TINYDIR_MALLOC) - #define _TINYDIR_MALLOC(_size) malloc(_size) - #define _TINYDIR_FREE(_ptr) free(_ptr) -#endif /* !defined(_TINYDIR_MALLOC) */ - -typedef struct tinydir_file -{ - _tinydir_char_t path[_TINYDIR_PATH_MAX]; - _tinydir_char_t name[_TINYDIR_FILENAME_MAX]; - _tinydir_char_t *extension; - int is_dir; - int is_reg; - -#ifndef _MSC_VER -#ifdef __MINGW32__ - struct _stat _s; -#else - struct stat _s; -#endif -#endif -} tinydir_file; - -typedef struct tinydir_dir -{ - _tinydir_char_t path[_TINYDIR_PATH_MAX]; - int has_next; - size_t n_files; - - tinydir_file *_files; -#ifdef _MSC_VER - HANDLE _h; - WIN32_FIND_DATA _f; -#else - _TINYDIR_DIR *_d; - struct _tinydir_dirent *_e; -#ifndef _TINYDIR_USE_READDIR - struct _tinydir_dirent *_ep; -#endif -#endif -} tinydir_dir; - - -/* declarations */ - -_TINYDIR_FUNC -int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path); -_TINYDIR_FUNC -int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path); -_TINYDIR_FUNC -void tinydir_close(tinydir_dir *dir); - -_TINYDIR_FUNC -int tinydir_next(tinydir_dir *dir); -_TINYDIR_FUNC -int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file); -_TINYDIR_FUNC -int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i); -_TINYDIR_FUNC -int tinydir_open_subdir_n(tinydir_dir *dir, size_t i); - -_TINYDIR_FUNC -int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path); -_TINYDIR_FUNC -void _tinydir_get_ext(tinydir_file *file); -_TINYDIR_FUNC -int _tinydir_file_cmp(const void *a, const void *b); -#ifndef _MSC_VER -#ifndef _TINYDIR_USE_READDIR -_TINYDIR_FUNC -size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp); -#endif -#endif - - -/* definitions*/ - -_TINYDIR_FUNC -int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path) -{ -#ifndef _MSC_VER -#ifndef _TINYDIR_USE_READDIR - int error; - int size; /* using int size */ -#endif -#else - _tinydir_char_t path_buf[_TINYDIR_PATH_MAX]; -#endif - _tinydir_char_t *pathp; - - if (dir == NULL || path == NULL || _tinydir_strlen(path) == 0) - { - errno = EINVAL; - return -1; - } - if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) - { - errno = ENAMETOOLONG; - return -1; - } - - /* initialise dir */ - dir->_files = NULL; -#ifdef _MSC_VER - dir->_h = INVALID_HANDLE_VALUE; -#else - dir->_d = NULL; -#ifndef _TINYDIR_USE_READDIR - dir->_ep = NULL; -#endif -#endif - tinydir_close(dir); - - _tinydir_strcpy(dir->path, path); - /* Remove trailing slashes */ - pathp = &dir->path[_tinydir_strlen(dir->path) - 1]; - while (pathp != dir->path && (*pathp == TINYDIR_STRING('\\') || *pathp == TINYDIR_STRING('/'))) - { - *pathp = TINYDIR_STRING('\0'); - pathp++; - } -#ifdef _MSC_VER - _tinydir_strcpy(path_buf, dir->path); - _tinydir_strcat(path_buf, TINYDIR_STRING("\\*")); -#if (defined WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) - dir->_h = FindFirstFileEx(path_buf, FindExInfoStandard, &dir->_f, FindExSearchNameMatch, NULL, 0); -#else - dir->_h = FindFirstFile(path_buf, &dir->_f); -#endif - if (dir->_h == INVALID_HANDLE_VALUE) - { - errno = ENOENT; -#else - dir->_d = _tinydir_opendir(path); - if (dir->_d == NULL) - { -#endif - goto bail; - } - - /* read first file */ - dir->has_next = 1; -#ifndef _MSC_VER -#ifdef _TINYDIR_USE_READDIR - dir->_e = _tinydir_readdir(dir->_d); -#else - /* allocate dirent buffer for readdir_r */ - size = _tinydir_dirent_buf_size(dir->_d); /* conversion to int */ - if (size == -1) return -1; - dir->_ep = (struct _tinydir_dirent*)_TINYDIR_MALLOC(size); - if (dir->_ep == NULL) return -1; - - error = readdir_r(dir->_d, dir->_ep, &dir->_e); - if (error != 0) return -1; -#endif - if (dir->_e == NULL) - { - dir->has_next = 0; - } -#endif - - return 0; - -bail: - tinydir_close(dir); - return -1; -} - -_TINYDIR_FUNC -int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path) -{ - /* Count the number of files first, to pre-allocate the files array */ - size_t n_files = 0; - if (tinydir_open(dir, path) == -1) - { - return -1; - } - while (dir->has_next) - { - n_files++; - if (tinydir_next(dir) == -1) - { - goto bail; - } - } - tinydir_close(dir); - - if (n_files == 0 || tinydir_open(dir, path) == -1) - { - return -1; - } - - dir->n_files = 0; - dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files); - if (dir->_files == NULL) - { - goto bail; - } - while (dir->has_next) - { - tinydir_file *p_file; - dir->n_files++; - - p_file = &dir->_files[dir->n_files - 1]; - if (tinydir_readfile(dir, p_file) == -1) - { - goto bail; - } - - if (tinydir_next(dir) == -1) - { - goto bail; - } - - /* Just in case the number of files has changed between the first and - second reads, terminate without writing into unallocated memory */ - if (dir->n_files == n_files) - { - break; - } - } - - qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp); - - return 0; - -bail: - tinydir_close(dir); - return -1; -} - -_TINYDIR_FUNC -void tinydir_close(tinydir_dir *dir) -{ - if (dir == NULL) - { - return; - } - - memset(dir->path, 0, sizeof(dir->path)); - dir->has_next = 0; - dir->n_files = 0; - _TINYDIR_FREE(dir->_files); - dir->_files = NULL; -#ifdef _MSC_VER - if (dir->_h != INVALID_HANDLE_VALUE) - { - FindClose(dir->_h); - } - dir->_h = INVALID_HANDLE_VALUE; -#else - if (dir->_d) - { - _tinydir_closedir(dir->_d); - } - dir->_d = NULL; - dir->_e = NULL; -#ifndef _TINYDIR_USE_READDIR - _TINYDIR_FREE(dir->_ep); - dir->_ep = NULL; -#endif -#endif -} - -_TINYDIR_FUNC -int tinydir_next(tinydir_dir *dir) -{ - if (dir == NULL) - { - errno = EINVAL; - return -1; - } - if (!dir->has_next) - { - errno = ENOENT; - return -1; - } - -#ifdef _MSC_VER - if (FindNextFile(dir->_h, &dir->_f) == 0) -#else -#ifdef _TINYDIR_USE_READDIR - dir->_e = _tinydir_readdir(dir->_d); -#else - if (dir->_ep == NULL) - { - return -1; - } - if (readdir_r(dir->_d, dir->_ep, &dir->_e) != 0) - { - return -1; - } -#endif - if (dir->_e == NULL) -#endif - { - dir->has_next = 0; -#ifdef _MSC_VER - if (GetLastError() != ERROR_SUCCESS && - GetLastError() != ERROR_NO_MORE_FILES) - { - tinydir_close(dir); - errno = EIO; - return -1; - } -#endif - } - - return 0; -} - -_TINYDIR_FUNC -int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file) -{ - const _tinydir_char_t *filename; - if (dir == NULL || file == NULL) - { - errno = EINVAL; - return -1; - } -#ifdef _MSC_VER - if (dir->_h == INVALID_HANDLE_VALUE) -#else - if (dir->_e == NULL) -#endif - { - errno = ENOENT; - return -1; - } - filename = -#ifdef _MSC_VER - dir->_f.cFileName; -#else - dir->_e->d_name; -#endif - if (_tinydir_strlen(dir->path) + - _tinydir_strlen(filename) + 1 + _TINYDIR_PATH_EXTRA >= - _TINYDIR_PATH_MAX) - { - /* the path for the file will be too long */ - errno = ENAMETOOLONG; - return -1; - } - if (_tinydir_strlen(filename) >= _TINYDIR_FILENAME_MAX) - { - errno = ENAMETOOLONG; - return -1; - } - - _tinydir_strcpy(file->path, dir->path); - if (_tinydir_strcmp(dir->path, TINYDIR_STRING("/")) != 0) - _tinydir_strcat(file->path, TINYDIR_STRING("/")); - _tinydir_strcpy(file->name, filename); - _tinydir_strcat(file->path, filename); -#ifndef _MSC_VER -#ifdef __MINGW32__ - if (_tstat( -#elif (defined _BSD_SOURCE) || (defined _DEFAULT_SOURCE) \ - || ((defined _XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500)) \ - || ((defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L)) \ - || ((defined __APPLE__) && (defined __MACH__)) \ - || (defined BSD) - if (lstat( -#else - if (stat( -#endif - file->path, &file->_s) == -1) - { - return -1; - } -#endif - _tinydir_get_ext(file); - - file->is_dir = -#ifdef _MSC_VER - !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); -#else - S_ISDIR(file->_s.st_mode); -#endif - file->is_reg = -#ifdef _MSC_VER - !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) || - ( - !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) && - !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && - !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) && -#ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM - !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) && -#endif -#ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA - !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) && -#endif - !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) && - !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)); -#else - S_ISREG(file->_s.st_mode); -#endif - - return 0; -} - -_TINYDIR_FUNC -int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i) -{ - if (dir == NULL || file == NULL) - { - errno = EINVAL; - return -1; - } - if (i >= dir->n_files) - { - errno = ENOENT; - return -1; - } - - memcpy(file, &dir->_files[i], sizeof(tinydir_file)); - _tinydir_get_ext(file); - - return 0; -} - -_TINYDIR_FUNC -int tinydir_open_subdir_n(tinydir_dir *dir, size_t i) -{ - _tinydir_char_t path[_TINYDIR_PATH_MAX]; - if (dir == NULL) - { - errno = EINVAL; - return -1; - } - if (i >= dir->n_files || !dir->_files[i].is_dir) - { - errno = ENOENT; - return -1; - } - - _tinydir_strcpy(path, dir->_files[i].path); - tinydir_close(dir); - if (tinydir_open_sorted(dir, path) == -1) - { - return -1; - } - - return 0; -} - -/* Open a single file given its path */ -_TINYDIR_FUNC -int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path) -{ - tinydir_dir dir; - int result = 0; - int found = 0; - _tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX]; - _tinydir_char_t file_name_buf[_TINYDIR_FILENAME_MAX]; - _tinydir_char_t *dir_name; - _tinydir_char_t *base_name; -#if (defined _MSC_VER || defined __MINGW32__) - _tinydir_char_t drive_buf[_TINYDIR_PATH_MAX]; - _tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX]; -#endif - - if (file == NULL || path == NULL || _tinydir_strlen(path) == 0) - { - errno = EINVAL; - return -1; - } - if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) - { - errno = ENAMETOOLONG; - return -1; - } - - /* Get the parent path */ -#if (defined _MSC_VER || defined __MINGW32__) -#if ((defined _MSC_VER) && (_MSC_VER >= 1400)) - errno = _tsplitpath_s( - path, - drive_buf, _TINYDIR_DRIVE_MAX, - dir_name_buf, _TINYDIR_FILENAME_MAX, - file_name_buf, _TINYDIR_FILENAME_MAX, - ext_buf, _TINYDIR_FILENAME_MAX); -#else - _tsplitpath( - path, - drive_buf, - dir_name_buf, - file_name_buf, - ext_buf); -#endif - - if (errno) - { - return -1; - } - -/* _splitpath_s not work fine with only filename and widechar support */ -#ifdef _UNICODE - if (drive_buf[0] == L'\xFEFE') - drive_buf[0] = '\0'; - if (dir_name_buf[0] == L'\xFEFE') - dir_name_buf[0] = '\0'; -#endif - - /* Emulate the behavior of dirname by returning "." for dir name if it's - empty */ - if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0') - { - _tinydir_strcpy(dir_name_buf, TINYDIR_STRING(".")); - } - /* Concatenate the drive letter and dir name to form full dir name */ - _tinydir_strcat(drive_buf, dir_name_buf); - dir_name = drive_buf; - /* Concatenate the file name and extension to form base name */ - _tinydir_strcat(file_name_buf, ext_buf); - base_name = file_name_buf; -#else - _tinydir_strcpy(dir_name_buf, path); - dir_name = dirname(dir_name_buf); - _tinydir_strcpy(file_name_buf, path); - base_name = basename(file_name_buf); -#endif - - /* Special case: if the path is a root dir, open the parent dir as the file */ -#if (defined _MSC_VER || defined __MINGW32__) - if (_tinydir_strlen(base_name) == 0) -#else - if ((_tinydir_strcmp(base_name, TINYDIR_STRING("/"))) == 0) -#endif - { - memset(file, 0, sizeof * file); - file->is_dir = 1; - file->is_reg = 0; - _tinydir_strcpy(file->path, dir_name); - file->extension = file->path + _tinydir_strlen(file->path); - return 0; - } - - /* Open the parent directory */ - if (tinydir_open(&dir, dir_name) == -1) - { - return -1; - } - - /* Read through the parent directory and look for the file */ - while (dir.has_next) - { - if (tinydir_readfile(&dir, file) == -1) - { - result = -1; - goto bail; - } - if (_tinydir_strcmp(file->name, base_name) == 0) - { - /* File found */ - found = 1; - break; - } - tinydir_next(&dir); - } - if (!found) - { - result = -1; - errno = ENOENT; - } - -bail: - tinydir_close(&dir); - return result; -} - -_TINYDIR_FUNC -void _tinydir_get_ext(tinydir_file *file) -{ - _tinydir_char_t *period = _tinydir_strrchr(file->name, TINYDIR_STRING('.')); - if (period == NULL) - { - file->extension = &(file->name[_tinydir_strlen(file->name)]); - } - else - { - file->extension = period + 1; - } -} - -_TINYDIR_FUNC -int _tinydir_file_cmp(const void *a, const void *b) -{ - const tinydir_file *fa = (const tinydir_file *)a; - const tinydir_file *fb = (const tinydir_file *)b; - if (fa->is_dir != fb->is_dir) - { - return -(fa->is_dir - fb->is_dir); - } - return _tinydir_strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX); -} - -#ifndef _MSC_VER -#ifndef _TINYDIR_USE_READDIR -/* -The following authored by Ben Hutchings -from https://womble.decadent.org.uk/readdir_r-advisory.html -*/ -/* Calculate the required buffer size (in bytes) for directory * -* entries read from the given directory handle. Return -1 if this * -* this cannot be done. * -* * -* This code does not trust values of NAME_MAX that are less than * -* 255, since some systems (including at least HP-UX) incorrectly * -* define it to be a smaller value. */ -_TINYDIR_FUNC -size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp) -{ - long name_max; - size_t name_end; - /* parameter may be unused */ - (void)dirp; - -#if defined _TINYDIR_USE_FPATHCONF - name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX); - if (name_max == -1) -#if defined(NAME_MAX) - name_max = (NAME_MAX > 255) ? NAME_MAX : 255; -#else - return (size_t)(-1); -#endif -#elif defined(NAME_MAX) - name_max = (NAME_MAX > 255) ? NAME_MAX : 255; -#else -#error "buffer size for readdir_r cannot be determined" -#endif - name_end = (size_t)offsetof(struct _tinydir_dirent, d_name) + name_max + 1; - return (name_end > sizeof(struct _tinydir_dirent) ? - name_end : sizeof(struct _tinydir_dirent)); -} -#endif -#endif - -#ifdef __cplusplus -} -#endif - -# if defined (_MSC_VER) -# pragma warning(pop) -# endif - -#endif diff --git a/Source/Utility/OSUtils.cpp b/Source/Utility/OSUtils.cpp index 7c171b8fe4..65c774f14b 100644 --- a/Source/Utility/OSUtils.cpp +++ b/Source/Utility/OSUtils.cpp @@ -30,16 +30,15 @@ #if HAS_STD_FILESYSTEM # if defined(__cpp_lib_filesystem) # include +namespace fs = std::filesystem; # elif defined(__cpp_lib_experimental_filesystem) # include -namespace std { -namespace filesystem = experimental::filesystem; -} +namespace fs = std::experimental::filesystem; # endif #else -# include "../Libraries/cpath/cpath.h" - +# include +namespace fs = ghc::filesystem; #endif #if defined(_WIN32) || defined(_WIN64) @@ -123,7 +122,7 @@ void OSUtils::createJunction(std::string from, std::string to) void OSUtils::createHardLink(std::string from, std::string to) { - std::filesystem::create_hard_link(from, to); + fs::create_hard_link(from, to); } // Function to run a command as admin on Windows @@ -297,9 +296,6 @@ OSUtils::KeyboardLayout OSUtils::getKeyboardLayout() } #endif // Linux/BSD -// Use std::filesystem directory iterator if available -// On old versions of GCC and macos <10.15, std::filesystem is not available -#if HAS_STD_FILESYSTEM juce::Array OSUtils::iterateDirectory(juce::File const& directory, bool recursive, bool onlyFiles, int maximum) { @@ -307,7 +303,7 @@ juce::Array OSUtils::iterateDirectory(juce::File const& directory, b if (recursive) { try { - for (auto const& dirEntry : std::filesystem::recursive_directory_iterator(directory.getFullPathName().toStdString())) { + for (auto const& dirEntry : fs::recursive_directory_iterator(directory.getFullPathName().toStdString())) { auto isDir = dirEntry.is_directory(); if ((isDir && !onlyFiles) || !isDir) { result.add(juce::File(dirEntry.path().string())); @@ -316,12 +312,12 @@ juce::Array OSUtils::iterateDirectory(juce::File const& directory, b if (maximum > 0 && result.size() >= maximum) break; } - } catch (std::filesystem::filesystem_error e) { + } catch (fs::filesystem_error e) { std::cerr << "Error while iterating over directory: " << e.path1() << std::endl; } } else { try { - for (auto const& dirEntry : std::filesystem::directory_iterator(directory.getFullPathName().toStdString())) { + for (auto const& dirEntry : fs::directory_iterator(directory.getFullPathName().toStdString())) { auto isDir = dirEntry.is_directory(); if ((isDir && !onlyFiles) || !isDir) { result.add(juce::File(dirEntry.path().string())); @@ -330,7 +326,7 @@ juce::Array OSUtils::iterateDirectory(juce::File const& directory, b if (maximum > 0 && result.size() >= maximum) break; } - } catch (std::filesystem::filesystem_error e) { + } catch (fs::filesystem_error e) { std::cerr << "Error while iterating over directory: " << e.path1() << std::endl; } } @@ -338,42 +334,6 @@ juce::Array OSUtils::iterateDirectory(juce::File const& directory, b return result; } -// Otherwise use cpath -#else - -static juce::Array iterateDirectoryRecurse(cpath::Dir&& dir, bool recursive, bool onlyFiles, int maximum) -{ - juce::Array result; - - while (cpath::Opt file = dir.GetNextFile()) { - auto isDir = file->IsDir(); - - if (isDir && recursive && !file->IsSpecialHardLink()) { - result.addArray(iterateDirectoryRecurse(file->ToDir().GetRaw(), recursive, onlyFiles, maximum)); - } - if ((isDir && !onlyFiles) || !isDir) { - auto path = juce::String::fromUTF8(file->GetPath().GetRawPath()->buf); - if(!path.isEmpty() && !path.endsWith("/.") && !path.endsWith("/..")) result.add(juce::File(path)); - } - - if (maximum > 0 && result.size() >= maximum) - break; - } - - dir.Close(); - - return result; -} - -juce::Array OSUtils::iterateDirectory(juce::File const& directory, bool recursive, bool onlyFiles, int maximum) -{ - auto pathName = directory.getFullPathName(); - auto dir = cpath::Dir(pathName.toRawUTF8()); - return iterateDirectoryRecurse(std::move(dir), recursive, onlyFiles, maximum); -} - -#endif - // needs to be in OSutils because it needs unsigned int OSUtils::keycodeToHID(unsigned int scancode) { From aa7d43874b2497ed11c1fcc321d6fabc32e316e1 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 6 Feb 2024 12:49:16 +0100 Subject: [PATCH 0190/1030] Only package Gem resources if Gem is enabled --- CMakeLists.txt | 3 +- Resources/Scripts/package_resources.py | 79 +++++++++++++++----------- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 360908dd4c..ff5f5e422a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,8 +90,7 @@ message(STATUS "Preparing documentation") execute_process(COMMAND python3 parse_documentation.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Scripts) message(STATUS "Packaging resources") -execute_process(COMMAND python3 package_resources.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Scripts) - +execute_process(COMMAND python3 package_resources.py ${ENABLE_GEM} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Scripts) set(PLUGDATA_VERSION "0.8.4") set(PLUGDATA_COMPANY_NAME "plugdata") diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index eb2afc0a9e..df3bb324e6 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -69,7 +69,6 @@ def splitFile(file, num_files): makeDir("Abstractions") makeDir("Abstractions/else") makeDir("Abstractions/cyclone") -makeDir("Abstractions/Gem") copyDir("../../Libraries/pure-data/doc", "./Documentation") globCopy("../../Libraries/pure-data/extra/*.pd", "./Abstractions") @@ -126,38 +125,52 @@ def splitFile(file, num_files): for src in ["pdlua"]: copyDir(pdlua_srcdir+src, "./Documentation/13.pdlua/"+src) -copyDir("../../Libraries/Gem/help", "Documentation/14.gem") -copyDir("../../Libraries/Gem/examples", "Documentation/14.gem/examples") -copyDir("../../Libraries/Gem/doc", "Documentation/14.gem/examples/Documentation") -globCopy("../../Libraries/Gem/abstractions/*.pd", "Abstractions/Gem/") -globMove("Abstractions/Gem/*-help.pd", "Documentation/14.gem/") - -makeDir("Extra/Gem") # user can put plugins and resources in here - -# extract precompiled Gem plugins for our architecture -system = platform.system().lower() -architecture = platform.architecture() -machine = platform.machine() - -gem_plugin_path = "../../Libraries/Gem/" -gem_plugins_file = "" - -if system == 'linux': - if 'aarch64' in machine or 'arm' in machine: - gem_plugins_file = 'plugins_linux_arm64' - elif '64' in architecture: - gem_plugins_file = 'plugins_linux_x64' -elif system == 'darwin': - gem_plugins_file = 'plugins_macos' -elif system == 'windows' and '64' in architecture[0]: - gem_plugins_file = 'plugins_win64' - -# unpack if architecture is supported -if len(gem_plugins_file) != 0: - with zipfile.ZipFile(gem_plugin_path + gem_plugins_file + ".zip", 'r') as zip_ref: - zip_ref.extractall("Extra/Gem/") - globMove("Extra/Gem/" + gem_plugins_file + "/*", "Extra/Gem/") - removeDir("Extra/Gem/" + gem_plugins_file) +value_mappings = { + "0": False, + "1": True, + "ON": True, + "OFF": False, + "TRUE": True, + "FALSE": False +} + +package_gem = value_mappings[sys.argv[1].upper()] + +if package_gem: + makeDir("Abstractions/Gem") + + copyDir("../../Libraries/Gem/help", "Documentation/14.gem") + copyDir("../../Libraries/Gem/examples", "Documentation/14.gem/examples") + copyDir("../../Libraries/Gem/doc", "Documentation/14.gem/examples/Documentation") + globCopy("../../Libraries/Gem/abstractions/*.pd", "Abstractions/Gem/") + globMove("Abstractions/Gem/*-help.pd", "Documentation/14.gem/") + + makeDir("Extra/Gem") # user can put plugins and resources in here + + # extract precompiled Gem plugins for our architecture + system = platform.system().lower() + architecture = platform.architecture() + machine = platform.machine() + + gem_plugin_path = "../../Libraries/Gem/" + gem_plugins_file = "" + + if system == 'linux': + if 'aarch64' in machine or 'arm' in machine: + gem_plugins_file = 'plugins_linux_arm64' + elif '64' in architecture: + gem_plugins_file = 'plugins_linux_x64' + elif system == 'darwin': + gem_plugins_file = 'plugins_macos' + elif system == 'windows' and '64' in architecture[0]: + gem_plugins_file = 'plugins_win64' + + # unpack if architecture is supported + if len(gem_plugins_file) != 0: + with zipfile.ZipFile(gem_plugin_path + gem_plugins_file + ".zip", 'r') as zip_ref: + zip_ref.extractall("Extra/Gem/") + globMove("Extra/Gem/" + gem_plugins_file + "/*", "Extra/Gem/") + removeDir("Extra/Gem/" + gem_plugins_file) changeWorkingDir("./..") From 5254efa290cd9577fa27e01148548381b71ee4f7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 6 Feb 2024 12:49:44 +0100 Subject: [PATCH 0191/1030] Fixed assertion --- Source/Dialogs/Dialogs.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index 000977adce..d1c923e7ee 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -696,7 +696,9 @@ void Dialogs::showOpenDialog(std::function callback, bool canSelectFi auto lastDir = result.isDirectory() ? result : result.getParentDirectory(); SettingsFile::getInstance()->setLastBrowserPathForId(lastFileId, lastDir); - callback(fileChooser.getURLResult()); + if(result.exists()) { + callback(fileChooser.getURLResult()); + } Dialogs::fileChooser = nullptr; }); } From a52e8e843d00a3761fbeb6fceb623655351c9d50 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 6 Feb 2024 17:14:19 +0100 Subject: [PATCH 0192/1030] Improvements to all draggable numbers, make sure [floatatom] fits as many numbers as in Pd --- Source/Components/DraggableNumber.h | 46 +++++++++++++++++-------- Source/Objects/FloatAtomObject.h | 8 +++-- Source/Objects/NumberObject.h | 4 +-- Source/Utility/StringUtils.h | 52 +++++++++++++++++++++++------ 4 files changed, 83 insertions(+), 27 deletions(-) diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index 0b28c980cb..cefb1a9de8 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -33,8 +33,7 @@ class DraggableNumber : public Label bool wasReset = false; double valueToResetTo = 0.0; double valueToRevertTo = 0.0; - - GlyphArrangement currentGlyphs; + bool showEllipses = true; public: std::function onValueChange = [](double) {}; @@ -65,6 +64,7 @@ class DraggableNumber : public Label { auto newValue = editor.getText().getDoubleValue(); setValue(newValue, dontSendNotification); + decimalDrag = 0; dragEnd(); } @@ -96,6 +96,12 @@ class DraggableNumber : public Label { logarithmicHeight = logHeight; } + + // Toggle between showing ellipses or ">" if number is too large to fit + void setShowEllipsesIfTooLong(bool shouldShowEllipses) + { + showEllipses = shouldShowEllipses; + } bool keyPressed(KeyPress const& key) override { @@ -232,7 +238,7 @@ class DraggableNumber : public Label GlyphArrangement glyphs; glyphs.addFittedText(getFont(), text, textArea.getX(), 0., 99999, getHeight(), 1, 1.0f); auto glyphsBounds = glyphs.getBoundingBox(0, glyphs.getNumGlyphs(), false); - if (x < glyphsBounds.getRight()) { + if (x < glyphsBounds.getRight() && x < getLocalBounds().getRight()) { if (position) *position = glyphsBounds; return 0; @@ -260,7 +266,7 @@ class DraggableNumber : public Label afterDecimalPoint = true; } - if (x <= glyph.getRight()) { + if (x <= glyph.getRight() && glyph.getRight() < getLocalBounds().getRight()) { draggedDecimal = isDecimalPoint ? 0 : i - decimalPointPosition; auto glyphBounds = glyph.getBounds(); @@ -291,23 +297,37 @@ class DraggableNumber : public Label PlugDataLook::fillSmoothedRectangle(g, hoveredDecimalPosition, 2.5f); } + auto font = getFont(); + if (!isBeingEdited()) { auto textArea = getBorderSize().subtractedFrom(getLocalBounds()).toFloat(); auto numberText = formatNumber(getText().getDoubleValue(), decimalDrag); auto extraNumberText = String(); auto numDecimals = numberText.fromFirstOccurrenceOf(".", false, false).length(); + auto numberTextLength = CachedFontStringWidth::calculateSingleLineWidth(font, numberText); + for (int i = 0; i < std::min(hoveredDecimal - decimalDrag, 7 - numDecimals); ++i) extraNumberText += "0"; - - auto numberTextLength = getFont().getStringWidthFloat(numberText); - - g.setFont(getFont()); + + // If show ellipses is false, only show ">" when integers are too large to fit + if(!showEllipses && numDecimals == 0) + { + while(numberTextLength > textArea.getWidth() + 3) + { + numberText = numberText.trimCharactersAtEnd(".>"); + numberText = numberText.dropLastCharacters(1); + numberText += ">"; + numberTextLength = CachedFontStringWidth::calculateSingleLineWidth(font, numberText); + } + } + + g.setFont(font); g.setColour(findColour(Label::textColourId)); - g.drawText(numberText, textArea, Justification::centredLeft); - + g.drawText(numberText, textArea, Justification::centredLeft, showEllipses); + if (dragMode == Regular) { g.setColour(findColour(Label::textColourId).withAlpha(0.4f)); - g.drawText(extraNumberText, textArea.withTrimmedLeft(numberTextLength), Justification::centredLeft); + g.drawText(extraNumberText, textArea.withTrimmedLeft(numberTextLength), Justification::centredLeft, false); } } } @@ -416,11 +436,11 @@ class DraggableNumber : public Label } } - static String formatNumber(double value, int precision = -1) + String formatNumber(double value, int precision = -1) { auto text = String(value, precision == -1 ? 8 : precision); - if (!text.containsChar('.')) + if (dragMode != Integer && !text.containsChar('.')) text << '.'; text = text.trimCharactersAtEnd("0"); diff --git a/Source/Objects/FloatAtomObject.h b/Source/Objects/FloatAtomObject.h index ff4a497758..20fa3e2148 100644 --- a/Source/Objects/FloatAtomObject.h +++ b/Source/Objects/FloatAtomObject.h @@ -28,7 +28,7 @@ class FloatAtomObject final : public ObjectBase { startEdition(); - editor->setBorder({ 0, 1, 3, 0 }); + editor->setBorder({ 0, -2, 3, 0 }); editor->setColour(TextEditor::focusedOutlineColourId, Colours::transparentBlack); if (editor != nullptr) { @@ -71,8 +71,12 @@ class FloatAtomObject final : public ObjectBase { objectParameters.addParamFloat("Minimum", cGeneral, &min); objectParameters.addParamFloat("Maximum", cGeneral, &max); atomHelper.addAtomParameters(objectParameters); + + input.setBorderSize(BorderSize(1, 2, 1, 0)); input.setResetValue(0.0f); + input.setShowEllipsesIfTooLong(false); + lookAndFeelChanged(); } @@ -175,7 +179,7 @@ class FloatAtomObject final : public ObjectBase { Rectangle getPdBounds() override { - return atomHelper.getPdBounds(input.getFont().getStringWidth(DraggableNumber::formatNumber(input.getText(true).getDoubleValue()))); + return atomHelper.getPdBounds(input.getFont().getStringWidth(input.formatNumber(input.getText(true).getDoubleValue()))); } void setPdBounds(Rectangle b) override diff --git a/Source/Objects/NumberObject.h b/Source/Objects/NumberObject.h index 59511a88e6..51b9996e67 100644 --- a/Source/Objects/NumberObject.h +++ b/Source/Objects/NumberObject.h @@ -34,7 +34,7 @@ class NumberObject final : public ObjectBase { startEdition(); editor->setColour(TextEditor::focusedOutlineColourId, Colours::transparentBlack); - editor->setBorder({ 0, 11, 3, 0 }); + editor->setBorder({ 0, 8, 4, 1 }); if (editor != nullptr) { editor->setInputRestrictions(0, "e.-0123456789"); @@ -45,7 +45,7 @@ class NumberObject final : public ObjectBase { stopEdition(); }; - input.setBorderSize({ 1, 15, 1, 1 }); + input.setBorderSize({ 1, 12, 2, 2 }); addAndMakeVisible(input); diff --git a/Source/Utility/StringUtils.h b/Source/Utility/StringUtils.h index b1f9034981..b196f5db87 100644 --- a/Source/Utility/StringUtils.h +++ b/Source/Utility/StringUtils.h @@ -82,20 +82,52 @@ struct CachedStringWidth { return maximumLineWidth; } - // Hack so you can use variables instead of only constants - template - static int variableCachedStringWidth(int size, const String& string) { - + static inline std::unordered_map stringWidthCache = std::unordered_map(); +}; + +struct CachedFontStringWidth { + + static float calculateSingleLineWidth(Font& font, const String& singleLine) + { + auto stringHash = hash(singleLine); - if constexpr(I > 64) return 0; + bool fontFound = false; + for(auto [cachedFont, cache] : stringWidthCache) + { + if(cachedFont == font) + { + fontFound = true; + + auto cacheHit = cache.find(stringHash); + if(cacheHit != cache.end()) return cacheHit->second; + + auto stringWidth = font.getStringWidthFloat(singleLine); + cache[stringHash] = stringWidth; + + return stringWidth; + } + } - if(size == I) { - return CachedStringWidth::calculateStringWidth(string); + if(!fontFound) + { + auto stringWidth = font.getStringWidth(singleLine); + stringWidthCache.push_back({font, {{stringHash, stringWidth}}}); + return stringWidth; } - else { - return variableCachedStringWidth(size, string); // Recursive call + + return font.getStringWidthFloat(singleLine); + } + + static int calculateStringWidth(Font& font, const String& string) + { + float maximumLineWidth = 7; + for(auto line : StringArray::fromLines(string)) + { + maximumLineWidth = std::max(calculateSingleLineWidth(font, line), maximumLineWidth); } + + return maximumLineWidth; } - static inline std::unordered_map stringWidthCache = std::unordered_map(); + static inline std::vector>> stringWidthCache = std::vector>>(); }; From 22c12a1fd907e3bc32af1b2938d1e7620ddca822 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 6 Feb 2024 17:54:43 +0100 Subject: [PATCH 0193/1030] Fix pdlua multi-instance support --- Libraries/pd-lua | 2 +- Source/Pd/Instance.cpp | 19 +++++++++++-------- Source/Pd/Setup.cpp | 6 ++++++ Source/Pd/Setup.h | 1 + 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Libraries/pd-lua b/Libraries/pd-lua index 5446dc0743..87a08b1051 160000 --- a/Libraries/pd-lua +++ b/Libraries/pd-lua @@ -1 +1 @@ -Subproject commit 5446dc0743194fbf1183dda0e6932f6dc3b7924e +Subproject commit 87a08b1051754edc7538a9eec16e315dffcb420e diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index c480c19ed0..c8fc32f0c4 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -291,18 +291,21 @@ void Instance::initialisePd(String& pdlua_version) class_set_extern_dir(gensym("")); set_class_prefix(nullptr); initialised = true; + + clear_class_loadsym(); + + // We want to initialise pdlua separately for each instance + auto extra = ProjectInfo::appDataDir.getChildFile("Extra"); + char vers[1000]; + *vers = 0; + pd::Setup::initialisePdLua(extra.getFullPathName().getCharPointer(), vers, 1000, ®isterLuaClass); + if (*vers) + pdlua_version = vers; } setThis(); + pd::Setup::initialisePdLuaInstance(); - clear_class_loadsym(); - // We want to initialise pdlua separately for each instance - auto extra = ProjectInfo::appDataDir.getChildFile("Extra"); - char vers[1000]; - *vers = 0; - pd::Setup::initialisePdLua(extra.getFullPathName().getCharPointer(), vers, 1000, ®isterLuaClass); - if (*vers) - pdlua_version = vers; // ag: need to do this here to suppress noise from chatty externals printReceiver = pd::Setup::createPrintHook(this, reinterpret_cast(internal::instance_multi_print)); diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index 96ec46c500..0214b17e38 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -1259,6 +1259,7 @@ void fm_tilde_setup(); void knob_setup(); void pdlua_setup(char const* datadir, char* vers, int vers_len, void(*register_class_callback)(const char*)); +void pdlua_instance_setup(); } namespace pd { @@ -1349,6 +1350,11 @@ void Setup::initialisePdLua(char const* datadir, char* vers, int vers_len, void( pdlua_setup(datadir, vers, vers_len, register_class_callback); } +void Setup::initialisePdLuaInstance() +{ + pdlua_instance_setup(); +} + void* Setup::createPrintHook(void* ptr, t_plugdata_printhook hook_print) { t_plugdata_print* x = (t_plugdata_print*)pd_new(plugdata_print_class); diff --git a/Source/Pd/Setup.h b/Source/Pd/Setup.h index d33a5c15d8..94934bef01 100644 --- a/Source/Pd/Setup.h +++ b/Source/Pd/Setup.h @@ -33,6 +33,7 @@ struct Setup { void parseArguments(char const** args, size_t argc, t_namelist** sys_openlist, t_namelist** sys_messagelist); static void initialisePdLua(char const* datadir, char* vers, int vers_len, void(*register_class_callback)(const char*)); + static void initialisePdLuaInstance(); static void initialiseELSE(); static void initialiseCyclone(); static void initialiseGem(std::string gemPluginPath); From 10d1f713ad15adf64bb81d91bfa6e942bfda7c2e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 6 Feb 2024 20:53:35 +0100 Subject: [PATCH 0194/1030] Make sure that objects always have a position --- Source/Object.cpp | 2 ++ Source/Objects/KeyboardObject.h | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index 298d528707..a6aff7aa3d 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -308,6 +308,8 @@ void Object::updateBounds() // Objects may return empty bounds if they are not a real object (like scalars) if (!newBounds.isEmpty()) setObjectBounds(newBounds); + else + setTopLeftPosition(newBounds.getX() + margin + cnv->canvasOrigin.x, newBounds.getY() + margin + + cnv->canvasOrigin.y); } if (newObjectEditor) { diff --git a/Source/Objects/KeyboardObject.h b/Source/Objects/KeyboardObject.h index 4b12778158..e8c459fc21 100644 --- a/Source/Objects/KeyboardObject.h +++ b/Source/Objects/KeyboardObject.h @@ -206,7 +206,7 @@ class KeyboardObject final : public ObjectBase Value lowC = SynchronousValue(); Value octaves = SynchronousValue(); - int numWhiteKeys = 0; + int numWhiteKeys = 8; Value sendSymbol = SynchronousValue(); Value receiveSymbol = SynchronousValue(); From 28685503a8a963187f0098438e06ff07b1cba56e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 6 Feb 2024 23:54:37 +0100 Subject: [PATCH 0195/1030] Fixed memory leak --- Source/Components/DraggableNumber.h | 4 ++-- Source/Utility/StringUtils.h | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index cefb1a9de8..99df78eaa6 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -304,7 +304,7 @@ class DraggableNumber : public Label auto numberText = formatNumber(getText().getDoubleValue(), decimalDrag); auto extraNumberText = String(); auto numDecimals = numberText.fromFirstOccurrenceOf(".", false, false).length(); - auto numberTextLength = CachedFontStringWidth::calculateSingleLineWidth(font, numberText); + auto numberTextLength = CachedFontStringWidth::get()->calculateSingleLineWidth(font, numberText); for (int i = 0; i < std::min(hoveredDecimal - decimalDrag, 7 - numDecimals); ++i) extraNumberText += "0"; @@ -317,7 +317,7 @@ class DraggableNumber : public Label numberText = numberText.trimCharactersAtEnd(".>"); numberText = numberText.dropLastCharacters(1); numberText += ">"; - numberTextLength = CachedFontStringWidth::calculateSingleLineWidth(font, numberText); + numberTextLength = CachedFontStringWidth::get()->calculateSingleLineWidth(font, numberText); } } diff --git a/Source/Utility/StringUtils.h b/Source/Utility/StringUtils.h index b196f5db87..b92a99f152 100644 --- a/Source/Utility/StringUtils.h +++ b/Source/Utility/StringUtils.h @@ -85,9 +85,9 @@ struct CachedStringWidth { static inline std::unordered_map stringWidthCache = std::unordered_map(); }; -struct CachedFontStringWidth { - - static float calculateSingleLineWidth(Font& font, const String& singleLine) +struct CachedFontStringWidth : public DeletedAtShutdown +{ + float calculateSingleLineWidth(Font& font, const String& singleLine) { auto stringHash = hash(singleLine); @@ -118,7 +118,7 @@ struct CachedFontStringWidth { return font.getStringWidthFloat(singleLine); } - static int calculateStringWidth(Font& font, const String& string) + int calculateStringWidth(Font& font, const String& string) { float maximumLineWidth = 7; for(auto line : StringArray::fromLines(string)) @@ -129,5 +129,14 @@ struct CachedFontStringWidth { return maximumLineWidth; } - static inline std::vector>> stringWidthCache = std::vector>>(); + std::vector>> stringWidthCache = std::vector>>(); + + static CachedFontStringWidth* get() + { + if(!instance) instance = new CachedFontStringWidth(); + + return instance; + } + + static inline CachedFontStringWidth* instance; }; From bab71fcdac49efc85a6b9c448cea5160fc32aac7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 6 Feb 2024 23:55:20 +0100 Subject: [PATCH 0196/1030] Better autosave time description --- Source/Utility/Autosave.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Utility/Autosave.h b/Source/Utility/Autosave.h index 989f2aed2b..f9c81c6979 100644 --- a/Source/Utility/Autosave.h +++ b/Source/Utility/Autosave.h @@ -47,10 +47,10 @@ class Autosave : public Timer auto autoSavedTime = static_cast(lastAutoSavedPatch.getProperty("LastModified")); auto fileChangedTime = patchPath.getLastModificationTime().toMilliseconds(); if (lastAutoSavedPatch.isValid() && autoSavedTime > fileChangedTime) { - int minutesDifference = (autoSavedTime - fileChangedTime) / 60000.0; - + auto timeDescription = RelativeTime((autoSavedTime - fileChangedTime) / 1000.0f).getApproximateDescription(); + Dialogs::showOkayCancelDialog( - &editor->openedDialog, editor, "Restore autosave?\n (last autosave is " + String(minutesDifference) + " minutes newer)", [lastAutoSavedPatch, patchPath, callback](bool useAutosaved) { + &editor->openedDialog, editor, "Restore autosave?\n (last autosave is " + timeDescription + " newer)", [lastAutoSavedPatch, patchPath, callback](bool useAutosaved) { if (useAutosaved) { MemoryOutputStream ostream; Base64::convertFromBase64(ostream, lastAutoSavedPatch.getProperty("Patch").toString()); From a0d34d33d4ac6f5857eff2790589321907f13a35 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 6 Feb 2024 23:55:35 +0100 Subject: [PATCH 0197/1030] Updated ELSE --- Libraries/pd-else | 2 +- Source/Utility/Config.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/pd-else b/Libraries/pd-else index c7042b5e7e..04893fcb59 160000 --- a/Libraries/pd-else +++ b/Libraries/pd-else @@ -1 +1 @@ -Subproject commit c7042b5e7ebb74c4faca5b293a77953f71edd730 +Subproject commit 04893fcb59f5f5649bd280e832d41dc8a0345499 diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index 3b8d39f572..2cc90577b7 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -38,7 +38,7 @@ struct ProjectInfo { static inline File const appDataDir = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory).getChildFile("plugdata"); - static inline String const versionSuffix = "-3"; + static inline String const versionSuffix = "-4"; static inline File const versionDataDir = appDataDir.getChildFile("Versions").getChildFile(ProjectInfo::versionString + versionSuffix); }; From a3561a38256656621d3071aa4135651d24eec198 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 7 Feb 2024 00:59:20 +0100 Subject: [PATCH 0198/1030] Make object/comment/message size a bit more similar to pd-vanilla --- Source/Iolet.cpp | 2 +- Source/Objects/CommentObject.h | 222 ++++++++++++++++----------------- Source/Objects/MessageObject.h | 8 +- Source/Objects/TextObject.h | 14 +-- 4 files changed, 123 insertions(+), 123 deletions(-) diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index e530018439..2716a35590 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -438,7 +438,7 @@ Iolet* Iolet::findNearestIolet(Canvas* cnv, Point position, bool inlet, Obj Iolet* nearestIolet = nullptr; for (auto& iolet : allEdges) { - auto bounds = iolet->getCanvasBounds().expanded(50); + auto bounds = iolet->getCanvasBounds().expanded(30); if (bounds.contains(position)) { if (!nearestIolet) nearestIolet = iolet; diff --git a/Source/Objects/CommentObject.h b/Source/Objects/CommentObject.h index 9704b386f4..46b94693ed 100644 --- a/Source/Objects/CommentObject.h +++ b/Source/Objects/CommentObject.h @@ -5,48 +5,48 @@ */ class CommentObject final : public ObjectBase - , public KeyListener - , public TextEditor::Listener { - +, public KeyListener +, public TextEditor::Listener { + bool locked; - + Value sizeProperty = SynchronousValue(); - + TextLayout textLayout; hash32 layoutTextHash = 0; int lastTextWidth = 0; int32 lastColourARGB = 0; - + std::unique_ptr editor; BorderSize border = BorderSize(1, 7, 1, 2); String objectText; - + public: CommentObject(pd::WeakReference obj, Object* object) - : ObjectBase(obj, object) + : ObjectBase(obj, object) { objectParameters.addParamInt("Width (chars)", cDimensions, &sizeProperty); locked = getValue(object->locked); updateTextLayout(); } - + bool isTransparent() override { return true; } - + void update() override { objectText = getText().trimEnd(); - + if (auto obj = ptr.get()) { sizeProperty = TextObjectHelper::getWidthInChars(obj.get()); } updateTextLayout(); } - + void paint(Graphics& g) override { updateTextLayout(); @@ -56,193 +56,193 @@ class CommentObject final : public ObjectBase textLayout.draw(g, textArea.toFloat()); } } - + void paintOverChildren(Graphics& g) override { auto selected = object->isSelected(); if (!locked && (object->isMouseOverOrDragging(true) || selected) && !cnv->isGraph) { g.setColour(object->findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::objectOutlineColourId)); - + g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(0.5f), Corners::objectCornerRadius, 1.0f); } } - + void mouseEnter(MouseEvent const&) override { repaint(); } - + void mouseExit(MouseEvent const&) override { repaint(); } - + void hideEditor() override { if (editor != nullptr) { std::unique_ptr outgoingEditor; std::swap(outgoingEditor, editor); - + auto newText = outgoingEditor->getText(); - + newText = TextObjectHelper::fixNewlines(newText); - + if (objectText != newText) { objectText = newText; } - + outgoingEditor.reset(); - + object->updateBounds(); // Recalculate bounds setPdBounds(object->getObjectBounds()); - + setSymbol(objectText); repaint(); } } - + bool isEditorShown() override { return editor != nullptr; } - + void showEditor() override { if (editor == nullptr) { editor.reset(TextObjectHelper::createTextEditor(object, 15)); - + editor->setBorder(border); editor->setBounds(getLocalBounds()); editor->setText(objectText, false); editor->addListener(this); editor->addKeyListener(this); editor->selectAll(); - + addAndMakeVisible(editor.get()); editor->grabKeyboardFocus(); - + editor->setColour(TextEditor::textColourId, object->findColour(PlugDataColour::commentTextColourId)); - + editor->onFocusLost = [this]() { hideEditor(); }; - + resized(); repaint(); } } - - Rectangle getPdBounds() override - { - updateTextLayout(); // make sure layout height is updated - - int x = 0, y = 0, w, h; - if (auto obj = ptr.get()) { - auto* cnvPtr = cnv->patch.getPointer().get(); - if (!cnvPtr) return {x, y, getTextObjectWidth(), std::max(textLayout.getHeight() + 6, 21)}; + + Rectangle getPdBounds() override + { + updateTextLayout(); // make sure layout height is updated - pd::Interface::getObjectBounds(cnvPtr, obj.get(), &x, &y, &w, &h); - } - - return {x, y, getTextObjectWidth(), std::max(textLayout.getHeight() + 6, 21)}; - } - - int getTextObjectWidth() - { - auto objText = editor ? editor->getText() : objectText; - - int fontWidth = 7; - int charWidth = 0; - if (auto obj = ptr.get()) { - charWidth = TextObjectHelper::getWidthInChars(obj.get()); - fontWidth = glist_fontwidth(cnv->patch.getPointer().get()); - } + int x = 0, y = 0, w, h; + if (auto obj = ptr.get()) { + auto* cnvPtr = cnv->patch.getPointer().get(); + if (!cnvPtr) return {x, y, getTextObjectWidth(), std::max(textLayout.getHeight() + 6, 20)}; - // Calculating string width is expensive, so we cache all the strings that we already calculated the width for - int idealWidth = CachedStringWidth<15>::calculateStringWidth(objText) + 12; - - // We want to adjust the width so ideal text with aligns with fontWidth - int offset = idealWidth % fontWidth; - - int textWidth; - if (objText.isEmpty()) { // If text is empty, set to minimum width - textWidth = std::max(charWidth, 6) * fontWidth; - } else if (charWidth == 0) { // If width is set to automatic, calculate based on text width - textWidth = std::clamp(idealWidth, TextObjectHelper::minWidth * fontWidth, fontWidth * 60); - } else { // If width was set manually, calculate what the width is - textWidth = std::max(charWidth, TextObjectHelper::minWidth) * fontWidth + offset; - } - - return textWidth; + pd::Interface::getObjectBounds(cnvPtr, obj.get(), &x, &y, &w, &h); + } + + return {x, y, getTextObjectWidth(), std::max(textLayout.getHeight() + 6, 20)}; + } + + int getTextObjectWidth() + { + auto objText = editor ? editor->getText() : objectText; + + int fontWidth = 7; + int charWidth = 0; + if (auto obj = ptr.get()) { + charWidth = TextObjectHelper::getWidthInChars(obj.get()); + fontWidth = glist_fontwidth(cnv->patch.getPointer().get()); + } + + // Calculating string width is expensive, so we cache all the strings that we already calculated the width for + int idealWidth = CachedStringWidth<15>::calculateStringWidth(objText) + 12; + + // We want to adjust the width so ideal text with aligns with fontWidth + int offset = idealWidth % fontWidth; + + int textWidth; + if (objText.isEmpty()) { // If text is empty, set to minimum width + textWidth = std::max(charWidth, 6) * fontWidth; + } else if (charWidth == 0) { // If width is set to automatic, calculate based on text width + textWidth = std::clamp(idealWidth, TextObjectHelper::minWidth * fontWidth, fontWidth * 60); + } else { // If width was set manually, calculate what the width is + textWidth = std::max(charWidth, TextObjectHelper::minWidth) * fontWidth + offset; } - void updateTextLayout() + return textWidth; + } + + void updateTextLayout() + { + auto objText = editor ? editor->getText() : objectText; + + int textWidth = getTextObjectWidth() - 12; // Reserve a bit of extra space for the text margin + auto currentLayoutHash = hash(objText); + auto colour = object->findColour(PlugDataColour::commentTextColourId); + if(layoutTextHash != currentLayoutHash || colour.getARGB() != lastColourARGB || textWidth != lastTextWidth) { - auto objText = editor ? editor->getText() : objectText; + auto attributedText = AttributedString(objText); + attributedText.setColour(colour); + attributedText.setJustification(Justification::centredLeft); + attributedText.setFont(Font(15)); - int textWidth = getTextObjectWidth() - 12; // Reserve a bit of extra space for the text margin - auto currentLayoutHash = hash(objText); - auto colour = object->findColour(PlugDataColour::commentTextColourId); - if(layoutTextHash != currentLayoutHash || colour.getARGB() != lastColourARGB || textWidth != lastTextWidth) - { - auto attributedText = AttributedString(objText); - attributedText.setColour(colour); - attributedText.setJustification(Justification::centredLeft); - attributedText.setFont(Font(15)); - - textLayout = TextLayout(); - textLayout.createLayout(attributedText, textWidth); - layoutTextHash = currentLayoutHash; - lastColourARGB = colour.getARGB(); - lastTextWidth = textWidth; - } + textLayout = TextLayout(); + textLayout.createLayout(attributedText, textWidth); + layoutTextHash = currentLayoutHash; + lastColourARGB = colour.getARGB(); + lastTextWidth = textWidth; } - + } + std::unique_ptr createConstrainer() override { return TextObjectHelper::createConstrainer(object); } - + void setPdBounds(Rectangle b) override { if (auto gobj = ptr.get()) { auto* patch = cnv->patch.getPointer().get(); if (!patch) return; - + pd::Interface::moveObject(patch, gobj.get(), b.getX(), b.getY()); - + if (TextObjectHelper::getWidthInChars(gobj.get())) { TextObjectHelper::setWidthInChars(gobj.get(), b.getWidth() / glist_fontwidth(patch)); } } } - + void updateSizeProperty() override { setPdBounds(object->getObjectBounds()); - + if (auto text = ptr.get()) { setParameterExcludingListener(sizeProperty, TextObjectHelper::getWidthInChars(text.get())); } } - + void valueChanged(Value& v) override { if (v.refersToSameSourceAs(sizeProperty)) { auto* constrainer = getConstrainer(); auto width = std::max(getValue(sizeProperty), constrainer->getMinimumWidth()); - + setParameterExcludingListener(sizeProperty, width); - + if (auto text = ptr.get()) { TextObjectHelper::setWidthInChars(text.get(), width); } - + object->updateBounds(); } } - + void setSymbol(String const& value) { if (auto comment = ptr.get()) { @@ -250,27 +250,27 @@ class CommentObject final : public ObjectBase auto* canvas = cnv->patch.getPointer().get(); if (!canvas) return; - + pd::Interface::renameObject(canvas, comment.cast(), cstr, value.getNumBytesAsUTF8()); } } - + bool hideInGraph() override { return false; } - + void lock(bool isLocked) override { locked = isLocked; repaint(); } - + bool canReceiveMouseEvent(int, int) override { return !locked; } - + bool keyPressed(KeyPress const& key, Component*) override { if (key == KeyPress::rightKey && editor && !editor->getHighlightedRegion().isEmpty()) { @@ -284,10 +284,10 @@ class CommentObject final : public ObjectBase if (key.getKeyCode() == KeyPress::returnKey && editor && key.getModifiers().isShiftDown()) { int caretPosition = editor->getCaretPosition(); auto text = editor->getText(); - + if (!editor->getHighlightedRegion().isEmpty()) return false; - + if (text[caretPosition - 1] == ';') { text = text.substring(0, caretPosition) + "\n" + text.substring(caretPosition); caretPosition += 1; @@ -295,14 +295,14 @@ class CommentObject final : public ObjectBase text = text.substring(0, caretPosition) + ";\n" + text.substring(caretPosition); caretPosition += 2; } - + editor->setText(text); editor->setCaretPosition(caretPosition); return true; } return false; } - + void resized() override { if (editor) { @@ -311,12 +311,12 @@ class CommentObject final : public ObjectBase updateTextLayout(); } - + void textEditorReturnKeyPressed(TextEditor&) override { cnv->grabKeyboardFocus(); } - + // For resize-while-typing behaviour void textEditorTextChanged(TextEditor&) override { diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index 7c99568b90..6687aec3f1 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -10,7 +10,7 @@ class MessageObject final : public ObjectBase Value sizeProperty = SynchronousValue(); std::unique_ptr editor; - BorderSize border = BorderSize(1, 7, 1, 2); + BorderSize border = BorderSize(1, 6, 1, 2); String objectText; @@ -59,7 +59,7 @@ class MessageObject final : public ObjectBase { auto objText = editor ? editor->getText() : objectText; if (editor && cnv->suggestor && cnv->suggestor->getText().isNotEmpty()) { - objText = cnv->suggestor->getText(); + objText = cnv->suggestor->getText() + " "; } int fontWidth = 7; @@ -70,7 +70,7 @@ class MessageObject final : public ObjectBase } // Calculating string width is expensive, so we cache all the strings that we already calculated the width for - int idealWidth = CachedStringWidth<15>::calculateStringWidth(objText) + 16; + int idealWidth = CachedStringWidth<15>::calculateStringWidth(objText) + 14; // We want to adjust the width so ideal text with aligns with fontWidth int offset = idealWidth % fontWidth; @@ -94,7 +94,7 @@ class MessageObject final : public ObjectBase objText = cnv->suggestor->getText(); } - int textWidth = getTextObjectWidth() - 16; // Remove some width for the message flag and text margin + int textWidth = getTextObjectWidth() - 14; // Remove some width for the message flag and text margin auto currentLayoutHash = hash(objText); auto colour = object->findColour(PlugDataColour::canvasTextColourId); if(layoutTextHash != currentLayoutHash || colour.getARGB() != lastColourARGB || textWidth != lastTextWidth) diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index 9b4e178a0a..cd371c55fb 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -140,7 +140,7 @@ struct TextObjectHelper { int w = minWidth; for (auto& line : lines) { - w = std::max(CachedStringWidth<15>::calculateStringWidth(line) + 14, w); + w = std::max(CachedStringWidth<15>::calculateStringWidth(line) + 11, w); } return w; @@ -159,7 +159,7 @@ struct TextObjectHelper { wchar_t lastChar; for (int i = 0; i < xOffsets.size(); i++) { - if ((xOffsets[i] + 12) >= static_cast(width) || (text.getCharPointer()[i] == '\n' && lastChar == ';')) { + if ((xOffsets[i] + 11) >= static_cast(width) || (text.getCharPointer()[i] == '\n' && lastChar == ';')) { for (int j = i + 1; j < xOffsets.size(); j++) { xOffsets.getReference(j) -= xOffsets[i]; } @@ -199,7 +199,7 @@ class TextBase : public ObjectBase protected: std::unique_ptr editor; - BorderSize border = BorderSize(1, 7, 1, 2); + BorderSize border = BorderSize(1, 6, 1, 1); TextLayout textLayout; hash32 layoutTextHash = 0; @@ -294,12 +294,12 @@ class TextBase : public ObjectBase int x = 0, y = 0, w, h; if (auto obj = ptr.get()) { auto* cnvPtr = cnv->patch.getPointer().get(); - if (!cnvPtr) return {x, y, getTextObjectWidth(), std::max(textLayout.getHeight() + 6, 21)}; + if (!cnvPtr) return {x, y, getTextObjectWidth(), std::max(textLayout.getHeight() + 5, 20)}; pd::Interface::getObjectBounds(cnvPtr, obj.get(), &x, &y, &w, &h); } - return {x, y, getTextObjectWidth(), std::max(textLayout.getHeight() + 6, 21)}; + return {x, y, getTextObjectWidth(), std::max(textLayout.getHeight() + 5, 20)}; } virtual int getTextObjectWidth() @@ -317,7 +317,7 @@ class TextBase : public ObjectBase } // Calculating string width is expensive, so we cache all the strings that we already calculated the width for - int idealWidth = CachedStringWidth<15>::calculateStringWidth(objText) + 14; + int idealWidth = CachedStringWidth<15>::calculateStringWidth(objText) + 12; // We want to adjust the width so ideal text with aligns with fontWidth int offset = idealWidth % fontWidth; @@ -344,7 +344,7 @@ class TextBase : public ObjectBase objText = cnv->suggestor->getText(); } - int textWidth = getTextObjectWidth() - 14; // Reserve a bit of extra space for the text margin + int textWidth = getTextObjectWidth() - 11; // Reserve a bit of extra space for the text margin auto currentLayoutHash = hash(objText); auto colour = object->findColour(PlugDataColour::canvasTextColourId); if(layoutTextHash != currentLayoutHash || colour.getARGB() != lastColourARGB || textWidth != lastTextWidth) From c6624bd03b5604092c2aa112dbce58d6242cd27b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 7 Feb 2024 01:02:04 +0100 Subject: [PATCH 0199/1030] Fix for draggable integer inputs --- Source/Components/DraggableNumber.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index 99df78eaa6..d1ab2f5b23 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -440,10 +440,10 @@ class DraggableNumber : public Label { auto text = String(value, precision == -1 ? 8 : precision); - if (dragMode != Integer && !text.containsChar('.')) - text << '.'; - - text = text.trimCharactersAtEnd("0"); + if (dragMode != Integer) { + if(!text.containsChar('.')) text << '.'; + text = text.trimCharactersAtEnd("0"); + } return text; } From b7d7be6f90ae0bb226a6ec753e048adf1ad782a0 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 7 Feb 2024 11:51:57 +0100 Subject: [PATCH 0200/1030] Hide inlets for [openfile] --- Source/Objects/OpenFileObject.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Objects/OpenFileObject.h b/Source/Objects/OpenFileObject.h index 673f4fd81d..eed155270c 100644 --- a/Source/Objects/OpenFileObject.h +++ b/Source/Objects/OpenFileObject.h @@ -15,6 +15,8 @@ class OpenFileObject final : public TextBase { : TextBase(ptr, object) { } + + bool hideInlets() override { return true; } void showEditor() override { From 32d1689ce9917d988d2a5a8efac2381155b0bcc4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 7 Feb 2024 12:19:37 +0100 Subject: [PATCH 0201/1030] Make sure ELSE helpfiles for abstractions can be found --- Source/Pd/Library.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Pd/Library.cpp b/Source/Pd/Library.cpp index 93700427d8..dee41f2858 100644 --- a/Source/Pd/Library.cpp +++ b/Source/Pd/Library.cpp @@ -299,7 +299,7 @@ File Library::findHelpfile(t_gobj* obj, File const& parentPatchFile) const return {}; atom_string(av, namebuf, MAXPDSTRING); - helpName = String::fromUTF8(namebuf); //.fromLastOccurrenceOf("/", false, false); + helpName = String::fromUTF8(namebuf); } else { helpDir = class_gethelpdir(pdclass); helpName = class_gethelpname(pdclass); @@ -335,7 +335,7 @@ File Library::findHelpfile(t_gobj* obj, File const& parentPatchFile) const auto findHelpPatch = [&firstName, &secondName](File const& searchDir) -> File { for (const auto& file : OSUtils::iterateDirectory(searchDir, false, true)) { auto pathName = file.getFullPathName().replace("\\", "/").trimCharactersAtEnd("/"); - if (pathName.endsWith("/" + firstName) || pathName.endsWith("/" + secondName)) { + if (pathName.endsWith("/" + firstName) || pathName.endsWith("/" + secondName) || pathName.endsWith("." + firstName) || pathName.endsWith("." + secondName)) { return file; } } From 1c49406546373bf764a0e39ae21baafb78395b1a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 7 Feb 2024 15:48:16 +0100 Subject: [PATCH 0202/1030] Fixed problem with else/keycode --- .../raw_keyboard_input/src/MacOsKeyboard.mm | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/MacOsKeyboard.mm b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/MacOsKeyboard.mm index 576de18abe..122162fe81 100644 --- a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/MacOsKeyboard.mm +++ b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/MacOsKeyboard.mm @@ -13,10 +13,7 @@ } void MacOsKeyboard::installMonitor() -{ - if (thisses.size() > 1) - return; - +{ keyDownMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown handler:^NSEvent*(NSEvent* event) { Keyboard::processKeyEvent([event keyCode], true); From 6971ba7bbd1bb419870b156301aa8f4566e25449 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 7 Feb 2024 15:49:38 +0100 Subject: [PATCH 0203/1030] Some connection paint optimisations --- Source/Connection.cpp | 28 +++++++++++++---------- Source/Connection.h | 1 + Source/Dialogs/ConnectionMessageDisplay.h | 18 +++++++++++---- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/Source/Connection.cpp b/Source/Connection.cpp index 2ed08711da..07afe8982d 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -82,9 +82,6 @@ Connection::Connection(Canvas* parent, Iolet* s, Iolet* e, t_outconnect* oc) valueChanged(presentationMode); updateOverlays(cnv->getOverlays()); - - // Prevents the connection from constantly being redrawn when scrolling or moving many objects - setBufferedToImage(true); } Connection::~Connection() @@ -425,6 +422,9 @@ void Connection::forceUpdate() void Connection::paint(Graphics& g) { + g.reduceClipRegion(clipRegion); + if(g.isClipEmpty()) return; + renderConnectionPath(g, cnv, toDrawLocalSpace, @@ -508,7 +508,7 @@ void Connection::mouseMove(MouseEvent const& e) setMouseCursor(MouseCursor::NormalCursor); } - repaint(); + //repaint(); } StringArray Connection::getMessageFormated() @@ -626,7 +626,6 @@ void Connection::mouseDrag(MouseEvent const& e) currentPlan[n].y = mouseDownPosition + delta.y; } - // setBufferedToImage(false); updatePath(); resizeToFit(); repaint(); @@ -743,9 +742,21 @@ void Connection::resizeToFit() toDrawLocalSpace = toDraw; auto offset = getLocalPoint(cnv, Point()); toDrawLocalSpace.applyTransform(AffineTransform::translation(offset)); + + // Calculate clip bounds for this connection as a list of rectangles + clipRegion = RectangleList(); + auto pathIter = PathFlatteningIterator(toDrawLocalSpace); + while(pathIter.next()) // skip first item, since only the x2/y2 coords of that one are valdid (and they will be the x1/y1 of the next item) + { + auto bounds = Rectangle(Point(pathIter.x1, pathIter.y1), Point(pathIter.x2, pathIter.y2)); + clipRegion.add(bounds.expanded(2)); + } startReconnectHandle = Rectangle(5, 5).withCentre(toDrawLocalSpace.getPointAlongPath(8.5f)); endReconnectHandle = Rectangle(5, 5).withCentre(toDrawLocalSpace.getPointAlongPath(std::max(toDrawLocalSpace.getLength() - 8.5f, 9.5f))); + + clipRegion.add(startReconnectHandle.toNearestIntEdges().expanded(4)); + clipRegion.add(endReconnectHandle.toNearestIntEdges().expanded(4)); } void Connection::componentMovedOrResized(Component& component, bool wasMoved, bool wasResized) @@ -777,13 +788,6 @@ void Connection::componentMovedOrResized(Component& component, bool wasMoved, bo } previousPStart = pstart; - // if buffered to image is true here it will take longer to redraw & buffer, - // and cause wiggling of cables, also greatly improves performance - // - // we may need to turn it off in other parts of this class, - // if getCachedComponentImage() returns true setBufferedToImage is on - // setBufferedToImage(false); - if (currentPlan.size() <= 2) { updatePath(); resizeToFit(); diff --git a/Source/Connection.h b/Source/Connection.h index 3bbcaca66b..e1e1545d59 100644 --- a/Source/Connection.h +++ b/Source/Connection.h @@ -36,6 +36,7 @@ class Connection : public Component WeakReference inobj, outobj; Path toDraw, toDrawLocalSpace; + RectangleList clipRegion; String lastId; std::atomic messageActivity; diff --git a/Source/Dialogs/ConnectionMessageDisplay.h b/Source/Dialogs/ConnectionMessageDisplay.h index 9942b90efb..fba82997dd 100644 --- a/Source/Dialogs/ConnectionMessageDisplay.h +++ b/Source/Dialogs/ConnectionMessageDisplay.h @@ -113,13 +113,16 @@ class ConnectionMessageDisplay auto firstOrLast = (i == 0 || i == textString.size() - 1); stringItem = textString[i]; stringItem += firstOrLast ? "" : ","; + // first item uses system font - stringWidth = textFont.getStringWidth(stringItem); + // use cached width calculation for performance + stringWidth = CachedFontStringWidth::get()->calculateSingleLineWidth(textFont, stringItem); if ((totalStringWidth + stringWidth) > halfEditorWidth) { auto elideText = String("(" + String(textString.size() - i) + String(")...")); auto elideFont = Font(Fonts::getSemiBoldFont()); - auto elideWidth = elideFont.getStringWidth(elideText); + + auto elideWidth = CachedFontStringWidth::get()->calculateSingleLineWidth(elideFont, elideText); messageItemsWithFormat.add(TextStringWithMetrics(elideText, FontStyle::Semibold, elideWidth)); totalStringWidth += elideWidth + 4; break; @@ -141,7 +144,13 @@ class ConnectionMessageDisplay if (totalStringWidth > getWidth() || isHoverEntered) { updateBoundsFromProposed(Rectangle().withSize(totalStringWidth, 36)); } - repaint(); + + // Check if changed + if(lastTextString != textString) + { + lastTextString = textString; + repaint(); + } } void updateBoundsFromProposed(Rectangle proposedPosition) @@ -325,7 +334,7 @@ class ConnectionMessageDisplay // Calculate text length auto numbersFont = Fonts::getTabularNumbersFont().withHeight(11.f); auto text = String(lastSamples[ch][rand() % 512], 3); - auto textWidth = numbersFont.getStringWidth(text); + auto textWidth = CachedFontStringWidth::get()->calculateSingleLineWidth(numbersFont, text); auto textBounds = channelBounds.expanded(5).removeFromBottom(18).removeFromRight(textWidth + 8); // Draw text background @@ -368,6 +377,7 @@ class ConnectionMessageDisplay Component::SafePointer activeConnection; int mouseDelay = 500; Point mousePosition; + StringArray lastTextString; enum TimerID { RepaintTimer, MouseHoverDelay, MouseHoverExitDelay }; From 686727295f4e9e435d3dd433404bfe6ead40aaba Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 7 Feb 2024 17:33:13 +0100 Subject: [PATCH 0204/1030] Don't allow empty library name --- Source/Dialogs/PathsAndLibrariesPanel.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Source/Dialogs/PathsAndLibrariesPanel.h b/Source/Dialogs/PathsAndLibrariesPanel.h index 952c9cf96d..f0386ec034 100644 --- a/Source/Dialogs/PathsAndLibrariesPanel.h +++ b/Source/Dialogs/PathsAndLibrariesPanel.h @@ -622,10 +622,12 @@ class LibraryLoadPanel : public Component auto librariesTree = SettingsFile::getInstance()->getLibrariesTree(); librariesTree.removeAllChildren(nullptr); - for (auto const& i : librariesToLoad) { - auto newLibrary = ValueTree("Library"); - newLibrary.setProperty("Name", i, nullptr); - librariesTree.appendChild(newLibrary, nullptr); + for (auto const& name : librariesToLoad) { + if(name.isNotEmpty()) { + auto newLibrary = ValueTree("Library"); + newLibrary.setProperty("Name", name, nullptr); + librariesTree.appendChild(newLibrary, nullptr); + } } listBox.updateContent(); From 0c21af0b3e84eea2fd2bd1e069a6d37b3186e33d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 7 Feb 2024 20:36:13 +0100 Subject: [PATCH 0205/1030] Load patches from DAW state, but let Pd think we are loading it from disk --- Libraries/pure-data | 2 +- Source/PluginProcessor.cpp | 28 +++++++++++++++++----------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Libraries/pure-data b/Libraries/pure-data index 1bf6e97fb7..7539930598 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit 1bf6e97fb7e4a0f7c157b08274c2a8c3dea6bb7b +Subproject commit 75399305989891cd4cf3a0470c111b5b862a6b73 diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 123a3902a2..c02af3ee69 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1101,17 +1101,16 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) std::unique_ptr xmlState(getXmlFromBinary(xmlData, xmlSize)); auto openPatch = [this](String const& content, File const& location, bool pluginMode = false, int splitIndex = 0) { - if (location.getFullPathName().isNotEmpty() && location.existsAsFile()) { - auto patch = loadPatch(URL(location), getEditors()[0], splitIndex); - if (patch) { - patch->setTitle(location.getFileName()); - patch->openInPluginMode = pluginMode; - } - } else { - if (location.getParentDirectory().exists()) { - auto parentPath = location.getParentDirectory().getFullPathName(); - libpd_add_to_search_path(parentPath.toRawUTF8()); - } + + // CHANGED IN v0.8.4: + // We now prefer loading the patch content over the patch file, if possible + // This generally makes it work more like the users expects, but before we couldn't get it to load abstractions (this is now fixed) + if(content.isNotEmpty()) + { + // Force pd to use this path for the next opened patch + // This makes sure the patch can find abstractions/resources, even though it's loading patch from state + glob_forcefilename(generateSymbol(location.getFileName().toRawUTF8()), generateSymbol(location.getParentDirectory().getFullPathName().toRawUTF8())); + auto patch = loadPatch(content, getEditors()[0], splitIndex); if (patch && ((location.exists() && location.getParentDirectory() == File::getSpecialLocation(File::tempDirectory)) || !location.exists())) { patch->setTitle("Untitled Patcher"); @@ -1124,6 +1123,13 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) patch->splitViewIndex = splitIndex; } } + else if (location.getFullPathName().isNotEmpty() && location.existsAsFile()) { + auto patch = loadPatch(URL(location), getEditors()[0], splitIndex); + if (patch) { + patch->setTitle(location.getFileName()); + patch->openInPluginMode = pluginMode; + } + } }; if (xmlState) { From f4b23fc7e59de4ebd6740d74fd68ca713565d2c6 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 7 Feb 2024 20:48:23 +0100 Subject: [PATCH 0206/1030] Fixed helpfile finding issue --- Source/Pd/Library.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Pd/Library.cpp b/Source/Pd/Library.cpp index dee41f2858..cd1a2c01ae 100644 --- a/Source/Pd/Library.cpp +++ b/Source/Pd/Library.cpp @@ -335,7 +335,11 @@ File Library::findHelpfile(t_gobj* obj, File const& parentPatchFile) const auto findHelpPatch = [&firstName, &secondName](File const& searchDir) -> File { for (const auto& file : OSUtils::iterateDirectory(searchDir, false, true)) { auto pathName = file.getFullPathName().replace("\\", "/").trimCharactersAtEnd("/"); - if (pathName.endsWith("/" + firstName) || pathName.endsWith("/" + secondName) || pathName.endsWith("." + firstName) || pathName.endsWith("." + secondName)) { + // Hack to make it find else/cyclone helpfiles... + pathName = pathName.replace("/else", "/9.else"); + pathName = pathName.replace("/cyclone", "/10.else"); + + if (pathName.endsWith("/" + firstName) || pathName.endsWith("/" + secondName)) { return file; } } From cccddf2b55583c0916a8ba719784c3ccaaa72efa Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 7 Feb 2024 21:13:41 +0100 Subject: [PATCH 0207/1030] Minor drawing optimisations --- Source/Connection.cpp | 2 +- Source/Object.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Connection.cpp b/Source/Connection.cpp index 07afe8982d..a88f1fbdd6 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -422,7 +422,7 @@ void Connection::forceUpdate() void Connection::paint(Graphics& g) { - g.reduceClipRegion(clipRegion); + g.getInternalContext().clipToRectangleList(clipRegion); if(g.isClipEmpty()) return; renderConnectionPath(g, diff --git a/Source/Object.h b/Source/Object.h index b6e6f5cdd4..ec42dc3694 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -99,7 +99,11 @@ class Object : public Component OwnedArray iolets; ResizableBorderComponent::Zone resizeZone; +#if JUCE_IOS static inline constexpr int margin = 8; +#else + static inline constexpr int margin = 6; +#endif static inline constexpr int doubleMargin = margin * 2; static inline constexpr int height = 37; From 8bb3ba1b9be7cee4fc69775dc1fab61df185dd2c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 8 Feb 2024 00:34:47 +0100 Subject: [PATCH 0208/1030] Fixed small object alignment issues --- Source/Components/SuggestionComponent.h | 2 +- Source/Object.cpp | 6 +++--- Source/Object.h | 2 +- Source/Objects/CommentObject.h | 2 +- Source/Objects/MessageObject.h | 2 +- Source/Objects/TextObject.h | 2 +- Source/Pd/Setup.cpp | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index 26e2fd8726..3f084d2a3d 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -131,7 +131,7 @@ class AutoCompleteComponent auto completionBounds = getLocalBounds().toFloat().withTrimmedLeft(editorTextWidth + 7.5f); auto colour = findColour(PlugDataColour::canvasTextColourId).withAlpha(0.65f); - Fonts::drawText(g, suggestion, completionBounds, colour); + Fonts::drawText(g, suggestion, completionBounds.translated(-1, -1), colour); } }; // Suggestions component that shows up when objects are edited diff --git a/Source/Object.cpp b/Source/Object.cpp index a6aff7aa3d..9140034693 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -519,10 +519,10 @@ void Object::paint(Graphics& g) if (newObjectEditor) { g.setColour(findColour(PlugDataColour::textObjectBackgroundColourId)); - g.fillRoundedRectangle(getLocalBounds().reduced(Object::margin + 1).toFloat(), Corners::objectCornerRadius); + g.fillRoundedRectangle(getLocalBounds().toFloat().reduced(Object::margin + 0.5f), Corners::objectCornerRadius); g.setColour(findColour(PlugDataColour::objectSelectedOutlineColourId)); - g.drawRoundedRectangle(getLocalBounds().reduced(Object::margin + 1).toFloat(), Corners::objectCornerRadius, 1.0f); + g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(Object::margin + 0.5f), Corners::objectCornerRadius, 1.0f); } g.setColour(findColour(PlugDataColour::objectSelectedOutlineColourId)); @@ -1268,7 +1268,7 @@ void Object::openNewObjectEditor() editor->setAlwaysOnTop(true); editor->setMultiLine(false); editor->setReturnKeyStartsNewLine(false); - editor->setBorder(BorderSize(1, 7, 1, 2)); + editor->setBorder(BorderSize(1, 6, 2, 2)); editor->setIndents(0, 0); editor->setJustification(Justification::centredLeft); diff --git a/Source/Object.h b/Source/Object.h index ec42dc3694..97ae1756d6 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -105,7 +105,7 @@ class Object : public Component static inline constexpr int margin = 6; #endif static inline constexpr int doubleMargin = margin * 2; - static inline constexpr int height = 37; + static inline constexpr int height = 32; Rectangle originalBounds; diff --git a/Source/Objects/CommentObject.h b/Source/Objects/CommentObject.h index 46b94693ed..a60ee0d12a 100644 --- a/Source/Objects/CommentObject.h +++ b/Source/Objects/CommentObject.h @@ -18,7 +18,7 @@ class CommentObject final : public ObjectBase int32 lastColourARGB = 0; std::unique_ptr editor; - BorderSize border = BorderSize(1, 7, 1, 2); + BorderSize border = BorderSize(1, 5, 1, 2); String objectText; public: diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index 6687aec3f1..122c1dc988 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -10,7 +10,7 @@ class MessageObject final : public ObjectBase Value sizeProperty = SynchronousValue(); std::unique_ptr editor; - BorderSize border = BorderSize(1, 6, 1, 2); + BorderSize border = BorderSize(1, 5, 1, 2); String objectText; diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index cd371c55fb..9e1bd80438 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -447,7 +447,7 @@ class TextBase : public ObjectBase if (editor == nullptr) { editor.reset(TextObjectHelper::createTextEditor(object, 15)); - editor->setBorder(border); + editor->setBorder(border.addedTo(BorderSize(0, 0, 1, 0))); editor->setBounds(getLocalBounds()); editor->setText(objectText, false); editor->addListener(this); diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index 0214b17e38..8e060ca25d 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -1711,6 +1711,7 @@ void Setup::initialiseELSE() pipe2_setup(); circuit_tilde_setup(); + setup_autofade0x2emc_tilde(); setup_autofade20x2emc_tilde(); setup_mtx0x2emc_tilde(); pan_tilde_setup(); @@ -1718,7 +1719,6 @@ void Setup::initialiseELSE() setup_xgate20x2emc_tilde(); setup_xselect20x2emc_tilde(); findfile_setup(); - setup_autofade0x2emc_tilde(); wt2d_tilde_setup(); pm_tilde_setup(); From 038884a77c81be4daf950e3db0cafc0a329b9def Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 8 Feb 2024 16:56:37 +0100 Subject: [PATCH 0209/1030] Fix inlet/outlet position after object size changes --- Source/Object.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index 9140034693..8fbc58c5c5 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -574,7 +574,7 @@ void Object::resized() int maxIoletHeight = (getHeight() / 2.0f) - 3; ioletSize = std::max(std::min({ ioletSize, maxIoletWidth, maxIoletHeight }), 10); - int borderWidth = jmap(ioletSize, 10, 13, 9, 14); + int borderWidth = jmap(ioletSize, 10, 13, 7, 12); auto inletBounds = getLocalBounds(); if (auto spaceToRemove = jlimit(0, borderWidth, inletBounds.getWidth() - (ioletHitBox * numInputs) - borderWidth)) { From 4582a91ea8858f2ccb92f7e8300c6380ffdbe4f5 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 9 Feb 2024 04:33:47 +0100 Subject: [PATCH 0210/1030] Fixed potential crash on shutdown --- Source/Objects/ObjectImplementations.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Objects/ObjectImplementations.h b/Source/Objects/ObjectImplementations.h index 595635351d..c9d47448d3 100644 --- a/Source/Objects/ObjectImplementations.h +++ b/Source/Objects/ObjectImplementations.h @@ -10,15 +10,17 @@ class SubpatchImpl : public ImplementationBase , public pd::MessageListener { public: + WeakReference pdWeakRef; + SubpatchImpl(t_gobj* ptr, t_canvas* parent, PluginProcessor* pd) - : ImplementationBase(ptr, parent, pd) + : ImplementationBase(ptr, parent, pd), pdWeakRef(pd) { pd->registerMessageListener(this->ptr.getRawUnchecked(), this); } ~SubpatchImpl() override { - pd->unregisterMessageListener(ptr.getRawUnchecked(), this); + if(pdWeakRef) pdWeakRef->unregisterMessageListener(ptr.getRawUnchecked(), this); closeOpenedSubpatchers(); } From c7ad88f361620dab050719c464d7272e804fac5c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 9 Feb 2024 04:34:50 +0100 Subject: [PATCH 0211/1030] Simplified canvas grid dots drawing --- Source/Canvas.cpp | 59 +++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 2d62e64d60..499cc91ddc 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -300,40 +300,33 @@ void Canvas::paint(Graphics& g) auto scale = ::getValue(zoomScale); if (!getValue(locked)) { - - auto startX = (canvasOrigin.x % objectGrid.gridSize); - startX += ((clipBounds.getX() / objectGrid.gridSize) * objectGrid.gridSize); - - auto startY = (canvasOrigin.y % objectGrid.gridSize); - startY += ((clipBounds.getY() / objectGrid.gridSize) * objectGrid.gridSize); - - g.setColour(findColour(PlugDataColour::canvasDotsColourId)); - - for (int x = startX; x < clipBounds.getRight(); x += objectGrid.gridSize) { - // calculate the x here, once per iteration, as it won't change for y - auto const gridSpacing = objectGrid.gridSize * 4; - auto const xGridSpacing = (x - canvasOrigin.x) % gridSpacing == 0; - - for (int y = startY; y < clipBounds.getBottom(); y += objectGrid.gridSize) { - - // Don't draw over origin or border line - if (showBorder || showOrigin) { - if ((x == canvasOrigin.x && y >= canvasOrigin.y && (showOrigin || (y <= patchHeightCanvas))) || (y == canvasOrigin.y && x >= canvasOrigin.x && (showOrigin || (x <= patchWidthCanvas)))) - continue; - } - auto dotWidth = 1.0f; - if (scale < 1.0f) { - if (((y - canvasOrigin.y) % gridSpacing == 0) || xGridSpacing) { - dotWidth = 1.0f / jmap(scale, 0.3f, 1.0f, 0.4f, 1.0f); - } else { - // TIM: draw the dot's differently for some grid sizes, or not at all? - if (objectGrid.gridSize == 5) - continue; - } - } - auto halfDotWidth = dotWidth * 0.5f; - g.fillRect(static_cast(x) - halfDotWidth, static_cast(y) - halfDotWidth, dotWidth, dotWidth); + auto const& gridSize = objectGrid.gridSize; + if(scale < 1.0f) + { + auto const largeGridSize = gridSize * 4; + Image dotsImage(Image::PixelFormat::ARGB, largeGridSize, largeGridSize, true); + Graphics g2(dotsImage); + g2.setColour(findColour(PlugDataColour::canvasDotsColourId)); + auto dotWidth = 1.0f / jmap(scale, 0.3f, 1.0f, 0.4f, 1.0f); + + for(int i = 0; i < 4; i++) + { + auto x = static_cast(i * gridSize); + auto y = static_cast(i * gridSize); + g2.fillRect(0.5f, y + 0.5f, dotWidth, dotWidth); + g2.fillRect(x + 0.5f, 0.5f, dotWidth, dotWidth); } + + g.setTiledImageFill(dotsImage, canvasOrigin.x - 1, canvasOrigin.y - 1, 1.0f); + g.fillAll(); + } + else { + Image dotImage(Image::PixelFormat::ARGB, gridSize, gridSize, true); + Graphics g2(dotImage); + g2.setColour(findColour(PlugDataColour::canvasDotsColourId)); + dotImage.setPixelAt(0, 0, findColour(PlugDataColour::canvasDotsColourId)); + g.setTiledImageFill(dotImage, canvasOrigin.x - 1, canvasOrigin.y - 1, 1.0f); + g.fillAll(); } } From 1ea0ad67e4f5a321c663f130f085fcb0a5fa0ccd Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 14 Feb 2024 16:34:15 +0100 Subject: [PATCH 0212/1030] Updated ELSE md files --- Resources/Documentation/ELSE/abs.pd~.md | 42 ++++++++++ Resources/Documentation/ELSE/autofade.mc~.md | 41 ++++++++++ Resources/Documentation/ELSE/autofade2.mc~.md | 45 ++++++++++ Resources/Documentation/ELSE/autofade2~.md | 9 +- Resources/Documentation/ELSE/autofade~.md | 10 +-- Resources/Documentation/ELSE/bl.wavetable~.md | 6 -- Resources/Documentation/ELSE/conv~.md | 6 +- .../ELSE/{canvas.file.md => findfile.md} | 4 +- Resources/Documentation/ELSE/midi2note.md | 45 ++++++++++ Resources/Documentation/ELSE/mtx.mc~.md | 44 ++++++++++ Resources/Documentation/ELSE/mtx~.md | 9 +- Resources/Documentation/ELSE/noisegate~.md | 5 +- Resources/Documentation/ELSE/note2dur.md | 31 ------- .../ELSE/{note2pitch.md => note2midi.md} | 4 +- Resources/Documentation/ELSE/notedur2ratio.md | 31 +++++++ Resources/Documentation/ELSE/pan.mc~.md | 55 +++++++++++++ Resources/Documentation/ELSE/pan8~.md | 65 --------------- Resources/Documentation/ELSE/pan~.md | 54 ++++++++++++ Resources/Documentation/ELSE/pitch2note.md | 4 +- Resources/Documentation/ELSE/sendmidi.md | 19 +++++ Resources/Documentation/ELSE/tabwriter~.md | 9 +- Resources/Documentation/ELSE/wavetable~.md | 6 -- Resources/Documentation/ELSE/wt2d~.md | 82 +++++++++++++++++++ Resources/Documentation/ELSE/xgate2.mc~.md | 46 +++++++++++ Resources/Documentation/ELSE/xgate2~.md | 13 ++- Resources/Documentation/ELSE/xselect2.mc~.md | 50 +++++++++++ Resources/Documentation/ELSE/xselect2~.md | 16 +++- Resources/Documentation/Gem/spline_path.md | 2 +- 28 files changed, 609 insertions(+), 144 deletions(-) create mode 100644 Resources/Documentation/ELSE/abs.pd~.md create mode 100644 Resources/Documentation/ELSE/autofade.mc~.md create mode 100644 Resources/Documentation/ELSE/autofade2.mc~.md rename Resources/Documentation/ELSE/{canvas.file.md => findfile.md} (55%) create mode 100644 Resources/Documentation/ELSE/midi2note.md create mode 100644 Resources/Documentation/ELSE/mtx.mc~.md delete mode 100644 Resources/Documentation/ELSE/note2dur.md rename Resources/Documentation/ELSE/{note2pitch.md => note2midi.md} (51%) create mode 100644 Resources/Documentation/ELSE/notedur2ratio.md create mode 100644 Resources/Documentation/ELSE/pan.mc~.md delete mode 100644 Resources/Documentation/ELSE/pan8~.md create mode 100644 Resources/Documentation/ELSE/pan~.md create mode 100644 Resources/Documentation/ELSE/sendmidi.md create mode 100644 Resources/Documentation/ELSE/wt2d~.md create mode 100644 Resources/Documentation/ELSE/xgate2.mc~.md create mode 100644 Resources/Documentation/ELSE/xselect2.mc~.md diff --git a/Resources/Documentation/ELSE/abs.pd~.md b/Resources/Documentation/ELSE/abs.pd~.md new file mode 100644 index 0000000000..9789efdf20 --- /dev/null +++ b/Resources/Documentation/ELSE/abs.pd~.md @@ -0,0 +1,42 @@ +--- +title: abd.pd~ + +description: simple wrapper for pd~ subprocess + +categories: + - object + +pdcategory: ELSE, Audio I/O, Data Management + +arguments: + - type: symbol + description: '.pd' file to load + default: none + +methods: + - type: start + description: start a subprocess, no symbol restarts last one + - type: stop + description: stop the subprocess + - type: show + description: open pd window + +inlets: + 1st: + - type: signal + description: the left input to pd~ subprocess + - type: anything + description: messages to the subprocess + 2nd: + - type: signal + description: the right input to pd~ subprocess + +outlets: + 1st: + - type: + description: sig + +draft: false +--- + +[abs.pd~] loads a pd file into a subprocess via [pd~] and makes things more convenient like making the subprocess able to receive MIDI data from MIDI devices connected to the parent process (thanks to [sendmidi]). Also, you can open the subprocess patch by clicking on the object. diff --git a/Resources/Documentation/ELSE/autofade.mc~.md b/Resources/Documentation/ELSE/autofade.mc~.md new file mode 100644 index 0000000000..98470720f9 --- /dev/null +++ b/Resources/Documentation/ELSE/autofade.mc~.md @@ -0,0 +1,41 @@ +--- +title: autofade.mc~ + +description: automatic fade in/out + +categories: + - object + +pdcategory: ELSE, Mixing And Routing + +arguments: + - type: symbol + description: fade types + default: quartic + - type: float + description: fade time in ms + default: 10 + +methods: + - type: fade + description: fade time in ms + - type: anything + description: fade types + +inlets: + 1st: + - type: signals + description: input signals to be faded in/out + 2nd: + - type: float/signal + description: gate on/off + +outlets: + 1st: + - type: signals + description: autofaded signals + +draft: false +--- + +[autofade.mc~] is an automatic fade in/out for multichanne inputs. It responds to a gate control and uses internal lookup tables just like [fader~]. A gate on happens when the last input value was zero or less and the incoming value isn't. A gate off happens when the last value was greater than zero value and the incoming value isn't! The maximum gain depends on the gate on level. diff --git a/Resources/Documentation/ELSE/autofade2.mc~.md b/Resources/Documentation/ELSE/autofade2.mc~.md new file mode 100644 index 0000000000..13f6755d22 --- /dev/null +++ b/Resources/Documentation/ELSE/autofade2.mc~.md @@ -0,0 +1,45 @@ +--- +title: autofade2.mc~ + +description: automatic fade with indepent in/out times + +categories: + - object + +pdcategory: ELSE, Mixing And Routing + +arguments: + - type: symbol + description: fade types + default: quartic + - type: float + description: fade time in ms + default: 10 +- type: float + description: fade time out ms + default: 10 +methods: + - type: fadein + description: fade in time in ms + - type: fadeout + description: fade out time in ms + - type: anything + description: fade types + +inlets: + 1st: + - type: signals + description: input signals to be faded in/out + 2nd: + - type: float/signal + description: gate on/off + +outlets: + 1st: + - type: signals + description: autofaded signals + +draft: false +--- + +[autofade2.mc~] is an automatic fade in/out for multichanne inputs. It responds to a gate control and uses internal lookup tables just like [fader~]. A gate on happens when the last input value was zero or less and the incoming value isn't. A gate off happens when the last value was greater than zero value and the incoming value isn't! The maximum gain depends on the gate on level. diff --git a/Resources/Documentation/ELSE/autofade2~.md b/Resources/Documentation/ELSE/autofade2~.md index 4997e2a6e6..190e526595 100644 --- a/Resources/Documentation/ELSE/autofade2~.md +++ b/Resources/Documentation/ELSE/autofade2~.md @@ -24,15 +24,16 @@ arguments: inlets: 1st: - - type: float/signal - description: gate; "fade in" if != 0, "fade out" otherwise - - type: anything - description: fade types - type: signal description: input signal to be faded in/out + - type: anything + description: fade types nth: - type: signal description: input signal to be faded in/out + 2nd: + - type: float/signal + description: gate; "fade in" if != 0, "fade out" otherwise outlets: nth: diff --git a/Resources/Documentation/ELSE/autofade~.md b/Resources/Documentation/ELSE/autofade~.md index e115a7a1ca..c87bf07953 100644 --- a/Resources/Documentation/ELSE/autofade~.md +++ b/Resources/Documentation/ELSE/autofade~.md @@ -21,16 +21,16 @@ arguments: inlets: 1st: - - type: float/signal - description: gate; "fade in" if != 0, "fade out" otherwise - - type: anything - description: fade types - type: signal description: input signal to be faded in/out + - type: anything + description: fade types nth: - type: signal description: input signal to be faded in/out - + 2nd: + - type: float/signal + description: gate; "fade in" if != 0, "fade out" otherwise outlets: nth: - type: signal diff --git a/Resources/Documentation/ELSE/bl.wavetable~.md b/Resources/Documentation/ELSE/bl.wavetable~.md index 10f0e83afd..f5e72a349c 100644 --- a/Resources/Documentation/ELSE/bl.wavetable~.md +++ b/Resources/Documentation/ELSE/bl.wavetable~.md @@ -23,12 +23,6 @@ flags: - name: -none/-lin/-cos/-lagrange description: set interpolation mode default: spline - - name: -size - description: sets table size in points - default: whole table - - name: -offset - description: sets table offset - default: 0 - name: -midi description: sets frequency input in MIDI pitch default: Hz diff --git a/Resources/Documentation/ELSE/conv~.md b/Resources/Documentation/ELSE/conv~.md index 850287cd06..e807166786 100644 --- a/Resources/Documentation/ELSE/conv~.md +++ b/Resources/Documentation/ELSE/conv~.md @@ -9,12 +9,12 @@ categories: pdcategory: ELSE, Effects arguments: -- description: optional - partition size - type: float - default: 1024, minimum 64 - description: impulse response table name type: symbol default: none +- description: optional - partition size + type: float + default: 1024, minimum 64 inlets: 1st: diff --git a/Resources/Documentation/ELSE/canvas.file.md b/Resources/Documentation/ELSE/findfile.md similarity index 55% rename from Resources/Documentation/ELSE/canvas.file.md rename to Resources/Documentation/ELSE/findfile.md index 179af1e112..6f7b540aae 100644 --- a/Resources/Documentation/ELSE/canvas.file.md +++ b/Resources/Documentation/ELSE/findfile.md @@ -1,5 +1,5 @@ --- -title: canvas.file +title: findfile description: search for file @@ -29,4 +29,4 @@ outlets: draft: false --- -[canvas.file] tries to locate the file in using the patch's directory or Pd's search-paths. If it finds it, it returns the absolute path in the left outlet. If it fails, the input name is output via the right outlet. The depth level sets the directory according to the parent patch and so on. +[findfile] tries to locate the file in using the patch's directory or Pd's search-paths. If it finds it, it returns the absolute path in the left outlet. If it fails, the input name is output via the right outlet. The depth level sets the directory according to the parent patch and so on. diff --git a/Resources/Documentation/ELSE/midi2note.md b/Resources/Documentation/ELSE/midi2note.md new file mode 100644 index 0000000000..ff01f893c5 --- /dev/null +++ b/Resources/Documentation/ELSE/midi2note.md @@ -0,0 +1,45 @@ +--- +title: midi2note + +description: Convert MIDI pitch to note name + +categories: +- object + +pdcategory: ELSE, Converters, MIDI + +arguments: + +inlets: + 1st: + - type: float/list + description: MIDI pitch value(s) + 2nd: + - type: anything/list + description: user defined scales + +outlets: + 1st: + - type: symbol/list + description: note name(s) + +flags: + - name: -unicode + description: sets to unicode mode + - name: -list + description: sets to list output mode + +methods: + - type: chromatic + description: sets to chromatic mode + default: chromatic + - type: sharp + description: sets to sharp mode + - type: flat + description: sets to flat mode + +draft: false +--- + +[midi2note] converts a MIDI pitch value to note names (such as Eb3). The names end with an octave number, in a way that middle C (MIDI pitch = 60) represents C4 represents. Float inputs are rounded to integers. + diff --git a/Resources/Documentation/ELSE/mtx.mc~.md b/Resources/Documentation/ELSE/mtx.mc~.md new file mode 100644 index 0000000000..9c7755a8da --- /dev/null +++ b/Resources/Documentation/ELSE/mtx.mc~.md @@ -0,0 +1,44 @@ +--- +title: mtx.mc~ + +description: Multuchannel signal routing matrix + +categories: + - object + +pdcategory: ELSE, Mixing And Routing + +methods: + - type: ramp + description: set ramp time in ms + - type: print + description: prints state off all cells in pd window + - type: clear + description: clears all connections +arguments: + - type: float + description: number of inputs (1 to 512) + default: 1 + - type: float + description: number of outputs (1 to 512) + default: 1 + - type: float + description: ramp time in ms + default: 10 + +inlets: + 1st: + - type: signals + description: multichannel to route/mix + - type: list + description: 3 floats to set: channel input, output and gain + +outlets: + 1st: + - type: signal + description: routed signals from inlets + +draft: false +--- + +[mtx.mc~] routes a channel signal from a multichannel input to one or more channels of a multichannel output. diff --git a/Resources/Documentation/ELSE/mtx~.md b/Resources/Documentation/ELSE/mtx~.md index 0cb072abd9..387672e7a6 100644 --- a/Resources/Documentation/ELSE/mtx~.md +++ b/Resources/Documentation/ELSE/mtx~.md @@ -22,7 +22,7 @@ arguments: inlets: 1st: - type: list - description: inlet, outlet, non-0 - on, 0 - off + description: inlet, outlet, gain nth: - type: signal description: signals to route/mix @@ -31,13 +31,10 @@ outlets: nth: - type: signal description: routed signals - 2nd: - - type: list - description: all connections list dump message methods: - - type: fade - description: sets fade time in ms + - type: ramp + description: sets ramp time in ms - type: dump description: outputs state of all cells - type: clear diff --git a/Resources/Documentation/ELSE/noisegate~.md b/Resources/Documentation/ELSE/noisegate~.md index 121ca84b6a..2cc49efe46 100644 --- a/Resources/Documentation/ELSE/noisegate~.md +++ b/Resources/Documentation/ELSE/noisegate~.md @@ -12,7 +12,10 @@ arguments: - description: threshold in dBFS type: float default: -100 - - description: attack/release time in ms + - description: attack time in ms + type: float + default: 10 + - description: release time in ms type: float default: 10 diff --git a/Resources/Documentation/ELSE/note2dur.md b/Resources/Documentation/ELSE/note2dur.md deleted file mode 100644 index 9d1eadd846..0000000000 --- a/Resources/Documentation/ELSE/note2dur.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: note2dur - -description: convert note duration to fraction or float - -categories: -- object - -pdcategory: ELSE, Converters, Triggers and Clocks - -arguments: - -inlets: - 1st: - - type: symbol - description: in note duration syntax to convert - -outlets: - 1st: - - type: float/symbol - description: converted duration to fraction or float - -flags: -- name: -f - description: sets output to float - default: fraction - -draft: false ---- - -[note2dur] converts from a note duration syntax to a fractional notation or a float. The syntax is: '1n' for whole note, '2n' for half note, '4n' for quarter note, '8n' for eighth note (and so on down to 128n). Dotted notes are possible and you can add tuplets to the symbol in the [x:y] format or you can add the following letters to specify most common tuplets: 'd' for duplet, 't' for triplet, 'q' for quintuplet, 's' for septuplet and 'n' for nonuplet. Tied notes are also possible with "_". Details in the examples below. diff --git a/Resources/Documentation/ELSE/note2pitch.md b/Resources/Documentation/ELSE/note2midi.md similarity index 51% rename from Resources/Documentation/ELSE/note2pitch.md rename to Resources/Documentation/ELSE/note2midi.md index bcd3d1771e..536b928eff 100644 --- a/Resources/Documentation/ELSE/note2pitch.md +++ b/Resources/Documentation/ELSE/note2midi.md @@ -1,5 +1,5 @@ --- -title: note2pitch +title: note2midi description: convert note name to MIDI pitch @@ -27,4 +27,4 @@ flags: draft: false --- -[note2pitch] converts note names (such as 'C4') to MIDI pitch values. All enharmonic names are included (even unusual ones like Cb and double sharps/flats). It takes symbols, lists or anythings that end with an octave number, in a way that C4 represents middle C (MIDI pitch = 60). Range is from C0 to B8. +[note2midi] converts note names (such as 'C4') to MIDI pitch values. All enharmonic names are included (even unusual ones like Cb and double sharps/flats). It takes symbols, lists or anythings that end with an octave number, in a way that C4 represents middle C (MIDI pitch = 60). Range is from C0 to B8. diff --git a/Resources/Documentation/ELSE/notedur2ratio.md b/Resources/Documentation/ELSE/notedur2ratio.md new file mode 100644 index 0000000000..0512b0c65b --- /dev/null +++ b/Resources/Documentation/ELSE/notedur2ratio.md @@ -0,0 +1,31 @@ +--- +title: notedur2ratio + +description: convert note duration to fraction or float + +categories: +- object + +pdcategory: ELSE, Converters, Triggers and Clocks + +arguments: + +inlets: + 1st: + - type: symbol + description: in note duration syntax to convert + +outlets: + 1st: + - type: float/symbol + description: converted duration to fraction or float + +flags: +- name: -f + description: sets output to float + default: fraction + +draft: false +--- + +[notedur2ratio] converts from a note duration syntax to a fractional notation or a float. The syntax is: '1n' for whole note, '2n' for half note, '4n' for quarter note, '8n' for eighth note (and so on down to 128n). Dotted notes are possible and you can add tuplets to the symbol in the [x:y] format or you can add the following letters to specify most common tuplets: 'd' for duplet, 't' for triplet, 'q' for quintuplet, 's' for septuplet and 'n' for nonuplet. Tied notes are also possible with "_". Details in the examples below. diff --git a/Resources/Documentation/ELSE/pan.mc~.md b/Resources/Documentation/ELSE/pan.mc~.md new file mode 100644 index 0000000000..7777a32e70 --- /dev/null +++ b/Resources/Documentation/ELSE/pan.mc~.md @@ -0,0 +1,55 @@ +--- +title: pan.mc~ + +description: circular panning over given number of channels + +categories: +- object + +pdcategory: ELSE, Mixing and Routing + +arguments: +- type: float + description: number of output channels (min 2, max 4096) + default: 2 +- type: float + description: spread + default: 1 +- type: float + description: offset in degrees + default: 0 + +methods: + - type: offset + description: set offset angle in degrees + - type: radians + description: set offset angle in radians + - type: n + description: set number of output channels +inlets: + 1st: + - type: signal + description: signal input + 2nd: + - type: float/signal + description: gain parameter + 3rd: + - type: float/signal + description: azimuth (normalized from 0-1 or radians) + 4th: + - type: float/signal + description: spread parameter (from -1 to 1) + +outlets: + 1st: + - type: signal + description: equal power panned output + +flags: + - name: -radians + description: change 3rd inlet input from degrees to radians + +draft: false +--- + +[pan.mc~] pans an input signal to 'n' specified channels in a multichannel output with equal power crossfading between adjacent channels according to a spread parameter. The speakers are supposedly disposed in a circular setting with equidistant angles. The output selection can then be considered an azimuth angle input whose range is normalized from 0 to 1, where 1 goes back to 0! diff --git a/Resources/Documentation/ELSE/pan8~.md b/Resources/Documentation/ELSE/pan8~.md deleted file mode 100644 index 3283b97058..0000000000 --- a/Resources/Documentation/ELSE/pan8~.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: pan8~ - -description: octaphonic panning - -categories: -- object - -pdcategory: ELSE, Mixing and Routing - -arguments: - -inlets: - 1st: - - type: signal - description: signal input - 2nd: - - type: float/signal - description: X horizontal value: from -1 (Left) to 1 (Right) - 3rd: - - type: float/signal - description: Y vertical value: from -1 (Back) to 1 (Front) - 4th: - - type: float - description: spread parameter (from -1 to 1) - -outlets: - 1st: - - type: signal - description: channel output 1 - 2nd: - - type: signal - description: channel output 2 - 3rd: - - type: signal - description: channel output 3 - 4th: - - type: signal - description: channel output 4 - 5th: - - type: signal - description: channel output 5 - 6th: - - type: signal - description: channel output 6 - 7th: - - type: signal - description: channel output 7 - 8th: - - type: signal - description: channel output 8 - -flags: - - name: -spread - description: sets spread parameter - default: 0 - - name: -mode - description: sets mode <0> or <1> - default: 0 - -draft: false ---- - -[pan8~] is an 8-channel equal power (sin/cos) panner. It has two modes of operation and a spread parameter. - diff --git a/Resources/Documentation/ELSE/pan~.md b/Resources/Documentation/ELSE/pan~.md new file mode 100644 index 0000000000..15261f7f97 --- /dev/null +++ b/Resources/Documentation/ELSE/pan~.md @@ -0,0 +1,54 @@ +--- +title: pan~ + +description: circular panning over given number of channels + +categories: +- object + +pdcategory: ELSE, Mixing and Routing + +arguments: +- type: float + description: number of output channels (min 2, max 4096) + default: 2 +- type: float + description: spread + default: 1 +- type: float + description: offset in degrees + default: 0 + +methods: + - type: offset + description: set offset angle in degrees + - type: radians + description: set offset angle in radians + +inlets: + 1st: + - type: signal + description: signal input + 2nd: + - type: float/signal + description: gain parameter + 3rd: + - type: float/signal + description: azimuth (normalized from 0-1 or radians) + 4th: + - type: float/signal + description: spread parameter (from -1 to 1) + +outlets: + nth: + - type: signal + description: equal power panned output $nth + +flags: + - name: -radians + description: change 3rd inlet input from degrees to radians + +draft: false +--- + +[pan~] pans an input signal to 'n' specified outlets with equal power crossfading between adjacent channels according to a spread parameter. The speakers are supposedly disposed in a circular setting with equidistant angles. The output selection can then be considered an azimuth angle input whose range is normalized from 0 to 1, where 1 goes back to 0! diff --git a/Resources/Documentation/ELSE/pitch2note.md b/Resources/Documentation/ELSE/pitch2note.md index cfc6c713c0..ff01f893c5 100644 --- a/Resources/Documentation/ELSE/pitch2note.md +++ b/Resources/Documentation/ELSE/pitch2note.md @@ -1,5 +1,5 @@ --- -title: pitch2note +title: midi2note description: Convert MIDI pitch to note name @@ -41,5 +41,5 @@ methods: draft: false --- -[pitch2note] converts a MIDI pitch value to note names (such as Eb3). The names end with an octave number, in a way that middle C (MIDI pitch = 60) represents C4 represents. Float inputs are rounded to integers. +[midi2note] converts a MIDI pitch value to note names (such as Eb3). The names end with an octave number, in a way that middle C (MIDI pitch = 60) represents C4 represents. Float inputs are rounded to integers. diff --git a/Resources/Documentation/ELSE/sendmidi.md b/Resources/Documentation/ELSE/sendmidi.md new file mode 100644 index 0000000000..22aec63994 --- /dev/null +++ b/Resources/Documentation/ELSE/sendmidi.md @@ -0,0 +1,19 @@ +--- +title: sendmidi + +description: helper to send MIDI send messages to [pd~] + +categories: + - object + +pdcategory: ELSE, MIDI + +outlets: + 1st: + - type: anything + description: MIDI messages to send to the [pd~] object + +draft: false +--- + +[sendmidi] is a helper abstraction to send MIDI messages to the [pd~] object, which cannot listen to the connected MIDI devices in Pd. diff --git a/Resources/Documentation/ELSE/tabwriter~.md b/Resources/Documentation/ELSE/tabwriter~.md index 101102beeb..2d9e99befb 100644 --- a/Resources/Documentation/ELSE/tabwriter~.md +++ b/Resources/Documentation/ELSE/tabwriter~.md @@ -31,15 +31,18 @@ flags: inlets: 1st: - - type: float/signals - description: non-0 starts recording, 0 stops it + - type: signal + description: signal to record into an array channel 0 - type: rec description: (re)starts recording - type: stop description: stops recording + nth: + - type: signal + description: signal to record into an array channel $nth 2nd: - type: signal - description: signal to record into an array channel 'n' + description: non-0 starts recording at that index, 0 stops it outlets: 1st: diff --git a/Resources/Documentation/ELSE/wavetable~.md b/Resources/Documentation/ELSE/wavetable~.md index 18ccaf7a72..d882710b6d 100644 --- a/Resources/Documentation/ELSE/wavetable~.md +++ b/Resources/Documentation/ELSE/wavetable~.md @@ -23,12 +23,6 @@ flags: - name: -none/-lin/-cos/-lagrange description: set interpolation mode default: spline - - name: -size - description: sets table size in points - default: whole table - - name: -offset - description: sets table offset - default: 0 - name: -midi description: sets frequency input in MIDI pitch default: Hz diff --git a/Resources/Documentation/ELSE/wt2d~.md b/Resources/Documentation/ELSE/wt2d~.md new file mode 100644 index 0000000000..8c7fb932d3 --- /dev/null +++ b/Resources/Documentation/ELSE/wt2d~.md @@ -0,0 +1,82 @@ +--- +title: wt2d~ + +description: two-dimensional wavetable oscillator + +categories: + - object + +pdcategory: ELSE, Signal Generators + +arguments: + - type: symbol + description: array name + default: none + - type: float + description: sets frequency in Hz + default: 0 + - type: float + description: sets phase offset + default: 0 + +methods: + - type: set + description: sets an entire array to be used as a waveform + - type: n + description: sets number of x and y slices to scan through + - type: midi + description: non zero sets to frequency input in MIDI pitch + - type: soft + description: non zero sets to soft sync mode + - type: none + description: sets to no interpolation mode + - type: lin + description: sets to linear interpolation mode + - type: cos + description: sets to cosine interpolation mode + - type: lagrange + description: sets to lagrange interpolation mode + - type: spline + description: sets to spline interpolation mode + +flags: + - name: -none + description: disable interpolation + - name: -lin + description: set interpolation mode to linear + - name: -cos + description: set interpolation mode to cosine + - name: -lagrange + description: set interpolation mode to lagrange + - name: -n + description: sets number of x and y slices + - name: -midi + description: sets frequency input in MIDI pitch + - name: -soft + description: sets to soft sync mode + +inlets: + 1st: + - type: float/signal + description: sets frequency in hertz + 2nd: + - type: float/signal + description: phase sync (resets internal phase) + 3rd: + - type: float/signal + description: phase offset (modulation input) + 4th: + - type: float/signal + description: x dimension crossfading input + 5th: + - type: float/signal + description: y dimension crossfading input +outlets: + 1st: + - type: signal + description: a periodically repeating waveform + +draft: false +--- + +[wt2d~] is an interpolating wavetable oscillator like [wt~], but besides a horizontal dimension, it can also scan through a vertical dimension of a sliced wavetable. As other oscilltors in ELSE, it has input for phase modulation and sync. diff --git a/Resources/Documentation/ELSE/xgate2.mc~.md b/Resources/Documentation/ELSE/xgate2.mc~.md new file mode 100644 index 0000000000..63386fa658 --- /dev/null +++ b/Resources/Documentation/ELSE/xgate2.mc~.md @@ -0,0 +1,46 @@ +--- +title: xgate2.mc~ + +description: route a multichannel signal with crossfade + +categories: + - object + +pdcategory: ELSE, Mixing and Routing + +arguments: +- type: float + description: number of output channels (min 2, max 500) + default: 2 +- type: float + description: spread + default: 1 + +flags: +- type: index + description: sets to indexed mode + +inlets: + 1st: + - type: signal + description: channel selection + 2nd: + - type: signal + description: output position + 3rd: + - type: signal + description: spread parameter + +outlets: + 1st: + - type: signals + description: routed outputs with crossfade + +methods: + - type: index + description: <1> — indexed mode, <0> — non-indexed (default) + +draft: false +--- + +[xgate2.mc~] routes an input signal to 'n' multichannel outlet with equal power crossfade between two adjacent channels. diff --git a/Resources/Documentation/ELSE/xgate2~.md b/Resources/Documentation/ELSE/xgate2~.md index 3bf9cd6c7e..3925e73bfc 100644 --- a/Resources/Documentation/ELSE/xgate2~.md +++ b/Resources/Documentation/ELSE/xgate2~.md @@ -13,17 +13,24 @@ arguments: description: number of output channels (min 2, max 500) default: 2 - type: float - description: <1> sets to indexed mode <0> sets to non-indexed + description: spread default: 1 +flags: +- type: index + description: sets to indexed mode + inlets: 1st: - type: signal description: channel selection 2nd: - type: signal - description: input channel to be routed - + description: output position + 3rd: + - type: signal + description: spread parameter + outlets: 1st: - type: signal diff --git a/Resources/Documentation/ELSE/xselect2.mc~.md b/Resources/Documentation/ELSE/xselect2.mc~.md new file mode 100644 index 0000000000..b854bd0b2c --- /dev/null +++ b/Resources/Documentation/ELSE/xselect2.mc~.md @@ -0,0 +1,50 @@ +--- +title: xselect2.mc~ + +description: select channel with crossfade + +categories: + - object + +pdcategory: ELSE, Mixing and Routing + +arguments: +- type: float + description: number of channels (min 2, max 500) + default: 2 +- type: float + description: spread + default: 1 + +flags: +- type: index + description: sets to indexed mode +- type: circular + description: sets to circular mode + +inlets: + 1st: + - type: signals + description: selected channel with crossfade + 2nd: + - type: signal + description: input position + 3rd: + - type: signal + description: spread parameter + +outlets: + 1st: + - type: signal + description: crossfaded channels + +methods: + - type: index + description: 1 sets to indexed mode, 0 to non-indexed mode + - type: circular + description: 1 sets to circular mode + +draft: false +--- + +[xselect2.mc~] selects between multiple inputs with equal power crossfade between two adjacent channels. diff --git a/Resources/Documentation/ELSE/xselect2~.md b/Resources/Documentation/ELSE/xselect2~.md index e3c46a0c5e..a2ac67c5aa 100644 --- a/Resources/Documentation/ELSE/xselect2~.md +++ b/Resources/Documentation/ELSE/xselect2~.md @@ -13,16 +13,22 @@ arguments: description: number of channels (min 2, max 500) default: 2 - type: float - description: <1> — indexed mode, <0> — non-indexed + description: spread default: 1 - + +flags: +- type: index + description: sets to indexed mode +- type: circular + description: sets to circular mode + inlets: 1st: - type: signal description: selected channel with crossfade nth: - type: signal - description: secondary inputs are the channels to select from + description: inlets alternate between input position and spread parameter for each channel outlets: 1st: @@ -31,7 +37,9 @@ outlets: methods: - type: index - description: <1> - indexed mode, <0> - non-indexed (default) + description: 1 sets to indexed mode, 0 to non-indexed mode + - type: circular + description: 1 sets to circular mode draft: false --- diff --git a/Resources/Documentation/Gem/spline_path.md b/Resources/Documentation/Gem/spline_path.md index 34ff0ad771..e843eb374a 100644 --- a/Resources/Documentation/Gem/spline_path.md +++ b/Resources/Documentation/Gem/spline_path.md @@ -3,7 +3,7 @@ title: spline_path description: reads out a table categories: - object -pdcategory: Gem, Graphics, Data Structures +pdcategory: Gem, Graphics arguments: - type: float description: reading point (0.0 to 1.0) From 33939a140f66981b5b87334d1e72b7ca1ab78ab6 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 15 Feb 2024 18:11:29 +0100 Subject: [PATCH 0213/1030] Improved support for pd scalars/templates --- Source/Objects/ScalarObject.h | 336 +++++++++++++++++++--------------- 1 file changed, 190 insertions(+), 146 deletions(-) diff --git a/Source/Objects/ScalarObject.h b/Source/Objects/ScalarObject.h index 53b03bcab4..c2cbd90fe8 100644 --- a/Source/Objects/ScalarObject.h +++ b/Source/Objects/ScalarObject.h @@ -6,20 +6,9 @@ #include "Utility/GlobalMouseListener.h" -extern "C" { - -int scalar_doclick(t_word* data, t_template* t, t_scalar* sc, - t_array* ap, struct _glist* owner, - t_float xloc, t_float yloc, int xpix, int ypix, - int shift, int alt, int dbl, int doit); -} - -#define CLOSED 1 /* polygon */ -#define BEZ 2 /* bezier shape */ -#define NOMOUSERUN 4 /* disable mouse interaction when in run mode */ -#define NOMOUSEEDIT 8 /* same in edit mode */ -#define NOVERTICES 16 /* disable only vertex grabbing in run mode */ -#define A_ARRAY 55 /* LATER decide whether to enshrine this in m_pd.h */ +#define CLOSED 1 // polygon +#define BEZ 2 // bezier shape +#define A_ARRAY 55 #define DRAWNUMBER_BUFSIZE 1024 @@ -39,6 +28,7 @@ class DrawableTemplate : public pd::MessageListener t_template* templ; t_template* parentTempl; pd::WeakReference scalar; + bool mouseWasDown = false; DrawableTemplate(t_scalar* object, t_word* scalarData, t_template* scalarTemplate, t_template* parentTemplate, Canvas* cnv, t_float x, t_float y) : pd(cnv->pd) @@ -171,12 +161,14 @@ class DrawableCurve final : public DrawableTemplate auto glist = cnv->patch.getPointer(); auto pos = e.getPosition() - cnv->canvasOrigin; gobj_click(gobj.get(), glist.get(), pos.x, pos.y, e.mods.isShiftDown(), e.mods.isAltDown(), e.getNumberOfClicks() > 1, 1); - cnv->updateDrawables(); glist->gl_editor->e_xwas = pos.x; glist->gl_editor->e_ywas = pos.y; + mouseWasDown = true; } }; globalMouseListener.globalMouseUp = [this, cnv](MouseEvent const& e) { + mouseWasDown = false; + auto localPos = e.getEventRelativeTo(this).getMouseDownPosition(); if (!getLocalBounds().contains(localPos) || !getValue(canvas->locked) || !canvas->isShowing()) return; @@ -185,27 +177,22 @@ class DrawableCurve final : public DrawableTemplate auto glist = cnv->patch.getPointer(); auto pos = e.getPosition() - cnv->canvasOrigin; gobj_click(gobj.get(), glist.get(), pos.x, pos.y, e.mods.isShiftDown(), e.mods.isAltDown(), 0, 0); - cnv->updateDrawables(); glist->gl_editor->e_xwas = pos.x; glist->gl_editor->e_ywas = pos.y; + mouseWasDown = false; } }; globalMouseListener.globalMouseDrag = [this, cnv](MouseEvent const& e) { - auto localPos = e.getEventRelativeTo(this).getMouseDownPosition(); - if (!getLocalBounds().contains(localPos) || !getValue(canvas->locked) || !canvas->isShowing()) - return; - + if(!mouseWasDown || !getValue(canvas->locked) || !canvas->isShowing()) return; if (auto gobj = scalar.get()) { auto glist = cnv->patch.getPointer(); auto pos = e.getPosition() - cnv->canvasOrigin; - gobj_click(gobj.get(), glist.get(), pos.x, pos.y, e.mods.isShiftDown(), e.mods.isAltDown(), e.getNumberOfClicks() > 1, 1); - + auto* canvas = glist_getcanvas(glist.get()); if (canvas->gl_editor->e_motionfn) { canvas->gl_editor->e_motionfn(&canvas->gl_editor->e_grab->g_pd, pos.x - glist->gl_editor->e_xwas, pos.y - glist->gl_editor->e_ywas, 0); } - cnv->updateDrawables(); glist->gl_editor->e_xwas = pos.x; glist->gl_editor->e_ywas = pos.y; } @@ -290,41 +277,38 @@ class DrawableCurve final : public DrawableTemplate Path toDraw; - toDraw.startNewSubPath(pix[0], pix[1]); - if (flags & BEZ) { for (int i = 0; i < n; i++) { - float x0 = pix[2 * i]; - float y0 = pix[2 * i + 1]; - - float x1, y1; - if (i == n - 1) { - if (closed) { - x1 = pix[0]; - y1 = pix[1]; - } else { - x1 = x0; - y1 = y0; - } - } else { - x1 = pix[2 * (i + 1)]; - y1 = pix[2 * (i + 1) + 1]; + int wrapped1 = i % (n - closed); + int wrapped2 = (i + 1) % (n - closed); + float x0 = pix[2 * wrapped1]; + float y0 = pix[2 * wrapped1 + 1]; + float x1 = pix[2 * wrapped2]; + float y1 = pix[2 * wrapped2 + 1]; + + if(!closed && i == n - 1) + { + x1 = x0; + y1 = y0; } - toDraw.quadraticTo(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2); - - if (i == n - 1) { - toDraw.quadraticTo((x0 + x1) / 2, (y0 + y1) / 2, x1, y1); + + if(i == 0) { + toDraw.startNewSubPath(closed ? (x0 + x1) / 2 : x0, closed ? (y0 + y1) / 2 : y0); + } + else { + toDraw.quadraticTo(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2); } } } else { + toDraw.startNewSubPath(pix[0], pix[1]); for (int i = 1; i < n; i++) { toDraw.lineTo(pix[2 * i], pix[2 * i + 1]); } + if (closed) { + toDraw.lineTo(pix[0], pix[1]); + } } - if (closed) { - toDraw.lineTo(pix[0], pix[1]); - } auto drawBounds = toDraw.getBounds(); @@ -476,6 +460,7 @@ class DrawablePlot final : public DrawableTemplate Point lastMouseDragPosition = { 0, 0 }; t_fake_curve* object; GlobalMouseListener globalMouseListener; + OwnedArray subplots; public: DrawablePlot(t_scalar* s, t_gobj* obj, t_word* data, t_template* templ, Canvas* cnv, int x, int y, t_template* parent = nullptr) @@ -483,7 +468,7 @@ class DrawablePlot final : public DrawableTemplate , object(reinterpret_cast(obj)) , globalMouseListener(cnv) { - /* TODO: finish this and enable it! + globalMouseListener.globalMouseDown = [this, cnv](const MouseEvent& e){ auto localPos = e.getEventRelativeTo(this).getMouseDownPosition(); if (!getLocalBounds().contains(localPos) || !getValue(canvas->locked) || !canvas->isShowing()) @@ -493,12 +478,14 @@ class DrawablePlot final : public DrawableTemplate auto glist = cnv->patch.getPointer(); auto pos = e.getPosition() - cnv->canvasOrigin; gobj_click(gobj.get(), glist.get(), pos.x, pos.y, e.mods.isShiftDown(), e.mods.isAltDown(), e.getNumberOfClicks() > 1, 1); - cnv->updateDrawables(); glist->gl_editor->e_xwas = pos.x; glist->gl_editor->e_ywas = pos.y; + mouseWasDown = true; } }; globalMouseListener.globalMouseUp = [this, cnv](const MouseEvent& e){ + mouseWasDown = false; + auto localPos = e.getEventRelativeTo(this).getMouseDownPosition(); if (!getLocalBounds().contains(localPos) || !getValue(canvas->locked) || !canvas->isShowing()) return; @@ -507,44 +494,38 @@ class DrawablePlot final : public DrawableTemplate auto glist = cnv->patch.getPointer(); auto pos = e.getPosition() - cnv->canvasOrigin; gobj_click(gobj.get(), glist.get(), pos.x, pos.y, e.mods.isShiftDown(), e.mods.isAltDown(), 0, 0); - cnv->updateDrawables(); glist->gl_editor->e_xwas = pos.x; glist->gl_editor->e_ywas = pos.y; } }; globalMouseListener.globalMouseDrag = [this, cnv](const MouseEvent& e){ - auto localPos = e.getEventRelativeTo(this).getMouseDownPosition(); - if (!getLocalBounds().contains(localPos) || !getValue(canvas->locked) || !canvas->isShowing()) - return; - - if(auto gobj = scalar.get()) { + if(!mouseWasDown || !getValue(canvas->locked) || !canvas->isShowing()) return; + if (auto gobj = scalar.get()) { auto glist = cnv->patch.getPointer(); auto pos = e.getPosition() - cnv->canvasOrigin; - gobj_click(gobj.get(), glist.get(), pos.x, pos.y, e.mods.isShiftDown(), e.mods.isAltDown(), e.getNumberOfClicks() > 1, 1); - + auto* canvas = glist_getcanvas(glist.get()); - if(canvas->gl_editor->e_motionfn) { + if (canvas->gl_editor->e_motionfn) { canvas->gl_editor->e_motionfn(&canvas->gl_editor->e_grab->g_pd, pos.x - glist->gl_editor->e_xwas, pos.y - glist->gl_editor->e_ywas, 0); } - cnv->updateDrawables(); glist->gl_editor->e_xwas = pos.x; glist->gl_editor->e_ywas = pos.y; } }; - globalMouseListener.globalMouseMove = [this, cnv](const MouseEvent& e){ + globalMouseListener.globalMouseMove = [this, cnv](MouseEvent const& e) { auto localPos = e.getEventRelativeTo(this).getMouseDownPosition(); if (!getLocalBounds().contains(localPos) || !getValue(canvas->locked) || !canvas->isShowing()) return; - if(auto gobj = scalar.get()) { + if (auto gobj = scalar.get()) { auto glist = cnv->patch.getPointer(); auto pos = e.getPosition() - cnv->canvasOrigin; gobj_click(gobj.get(), glist.get(), pos.x, pos.y, e.mods.isShiftDown(), e.mods.isAltDown(), 0, 0); glist->gl_editor->e_xwas = pos.x; glist->gl_editor->e_ywas = pos.y; } - }; */ + }; } static int readOwnerTemplate(t_fake_plot* x, @@ -589,74 +570,6 @@ class DrawablePlot final : public DrawableTemplate return (0); } - Array getSubPlots() - { - auto* s = scalar.getRaw(); - - if (!s || !s->sc_template) - return {}; - - auto* glist = canvas->patch.getPointer().get(); - if (!glist) - return {}; - - auto* x = reinterpret_cast(object); - int elemsize, yonset, wonset, xonset, i; - t_canvas* elemtemplatecanvas; - t_template* elemtemplate; - t_symbol* elemtemplatesym; - t_float linewidth, xloc, xinc, yloc, style, yval, - vis, scalarvis, edit; - double xsum; - t_array* array; - t_fake_fielddesc *xfielddesc, *yfielddesc, *wfielddesc; - - if (readOwnerTemplate(x, data, templ, - &elemtemplatesym, &array, &linewidth, &xloc, &xinc, &yloc, &style, - &vis, &scalarvis, &edit, &xfielddesc, &yfielddesc, &wfielddesc) - || array_getfields(elemtemplatesym, &elemtemplatecanvas, - &elemtemplate, &elemsize, (t_fielddesc*)xfielddesc, (t_fielddesc*)yfielddesc, (t_fielddesc*)wfielddesc, - &xonset, &yonset, &wonset)) - return {}; - - int nelem = array->a_n; - auto* elem = (char*)array->a_vec; - - Array drawables; - - for (xsum = xloc, i = 0; i < nelem; i++) { - t_float usexloc, useyloc; - - if (xonset >= 0) - usexloc = baseX + xloc + *(t_float*)((elem + elemsize * i) + xonset); - else - usexloc = baseX + xsum, xsum += xinc; - if (yonset >= 0) - yval = *(t_float*)((elem + elemsize * i) + yonset); - else - yval = 0; - useyloc = baseY + yloc + fielddesc_cvttocoord((t_fielddesc*)yfielddesc, yval); - auto* subData = (t_word*)(elem + elemsize * i); - - for (auto* y = elemtemplatecanvas->gl_list; y; y = y->g_next) { - t_parentwidgetbehavior const* wb = pd_getparentwidget(&y->g_pd); - if (!wb) - continue; - - auto name = String::fromUTF8(y->g_pd->c_name->s_name); - if (name == "drawtext" || name == "drawnumber" || name == "drawsymbol") { - drawables.add(new DrawableSymbol(s, y, subData, elemtemplate, canvas, static_cast(usexloc), static_cast(useyloc), templ)); - } else if (name == "drawpolygon" || name == "drawcurve" || name == "filledpolygon" || name == "filledcurve") { - drawables.add(new DrawableCurve(s, y, subData, elemtemplate, canvas, static_cast(usexloc), static_cast(useyloc), templ)); - } else if (name == "plot") { - drawables.add(new DrawablePlot(s, y, subData, elemtemplate, canvas, static_cast(usexloc), static_cast(useyloc), templ)); - } - } - } - - return drawables; - } - void update() override { auto* s = scalar.getRaw(); @@ -668,6 +581,7 @@ class DrawablePlot final : public DrawableTemplate if (!glist) return; + auto* x = reinterpret_cast(object); int elemsize, yonset, wonset, xonset, i; t_canvas* elemtemplatecanvas; @@ -680,7 +594,12 @@ class DrawablePlot final : public DrawableTemplate int nelem; char* elem; t_fake_fielddesc *xfielddesc, *yfielddesc, *wfielddesc; - + + if (!fielddesc_getfloat(&x->x_vis, templ, data, 0)) { + setPath(Path()); + return; + } + /* even if the array is "invisible", if its visibility is set by an instance variable you have to explicitly erase it, because the flag could earlier have been on when we were getting @@ -763,11 +682,11 @@ class DrawablePlot final : public DrawableTemplate int lastpixel = -1, ndrawn = 0; t_float yval = 0, wval = 0, xpix; int ixpix = 0; - /* draw the trace */ + // draw the trace if (wonset >= 0) { - /* found "w" field which controls linewidth. The trace is - a filled polygon with 2n points. */ + // found "w" field which controls linewidth. The trace is + // a filled polygon with 2n points. setFill(outline); for (i = 0, xsum = xloc; i < nelem; i++) { @@ -820,8 +739,8 @@ class DrawablePlot final : public DrawableTemplate goto ouch; } - /* TK will complain if there aren't at least 3 points. - There should be at least two already. */ + // TK will complain if there aren't at least 3 points. + // There should be at least two already. if (ndrawn < 4) { coordinates[ndrawn * 2 + 0] = ixpix + 10; coordinates[ndrawn * 2 + 1] = yToPixels(baseY + yloc + fielddesc_cvttocoord((t_fielddesc*)yfielddesc, yval) - fielddesc_cvttocoord((t_fielddesc*)wfielddesc, wval)); @@ -869,9 +788,9 @@ class DrawablePlot final : public DrawableTemplate toDraw.lineTo(coordinates[0] + canvas->canvasOrigin.x, coordinates[1] + canvas->canvasOrigin.y); } } else if (linewidth > 0) { - /* no "w" field. If the linewidth is positive, draw a - segmented line with the requested width; otherwise don't - draw the trace at all. */ + // no "w" field. If the linewidth is positive, draw a + // segmented line with the requested width; otherwise don't + // draw the trace at all. for (i = 0, xsum = xloc; i < nelem; i++) { t_float usexloc; if (xonset >= 0) @@ -896,9 +815,8 @@ class DrawablePlot final : public DrawableTemplate break; } - /* TK will complain if there aren't at least 2 points... - Don't know about JUCE though... - */ + // TK will complain if there aren't at least 2 points... + // Don't know about JUCE though... if (ndrawn == 1) { coordinates[2] = ixpix + 10; coordinates[3] = yToPixels(baseY + yloc + fielddesc_cvttocoord((t_fielddesc*)yfielddesc, yval)); @@ -906,15 +824,145 @@ class DrawablePlot final : public DrawableTemplate } if (ndrawn) { + /* toDraw.startNewSubPath(coordinates[0] + canvas->canvasOrigin.x, coordinates[1] + canvas->canvasOrigin.y); for (int i = 1; i < ndrawn; i++) { toDraw.lineTo(coordinates[2 * i] + canvas->canvasOrigin.x, coordinates[2 * i + 1] + canvas->canvasOrigin.y); + } */ + if (style == PLOTSTYLE_BEZ) { + float startX = coordinates[0] + canvas->canvasOrigin.x; + float startY = coordinates[1] + canvas->canvasOrigin.y; + + toDraw.startNewSubPath(startX, startY); + + for (int i = 0; i < ndrawn; i++) { + float x0 = coordinates[2 * i] + canvas->canvasOrigin.x; + float y0 = coordinates[2 * i + 1] + canvas->canvasOrigin.y; + + float x1, y1; + if (i == ndrawn - 1) { + x1 = x0; + y1 = y0; + } else { + x1 = coordinates[2 * (i + 1)] + canvas->canvasOrigin.x; + y1 = coordinates[2 * (i + 1) + 1] + canvas->canvasOrigin.y; + } + + toDraw.quadraticTo(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2); + + if (i == ndrawn - 1) { + toDraw.quadraticTo((x0 + x1) / 2, (y0 + y1) / 2, x1, y1); + } + } + + toDraw = toDraw.createPathWithRoundedCorners(6.0f); + } else { + toDraw.startNewSubPath(coordinates[0] + canvas->canvasOrigin.x, coordinates[1] + canvas->canvasOrigin.y); + for (int i = 1; i < ndrawn; i++) { + toDraw.lineTo(coordinates[2 * i] + canvas->canvasOrigin.x, coordinates[2 * i + 1] + canvas->canvasOrigin.y); + } } } } } setPath(toDraw); + updateSubplots(); + } + + + void updateSubplots() + { + auto* s = scalar.getRaw(); + + if (!s || !s->sc_template) + return; + + auto* glist = canvas->patch.getPointer().get(); + if (!glist) + return; + + auto* x = reinterpret_cast(object); + int elemsize, yonset, wonset, xonset, i; + t_canvas* elemtemplatecanvas; + t_template* elemtemplate; + t_symbol* elemtemplatesym; + t_float linewidth, xloc, xinc, yloc, style, yval, + vis, scalarvis, edit; + double xsum; + t_array* array; + t_fake_fielddesc *xfielddesc, *yfielddesc, *wfielddesc; + + if (readOwnerTemplate(x, data, templ, + &elemtemplatesym, &array, &linewidth, &xloc, &xinc, &yloc, &style, + &vis, &scalarvis, &edit, &xfielddesc, &yfielddesc, &wfielddesc) + || array_getfields(elemtemplatesym, &elemtemplatecanvas, + &elemtemplate, &elemsize, (t_fielddesc*)xfielddesc, (t_fielddesc*)yfielddesc, (t_fielddesc*)wfielddesc, + &xonset, &yonset, &wonset)) + return; + + int nelem = array->a_n; + auto* elem = (char*)array->a_vec; + + Array oldDrawables; + oldDrawables.addArray(subplots); + + auto addOrUpdateSubplot = [this, s, elemtemplate, &oldDrawables](t_gobj* y, t_word* subdata, int xloc, int yloc) mutable { + for(auto* existingPlot : subplots) + { + if(auto* plot = dynamic_cast(existingPlot)) + { + if(plot->data == subdata) + { + plot->baseX = xloc; + plot->baseY = yloc; + plot->update(); + oldDrawables.removeFirstMatchingValue(existingPlot); + return; + } + } + } + + auto name = String::fromUTF8(y->g_pd->c_name->s_name); + if (name == "drawtext" || name == "drawnumber" || name == "drawsymbol") { + subplots.add(new DrawableSymbol(s, y, subdata, elemtemplate, canvas, static_cast(xloc), static_cast(yloc), templ)); + canvas->addAndMakeVisible(subplots.getLast()); + } else if (name == "drawpolygon" || name == "drawcurve" || name == "filledpolygon" || name == "filledcurve") { + subplots.add(new DrawableCurve(s, y, subdata, elemtemplate, canvas, static_cast(xloc), static_cast(yloc), templ)); + canvas->addAndMakeVisible(subplots.getLast()); + } else if (name == "plot") { + subplots.add(new DrawablePlot(s, y, subdata, elemtemplate, canvas, static_cast(xloc), static_cast(yloc), templ)); + canvas->addAndMakeVisible(subplots.getLast()); + } + }; + + for (xsum = xloc, i = 0; i < nelem; i++) { + t_float usexloc, useyloc; + + if (xonset >= 0) + usexloc = baseX + xloc + *(t_float*)((elem + elemsize * i) + xonset); + else + usexloc = baseX + xsum, xsum += xinc; + if (yonset >= 0) + yval = *(t_float*)((elem + elemsize * i) + yonset); + else + yval = 0; + useyloc = baseY + yloc + fielddesc_cvttocoord((t_fielddesc*)yfielddesc, yval); + auto* subdata = (t_word*)(elem + elemsize * i); + + for (auto* y = elemtemplatecanvas->gl_list; y; y = y->g_next) { + t_parentwidgetbehavior const* wb = pd_getparentwidget(&y->g_pd); + if (!wb) + continue; + + addOrUpdateSubplot(y, subdata, usexloc, useyloc); + } + } + + for(auto* drawable : oldDrawables) + { + subplots.removeObject(drawable); + } } }; @@ -948,10 +996,6 @@ struct ScalarObject final : public ObjectBase { } else if (name == "plot") { auto* plot = new DrawablePlot(scalar.get(), y, data, templ, cnv, static_cast(baseX), static_cast(baseY)); cnv->addAndMakeVisible(templates.add(plot)); - - for (auto* subplot : plot->getSubPlots()) { - cnv->addAndMakeVisible(templates.add(subplot)); - } } } From dfe9dff5aeff6feb43ccff0d79cefa06200ecd5b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 15 Feb 2024 18:52:21 +0100 Subject: [PATCH 0214/1030] Small cleanup --- Source/Utility/StringUtils.h | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Source/Utility/StringUtils.h b/Source/Utility/StringUtils.h index b92a99f152..ae2e6919f2 100644 --- a/Source/Utility/StringUtils.h +++ b/Source/Utility/StringUtils.h @@ -91,13 +91,10 @@ struct CachedFontStringWidth : public DeletedAtShutdown { auto stringHash = hash(singleLine); - bool fontFound = false; for(auto [cachedFont, cache] : stringWidthCache) { if(cachedFont == font) { - fontFound = true; - auto cacheHit = cache.find(stringHash); if(cacheHit != cache.end()) return cacheHit->second; @@ -108,14 +105,9 @@ struct CachedFontStringWidth : public DeletedAtShutdown } } - if(!fontFound) - { - auto stringWidth = font.getStringWidth(singleLine); - stringWidthCache.push_back({font, {{stringHash, stringWidth}}}); - return stringWidth; - } - - return font.getStringWidthFloat(singleLine); + auto stringWidth = font.getStringWidth(singleLine); + stringWidthCache.push_back({font, {{stringHash, stringWidth}}}); + return stringWidth; } int calculateStringWidth(Font& font, const String& string) From 6fe6119c24712e598d3bf2e9d424743289b31f97 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 15 Feb 2024 18:52:42 +0100 Subject: [PATCH 0215/1030] No longer centre iolets if object width is small --- Source/Object.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index 8fbc58c5c5..32ca363007 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -598,8 +598,7 @@ void Object::resized() auto const bounds = isInlet ? inletBounds : outletBounds; if (total == 1 && position == 0) { - int xPosition = getWidth() < 50 ? getLocalBounds().getCentreX() - ioletSize / 2.0f : bounds.getX(); - iolet->setBounds(xPosition, yPosition, ioletSize, ioletSize); + iolet->setBounds(bounds.getX(), yPosition, ioletSize, ioletSize); } else if (total > 1) { float const ratio = (bounds.getWidth() - ioletSize) / static_cast(total - 1); iolet->setBounds(bounds.getX() + ratio * position, yPosition, ioletSize, ioletSize); From 34918eabab7345b4e1dc75094789020aa07e5e6d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 16 Feb 2024 00:49:42 +0100 Subject: [PATCH 0216/1030] Fixed array/graph ticks issue --- Source/Objects/GraphOnParent.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Objects/GraphOnParent.h b/Source/Objects/GraphOnParent.h index f7d9d6f5a5..1f2bca887c 100644 --- a/Source/Objects/GraphOnParent.h +++ b/Source/Objects/GraphOnParent.h @@ -240,7 +240,7 @@ class GraphOnParent final : public ObjectBase { } f = x->gl_xtick.k_point - x->gl_xtick.k_inc; - for (int i = 1; f > 0.99f * x->gl_x2 + 0.01f * x->gl_x1; i++, f -= x->gl_xtick.k_inc) + for (int i = 1; f > 0.99f * x->gl_x1 + 0.01f * x->gl_x2; i++, f -= x->gl_xtick.k_inc) { auto xpos = jmap(f, x->gl_x2, x->gl_x1, x1, x2); int tickpix = (i % x->gl_xtick.k_lperb ? 2 : 4); @@ -252,7 +252,7 @@ class GraphOnParent final : public ObjectBase { if (x->gl_ytick.k_lperb) { t_float f = x->gl_ytick.k_point; - for (int i = 0; f < 0.99f * x->gl_y2 + 0.01f * x->gl_y1; i++, f += x->gl_ytick.k_inc) + for (int i = 0; f < 0.99f * x->gl_y1 + 0.01f * x->gl_y2; i++, f += x->gl_ytick.k_inc) { auto ypos = jmap(f, x->gl_y2, x->gl_y1, y1, y2); int tickpix = (i % x->gl_ytick.k_lperb ? 2 : 4); From 5e5057260b4caddaa2b6b93d39ca0c33365f74ce Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 16 Feb 2024 00:56:45 +0100 Subject: [PATCH 0217/1030] Fixed md docs issues --- Resources/Documentation/ELSE/xgate2.mc~.md | 6 +++--- Resources/Documentation/ELSE/xgate2~.md | 6 +++--- Resources/Documentation/ELSE/xselect2.mc~.md | 6 +++--- Resources/Documentation/ELSE/xselect2~.md | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Resources/Documentation/ELSE/xgate2.mc~.md b/Resources/Documentation/ELSE/xgate2.mc~.md index 63386fa658..361bdfb6f5 100644 --- a/Resources/Documentation/ELSE/xgate2.mc~.md +++ b/Resources/Documentation/ELSE/xgate2.mc~.md @@ -5,7 +5,7 @@ description: route a multichannel signal with crossfade categories: - object - + pdcategory: ELSE, Mixing and Routing arguments: @@ -15,9 +15,9 @@ arguments: - type: float description: spread default: 1 - + flags: -- type: index +- name: index description: sets to indexed mode inlets: diff --git a/Resources/Documentation/ELSE/xgate2~.md b/Resources/Documentation/ELSE/xgate2~.md index 3925e73bfc..71c216636c 100644 --- a/Resources/Documentation/ELSE/xgate2~.md +++ b/Resources/Documentation/ELSE/xgate2~.md @@ -5,7 +5,7 @@ description: route an input with crossfade categories: - object - + pdcategory: ELSE, Mixing and Routing arguments: @@ -15,9 +15,9 @@ arguments: - type: float description: spread default: 1 - + flags: -- type: index +- name: index description: sets to indexed mode inlets: diff --git a/Resources/Documentation/ELSE/xselect2.mc~.md b/Resources/Documentation/ELSE/xselect2.mc~.md index b854bd0b2c..f9ca708aed 100644 --- a/Resources/Documentation/ELSE/xselect2.mc~.md +++ b/Resources/Documentation/ELSE/xselect2.mc~.md @@ -5,7 +5,7 @@ description: select channel with crossfade categories: - object - + pdcategory: ELSE, Mixing and Routing arguments: @@ -17,9 +17,9 @@ arguments: default: 1 flags: -- type: index +- name: index description: sets to indexed mode -- type: circular +- name: circular description: sets to circular mode inlets: diff --git a/Resources/Documentation/ELSE/xselect2~.md b/Resources/Documentation/ELSE/xselect2~.md index a2ac67c5aa..4f6d533df7 100644 --- a/Resources/Documentation/ELSE/xselect2~.md +++ b/Resources/Documentation/ELSE/xselect2~.md @@ -5,7 +5,7 @@ description: select channel with crossfade categories: - object - + pdcategory: ELSE, Mixing and Routing arguments: @@ -17,9 +17,9 @@ arguments: default: 1 flags: -- type: index +- name: index description: sets to indexed mode -- type: circular +- name: circular description: sets to circular mode inlets: @@ -29,7 +29,7 @@ inlets: nth: - type: signal description: inlets alternate between input position and spread parameter for each channel - + outlets: 1st: - type: signal From 4341a369efe323380b650f555562eaad4a34a1ec Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 16 Feb 2024 02:46:15 +0100 Subject: [PATCH 0218/1030] Fixed iOS bug --- Source/Object.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Source/Object.h b/Source/Object.h index 97ae1756d6..4f9ef7c64d 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -99,11 +99,8 @@ class Object : public Component OwnedArray iolets; ResizableBorderComponent::Zone resizeZone; -#if JUCE_IOS - static inline constexpr int margin = 8; -#else static inline constexpr int margin = 6; -#endif + static inline constexpr int doubleMargin = margin * 2; static inline constexpr int height = 32; From c9aa3ff389421978e1d3ba9b4a0e2574becae45c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 16 Feb 2024 02:49:54 +0100 Subject: [PATCH 0219/1030] Added keyboard shortcut for toggling DSP state --- Source/Constants.h | 1 + Source/PluginEditor.cpp | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Source/Constants.h b/Source/Constants.h index 75d16aa7bf..0b5eb0d6e9 100644 --- a/Source/Constants.h +++ b/Source/Constants.h @@ -342,6 +342,7 @@ enum CommandIDs { ShowReference, ShowHelp, OpenObjectBrowser, + ToggleDSP, NumItems }; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 9647d88060..350aecedd6 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1279,6 +1279,11 @@ void PluginEditor::getCommandInfo(CommandID const commandID, ApplicationCommandI result.setActive(true); break; } + case CommandIDs::ToggleDSP: { + result.setInfo("Toggle DSP", "Enables or disables audio DSP", "Edit", 0); + result.setActive(true); + break; + } default: break; } @@ -1625,6 +1630,18 @@ bool PluginEditor::perform(InvocationInfo const& info) Dialogs::showObjectBrowserDialog(&openedDialog, this); return true; } + case CommandIDs::ToggleDSP: { + + if(pd_getdspstate()) + { + pd->releaseDSP(); + } + else { + pd->startDSP(); + }; + + return true; + } default: { cnv = getCurrentCanvas(); From e1f8836b232a5e48d17101090b296a0d3430cc9d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 16 Feb 2024 03:31:58 +0100 Subject: [PATCH 0220/1030] Fixed potential crash when saving and exiting --- Source/Pd/Patch.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Pd/Patch.cpp b/Source/Pd/Patch.cpp index 4f1364d7c2..c9705ab17d 100644 --- a/Source/Pd/Patch.cpp +++ b/Source/Pd/Patch.cpp @@ -180,9 +180,9 @@ void Patch::savePatch() pd::Interface::saveToFile(patch.get(), file, dir); } - MessageManager::callAsync([this, patch = ptr.getRaw()]() { + MessageManager::callAsync([instance = this->instance, file = this->currentFile, patch = ptr.getRaw()]() { sys_lock(); - instance->reloadAbstractions(currentFile, patch); + instance->reloadAbstractions(file, patch); sys_unlock(); }); } From 23b6ec76d4cb617e7e9925defe08aaf38be4a315 Mon Sep 17 00:00:00 2001 From: "taylor.fish" Date: Fri, 16 Feb 2024 00:18:08 -0800 Subject: [PATCH 0221/1030] Fix permissions of created directories With the custom default directory permissions, directories are created with mode `007 & umask` on Unix, which typically results in read-only directories, causing installation to fail. On Windows, the custom permissions don't do anything, because _wchmod() doesn't support any of the WORLD_* bits (S_I*OTH). CMake's default directory permissions are sufficient for being able to install files into those directories. --- CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ff5f5e422a..b8f7ae5530 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -578,12 +578,6 @@ if(MSVC) set_target_properties(pthreadVC3 pthreadVSE3 pthreadVCE3 PROPERTIES EXCLUDE_FROM_ALL 1 EXCLUDE_FROM_DEFAULT_BUILD 1) endif() -set(CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS - WORLD_READ - WORLD_WRITE - WORLD_EXECUTE -) - if(APPLE) install(DIRECTORY ${PLUGDATA_PLUGINS_LOCATION}/VST3/plugdata.vst3 DESTINATION "/Library/Audio/Plug-ins/VST3") install(DIRECTORY ${PLUGDATA_PLUGINS_LOCATION}/VST3/plugdata-fx.vst3 DESTINATION "/Library/Audio/Plug-ins/VST3") From c442bfab021d2c162d82ddcad52e67eaa6307761 Mon Sep 17 00:00:00 2001 From: "taylor.fish" Date: Fri, 16 Feb 2024 00:57:20 -0800 Subject: [PATCH 0222/1030] Fix JUCE submodule URL --- .gitmodules | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 7bd4ec1cb3..788eb8bf94 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "JUCE"] path = Libraries/JUCE - url = https://github.com/juce-framework/JUCE.git + url = https://github.com/plugdata-team/JUCE.git [submodule "Libraries/pure-data"] path = Libraries/pure-data url = https://github.com/plugdata-team/plugdata-pd @@ -22,8 +22,6 @@ [submodule "Libraries/juce-raw-keyboard-input-module"] path = Libraries/juce-raw-keyboard-input-module url = https://github.com/izzyreal/juce-raw-keyboard-input-module.git -[submodule "./JUCE"] - url = https://github.com/plugdata-team/JUCE.git [submodule "Libraries/readerwriterqueue"] path = Libraries/readerwriterqueue url = https://github.com/cameron314/readerwriterqueue.git From 99be2374bc2888588c648830e6fd985716206089 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 16 Feb 2024 14:52:57 +0100 Subject: [PATCH 0223/1030] Fixed iOS resource opening issues --- Source/Pd/Patch.cpp | 11 +++++++---- Source/PluginProcessor.cpp | 9 ++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Source/Pd/Patch.cpp b/Source/Pd/Patch.cpp index c9705ab17d..da556a9116 100644 --- a/Source/Pd/Patch.cpp +++ b/Source/Pd/Patch.cpp @@ -110,6 +110,7 @@ void Patch::savePatch(URL const& locationURL) outputStream->flush(); instance->logMessage("saved to: " + location.getFullPathName()); + canvas_rename(patch.get(), file, dir); #else pd::Interface::saveToFile(patch.get(), file, dir); #endif @@ -180,10 +181,12 @@ void Patch::savePatch() pd::Interface::saveToFile(patch.get(), file, dir); } - MessageManager::callAsync([instance = this->instance, file = this->currentFile, patch = ptr.getRaw()]() { - sys_lock(); - instance->reloadAbstractions(file, patch); - sys_unlock(); + MessageManager::callAsync([instance = juce::WeakReference(this->instance), file = this->currentFile, patch = ptr.getRaw()]() { + if(instance) { + sys_lock(); + instance->reloadAbstractions(file, patch); + sys_unlock(); + } }); } diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index c02af3ee69..955bd19694 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1262,16 +1262,15 @@ pd::Patch::Ptr PluginProcessor::loadPatch(URL const& patchURL, PluginEditor* edi auto inputStream = patchURL.createInputStream (URL::InputStreamOptions (URL::ParameterHandling::inAddress)); tempFile.appendText(inputStream->readEntireStreamAsString()); + auto dirname = patchFile.getParentDirectory().getFullPathName().replace("\\", "/"); + auto filename = patchFile.getFileName(); + + glob_forcefilename(generateSymbol(filename), generateSymbol(dirname)); auto newPatch = openPatch(tempFile); if(newPatch) { if(auto patch = newPatch->getPointer()) { - String dirname = patchFile.getParentDirectory().getFullPathName().replace("\\", "/"); - auto const* dir = dirname.toRawUTF8(); - String filename = patchFile.getFileName(); - auto const* file = filename.toRawUTF8(); - canvas_rename(patch.get(), gensym(file), gensym(dir)); newPatch->setTitle(filename); newPatch->setCurrentFile(patchURL); } From d5069fd88367f8b190389888a20bada7777d8622 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 16 Feb 2024 19:21:46 +0100 Subject: [PATCH 0224/1030] Updated gem, re-enabled modelloader --- Libraries/Gem | 2 +- Resources/Scripts/package_resources.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Libraries/Gem b/Libraries/Gem index 184934dc18..46c6af801b 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 184934dc183821b2dc3d071f391d26e1685d989e +Subproject commit 46c6af801bc304ebab9fbf35d7465ce653651df4 diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index df3bb324e6..dfc2e6fa25 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -138,7 +138,7 @@ def splitFile(file, num_files): if package_gem: makeDir("Abstractions/Gem") - + copyDir("../../Libraries/Gem/help", "Documentation/14.gem") copyDir("../../Libraries/Gem/examples", "Documentation/14.gem/examples") copyDir("../../Libraries/Gem/doc", "Documentation/14.gem/examples/Documentation") @@ -158,7 +158,7 @@ def splitFile(file, num_files): if system == 'linux': if 'aarch64' in machine or 'arm' in machine: gem_plugins_file = 'plugins_linux_arm64' - elif '64' in architecture: + elif '64' in machine: gem_plugins_file = 'plugins_linux_x64' elif system == 'darwin': gem_plugins_file = 'plugins_macos' From 01533cc7edeff8d10a267b7a099231c3ca04bf27 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 16 Feb 2024 21:20:05 +0100 Subject: [PATCH 0225/1030] Incremented version suffix --- Source/Utility/Config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index 2cc90577b7..b761a6cf5d 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -38,7 +38,7 @@ struct ProjectInfo { static inline File const appDataDir = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory).getChildFile("plugdata"); - static inline String const versionSuffix = "-4"; + static inline String const versionSuffix = "-5"; static inline File const versionDataDir = appDataDir.getChildFile("Versions").getChildFile(ProjectInfo::versionString + versionSuffix); }; From c38ccb22f536338b1001515d5cd5e10a3be35916 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 17 Feb 2024 14:26:22 +0100 Subject: [PATCH 0226/1030] Trying to fix initialisation issues on Windows --- Source/PluginProcessor.cpp | 16 ++++++++-------- Source/Standalone/InternalSynth.cpp | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 955bd19694..60761ec525 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -210,14 +210,16 @@ void PluginProcessor::initialiseFilesystem() MemoryInputStream memstream(allData.data(), allData.size(), false); homeDir.createDirectory(); - + versionDataDir.getParentDirectory().createDirectory(); + auto tempVersionDataDir = versionDataDir.getParentDirectory().getChildFile("plugdata_version"); + auto file = ZipFile(memstream); - file.uncompressTo(homeDir); - + file.uncompressTo(tempVersionDataDir.getParentDirectory()); + // Create filesystem for this specific version - versionDataDir.getParentDirectory().createDirectory(); - versionDataDir.createDirectory(); - homeDir.getChildFile("plugdata_version").moveFileTo(versionDataDir); + tempVersionDataDir.moveFileTo(versionDataDir); + + if(versionDataDir.isDirectory()) internalSynth->extractSoundfont(); } if (!deken.exists()) { deken.createDirectory(); @@ -266,8 +268,6 @@ void PluginProcessor::initialiseFilesystem() versionDataDir.getChildFile("Documentation").createSymbolicLink(homeDir.getChildFile("Documentation"), true); versionDataDir.getChildFile("Extra").createSymbolicLink(homeDir.getChildFile("Extra"), true); #endif - - internalSynth->extractSoundfont(); } void PluginProcessor::updateSearchPaths() diff --git a/Source/Standalone/InternalSynth.cpp b/Source/Standalone/InternalSynth.cpp index 766a176ef0..23b4043cdd 100644 --- a/Source/Standalone/InternalSynth.cpp +++ b/Source/Standalone/InternalSynth.cpp @@ -43,7 +43,6 @@ void InternalSynth::extractSoundfont() #ifdef PLUGDATA_STANDALONE // Unpack soundfont if (!soundFont.existsAsFile()) { - soundFont.getParentDirectory().createDirectory(); FileOutputStream ostream(soundFont); ostream.write(StandaloneBinaryData::GeneralUser_GS_sf3, StandaloneBinaryData::GeneralUser_GS_sf3Size); ostream.flush(); From a3dac27522b4fbbaf7664e0f1b29cb4be3902399 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 17 Feb 2024 14:52:59 +0100 Subject: [PATCH 0227/1030] Protect against 2 instances of plugdata initialising at the same time --- Source/PluginProcessor.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 60761ec525..b776573f78 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -187,9 +187,24 @@ void PluginProcessor::initialiseFilesystem() auto const& versionDataDir = ProjectInfo::versionDataDir; auto deken = homeDir.getChildFile("Externals"); auto patches = homeDir.getChildFile("Patches"); + + if(!homeDir.exists()) homeDir.createDirectory(); + auto initMutex = homeDir.getChildFile(".initialising"); + + // If this is true, another instance of plugdata is already initialising + // We wait a maximum of 5 seconds before we continue initialising, to prevent problems + int wait = 0; + while(initMutex.exists() && wait < 10) + { + Time::waitForMillisecondCounter(Time::getMillisecondCounter() + 500); + wait++; + } + + initMutex.create(); + // Check if the abstractions directory exists, if not, unzip it from binaryData - if (!homeDir.exists() || !versionDataDir.exists()) { + if (!versionDataDir.exists()) { // Binary data shouldn't be too big, then the compiler will run out of memory // To prevent this, we split the binarydata into multiple files, and add them back together here @@ -208,8 +223,7 @@ void PluginProcessor::initialiseFilesystem() } MemoryInputStream memstream(allData.data(), allData.size(), false); - - homeDir.createDirectory(); + versionDataDir.getParentDirectory().createDirectory(); auto tempVersionDataDir = versionDataDir.getParentDirectory().getChildFile("plugdata_version"); @@ -268,6 +282,8 @@ void PluginProcessor::initialiseFilesystem() versionDataDir.getChildFile("Documentation").createSymbolicLink(homeDir.getChildFile("Documentation"), true); versionDataDir.getChildFile("Extra").createSymbolicLink(homeDir.getChildFile("Extra"), true); #endif + + initMutex.deleteFile(); } void PluginProcessor::updateSearchPaths() From 152985ce394480c181bc63d126abe02b07374c79 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 17 Feb 2024 14:52:59 +0100 Subject: [PATCH 0228/1030] Protect against 2 instances of plugdata initialising at the same time --- Source/PluginProcessor.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 60761ec525..eee1e579a5 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -187,9 +187,24 @@ void PluginProcessor::initialiseFilesystem() auto const& versionDataDir = ProjectInfo::versionDataDir; auto deken = homeDir.getChildFile("Externals"); auto patches = homeDir.getChildFile("Patches"); + + if(!homeDir.exists()) homeDir.createDirectory(); + auto initMutex = homeDir.getChildFile(".initialising"); + + // If this is true, another instance of plugdata is already initialising + // We wait a maximum of 5 seconds before we continue initialising, to prevent problems + int wait = 0; + while(initMutex.exists() && wait < 20) + { + Time::waitForMillisecondCounter(Time::getMillisecondCounter() + 500); + wait++; + } + + initMutex.create(); + // Check if the abstractions directory exists, if not, unzip it from binaryData - if (!homeDir.exists() || !versionDataDir.exists()) { + if (!versionDataDir.exists()) { // Binary data shouldn't be too big, then the compiler will run out of memory // To prevent this, we split the binarydata into multiple files, and add them back together here @@ -208,8 +223,7 @@ void PluginProcessor::initialiseFilesystem() } MemoryInputStream memstream(allData.data(), allData.size(), false); - - homeDir.createDirectory(); + versionDataDir.getParentDirectory().createDirectory(); auto tempVersionDataDir = versionDataDir.getParentDirectory().getChildFile("plugdata_version"); @@ -268,6 +282,8 @@ void PluginProcessor::initialiseFilesystem() versionDataDir.getChildFile("Documentation").createSymbolicLink(homeDir.getChildFile("Documentation"), true); versionDataDir.getChildFile("Extra").createSymbolicLink(homeDir.getChildFile("Extra"), true); #endif + + initMutex.deleteFile(); } void PluginProcessor::updateSearchPaths() From 2c3dffa33a74b506a29d5c227b6d3efab4c922d7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 17 Feb 2024 19:04:28 +0100 Subject: [PATCH 0229/1030] Fixed link hitbox issues on Linux and Windows in heavy documentation dialog --- Source/Utility/MarkupDisplay.h | 127 ++++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 43 deletions(-) diff --git a/Source/Utility/MarkupDisplay.h b/Source/Utility/MarkupDisplay.h index d43e07cad5..90d84b47fd 100644 --- a/Source/Utility/MarkupDisplay.h +++ b/Source/Utility/MarkupDisplay.h @@ -195,35 +195,30 @@ class Block : public Component { { mouseDownPosition = event.position; // keep track of position } + + void mouseMove(MouseEvent const& event) override + { + bool isHoveringLink = false; + + for(auto& [link, bounds] : linkBounds) + { + if(bounds.contains(event.x, event.y)) + { + isHoveringLink = true; + break; + } + } + + setMouseCursor(isHoveringLink ? MouseCursor::PointingHandCursor : MouseCursor::NormalCursor); + } void mouseUp(MouseEvent const& event) override { - if (links.size() && !attributedString.getText().isEmpty()) { // if we have a link, and the block contains text - if (event.position.getDistanceFrom(mouseDownPosition) > 20) - return; - TextLayout layout; - layout.createLayout(attributedString, getWidth()); - // Look for clickable links - for (auto& [link, start, end] : links) { - int offset = 0; - for (auto& line : layout) { - for (auto* run : line.runs) { - auto linkBounds = Rectangle(); - for (int i = start - offset; i < end - offset; i++) { - if (i < 0 || i >= run->glyphs.size()) - continue; - auto& glyph = run->glyphs.getReference(i); - auto lineBounds = Rectangle(glyph.width, 14).withPosition((glyph.anchor + line.lineOrigin)); - linkBounds = linkBounds.isEmpty() ? lineBounds : linkBounds.getUnion(lineBounds); - } - - if (linkBounds.translated(0, -8).contains(mouseDownPosition)) { - URL(link).launchInDefaultBrowser(); // ...open link. - } - - offset += run->glyphs.size(); - } - } + for(auto& [link, bounds] : linkBounds) + { + if(bounds.contains(event.x, event.y)) + { + URL(link).launchInDefaultBrowser(); } } } @@ -241,20 +236,26 @@ class Block : public Component { for (auto line : lines) { if (line.startsWith("##### ")) { - attributedString.append(parsePureText(line.substring(6), font.boldened().withHeight(font.getHeight() * 1.1f), false)); + attributedString.append(parsePureText(line.substring(6), Fonts::getBoldFont().withHeight(font.getHeight() * 1.1f), false)); } else if (line.startsWith("#### ")) { - attributedString.append(parsePureText(line.substring(5), font.boldened().withHeight(font.getHeight() * 1.25f), false)); + attributedString.append(parsePureText(line.substring(5), Fonts::getBoldFont().withHeight(font.getHeight() * 1.25f), false)); } else if (line.startsWith("### ")) { - attributedString.append(parsePureText(line.substring(4), font.boldened().withHeight(font.getHeight() * 1.42f), false)); + attributedString.append(parsePureText(line.substring(4), Fonts::getBoldFont().withHeight(font.getHeight() * 1.42f), false)); } else if (line.startsWith("## ")) { - attributedString.append(parsePureText(line.substring(3), font.boldened().withHeight(font.getHeight() * 1.7f), false)); + attributedString.append(parsePureText(line.substring(3), Fonts::getBoldFont().withHeight(font.getHeight() * 1.7f), false)); } else if (line.startsWith("# ")) { - attributedString.append(parsePureText(line.substring(2), font.boldened().withHeight(font.getHeight() * 2.1f), false)); + attributedString.append(parsePureText(line.substring(2), Fonts::getBoldFont().withHeight(font.getHeight() * 2.1f), false)); } else { - auto parseLink = [this, &attributedString](String& link, int length) { + auto parseLink = [this, &attributedString](String& link, String& linkText) { if (link.isNotEmpty()) { +#if JUCE_MAC auto start = attributedString.getText().length(); - auto end = start + length; + auto end = start + linkText.length(); +#else + // macOS version of TextLayout considers whitespace as a character, but the Windows/Linux versions don't! + auto start = attributedString.getText().replace(" ", "").replace("\n", "").replace("\r", "").replace("\t", "").length(); + auto end = start + linkText.replace(" ", "").replace("\n", "").replace("\r", "").replace("\t", "").length(); +#endif links.add({ link, start, end }); link = ""; } @@ -272,18 +273,20 @@ class Block : public Component { if (bidx > -1 && ((bidx < iidx) | (iidx == -1)) && ((bidx < tidx) | (tidx == -1))) { // if the next token is toggling the bold state... // ...first add everything up to the token... + auto linkText = line.substring(0, bidx); if (bold) - parseLink(currentLink, bidx); - attributedString.append(line.substring(0, bidx), font, currentColour); + parseLink(currentLink, linkText); + attributedString.append(linkText, font, currentColour); line = line.substring(bidx + 1); // ...then drop up to and including the token... bold = !bold; // ...toggle the bold status... needsNewFont = true; // ...and request new font. } else if (iidx > -1 && ((iidx < tidx) | (tidx == -1))) { // if the next token is toggling the italic state... // ...first add everything up to the token... + auto linkText = line.substring(0, iidx); if (italic) - parseLink(currentLink, iidx); - attributedString.append(line.substring(0, iidx), font, currentColour); + parseLink(currentLink, linkText); + attributedString.append(linkText, font, currentColour); line = line.substring(iidx + 1); // ...then drop up to and including the token... italic = !italic; // ...toggle the italic status... needsNewFont = true; // ...and request new font. @@ -332,7 +335,7 @@ class Block : public Component { line = line.substring(tidx + 1); } } else { - parseLink(currentLink, line.length()); + parseLink(currentLink, line); // if no token was found -> add the remaining text... attributedString.append(line, font, currentColour); // ...and clear the line. @@ -341,12 +344,17 @@ class Block : public Component { currentColour = nextColour; if (needsNewFont) { - font = font.withStyle(Font::plain); + font = Fonts::getDefaultFont().withHeight(15); if (bold) { - font = font.boldened(); + font = Fonts::getBoldFont().withHeight(15); } if (italic) { - font = font.italicised(); + // italic only seems to work on macOS... +#if JUCE_MAC + font = Fonts::getVariableFont().italicised().withHeight(15); +#else + font = Fonts::getDefaultFont().withHeight(15); +#endif } } } @@ -358,6 +366,30 @@ class Block : public Component { } return attributedString; } + + void updateLinkBounds(TextLayout& layout) + { + // Look for clickable links + for (auto& [link, start, end] : links) { + int offset = 0; + auto currentLinkBounds = Rectangle(); + for (auto& line : layout) { + for (auto* run : line.runs) { + for (int i = start - offset; i < end - offset; i++) { + if (i < 0 || i >= run->glyphs.size()) + continue; + + auto& glyph = run->glyphs.getReference(i); + auto lineBounds = Rectangle(glyph.width, 14).withPosition((glyph.anchor + line.lineOrigin)); + currentLinkBounds = linkBounds.isEmpty() ? lineBounds : currentLinkBounds.getUnion(lineBounds); + } + + linkBounds.add({link, currentLinkBounds.translated(0, -11)}); + offset += run->glyphs.size(); + } + } + } + } Colour defaultColour; Colour currentColour; @@ -370,8 +402,9 @@ class Block : public Component { AttributedString attributedString; - + private: + Array>> linkBounds; Array> links; Point mouseDownPosition; }; @@ -396,6 +429,13 @@ class TextBlock : public Block { layout.createLayout(attributedString, getWidth()); layout.draw(g, getLocalBounds().toFloat()); } + + void resized() override + { + TextLayout layout; + layout.createLayout(attributedString, getWidth()); + updateLinkBounds(layout); + } private: }; @@ -509,6 +549,7 @@ class TableBlock : public Block { AttributedString attributedString = parsePureText(rawString.trim(), isHeader ? font.boldened() : font); TextLayout layout; layout.createLayout(attributedString, 1.0e7f); + updateLinkBounds(layout); row->add(new Cell { attributedString, isHeader, layout.getWidth(), layout.getHeight() }); } } @@ -839,7 +880,7 @@ class MarkupDisplayComponent : public Component { colours.set("gray", "#777"); // default font - font = Fonts::getVariableFont().withHeight(15); + font = Fonts::getDefaultFont().withHeight(15); // default table backgrounds tableBGHeader = Block::parseHexColourStatic(colours["lightcyan"], Colours::black); From c1a1556287453e2d18f8336c6f9bd37ba455943c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 17 Feb 2024 19:07:03 +0100 Subject: [PATCH 0230/1030] Small heavy docs panel fix --- Source/Utility/MarkupDisplay.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Utility/MarkupDisplay.h b/Source/Utility/MarkupDisplay.h index 90d84b47fd..69ea277b60 100644 --- a/Source/Utility/MarkupDisplay.h +++ b/Source/Utility/MarkupDisplay.h @@ -845,6 +845,13 @@ class ListItem : public Block { label.draw(g, getLocalBounds().withTrimmedLeft(indent).toFloat()); attributedString.draw(g, getLocalBounds().withTrimmedLeft(indent + gap).toFloat()); } + + void resized() override + { + TextLayout layout; + layout.createLayout(attributedString, getWidth() - indent - gap); + updateLinkBounds(layout); + } private: AttributedString label; From c8d6d07af373b2cd967ce0294dc051e2acf0a5d2 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 17 Feb 2024 19:12:40 +0100 Subject: [PATCH 0231/1030] Linux fixes for Heavy documentation component --- Source/Dialogs/HelpDialog.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Dialogs/HelpDialog.h b/Source/Dialogs/HelpDialog.h index fb080ac667..d36d08c522 100644 --- a/Source/Dialogs/HelpDialog.h +++ b/Source/Dialogs/HelpDialog.h @@ -100,7 +100,8 @@ class HelpDialog : public Component }); }; - addToDesktop(ComponentPeer::windowIsTemporary | ComponentPeer::windowHasDropShadow); + + addToDesktop(ComponentPeer::windowIsResizable | ComponentPeer::windowIsSemiTransparent | ComponentPeer::windowHasDropShadow); setVisible(true); // Position in centre of screen From a2eb4d0122a4ffff5ca7b94fa0fe20f078461775 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 18 Feb 2024 13:01:04 +0100 Subject: [PATCH 0232/1030] Fixed array vis and resize messages not working --- Source/Objects/ArrayObject.h | 40 +++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index e6f7465f90..867ad371da 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -26,6 +26,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess Value drawMode = SynchronousValue(); Value saveContents = SynchronousValue(); Value range = SynchronousValue(); + bool visible = true; std::function reloadGraphs = [](){}; @@ -228,11 +229,39 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess }); break; } - case hash("xpix"): { - // TODO: implement ticks + case hash("xticks"): { + MessageManager::callAsync([_this = SafePointer(this)] { + if(_this) _this->repaint(); + }); + break; + } + case hash("yticks"): { + MessageManager::callAsync([_this = SafePointer(this)] { + if(_this) _this->repaint(); + }); + break; + } + case hash("vis"): { + MessageManager::callAsync([_this = SafePointer(this), visible = atoms[0].getFloat()] { + if(_this) + { + _this->visible = static_cast(visible); + _this->repaint(); + } + }); + + break; } - case hash("ypix"): { - // TODO: implement ticks + case hash("resize"): { + MessageManager::callAsync([_this = SafePointer(this), newSize = atoms[0].getFloat()] { + if(_this) + { + _this->size = static_cast(newSize); + _this->updateSettings(); + _this->repaint(); + } + }); + break; } } } @@ -243,7 +272,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess // TODO: error colour Fonts::drawText(g, "array " + getUnexpandedName() + " is invalid", 0, 0, getWidth(), getHeight(), object->findColour(PlugDataColour::canvasTextColourId), 15, Justification::centred); error = false; - } else { + } else if(visible) { paintGraph(g); } } @@ -451,6 +480,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess return 0; } + Colour getContentColour() { if (auto garray = arr.get()) { From b9d213e6687c3444026382abc5b43a051f861771 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 18 Feb 2024 13:07:45 +0100 Subject: [PATCH 0233/1030] Fixed file drag-and-drop on canvas when path has spaces in it --- Source/PluginEditor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 350aecedd6..f9b95cc8bc 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -600,7 +600,7 @@ void PluginEditor::filesDropped(StringArray const& files, int x, int y) auto file = File(path); if (file.exists()) { auto position = cnv->getLocalPoint(this, Point(x, y)); - auto filePath = file.getFullPathName().replaceCharacter('\\', '/'); + auto filePath = file.getFullPathName().replaceCharacter('\\', '/').replace(" ", "\ "); auto* object = cnv->objects.add(new Object(cnv, "msg " + filePath, position)); object->hideEditor(); } From 12aa0811d0b4c3f577983c8d92f028e2a9d91923 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 18 Feb 2024 13:08:47 +0100 Subject: [PATCH 0234/1030] Improvement to drag-over to toggle, now also supports [button] --- Source/Canvas.cpp | 7 +++++++ Source/Objects/ButtonObject.h | 29 +++++++++++++++-------------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 499cc91ddc..5b5cdb78b4 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -791,6 +791,13 @@ void Canvas::mouseUp(MouseEvent const& e) } } } + + // Make sure the drag-over toggle action is ended + for (auto* object : objects) { + if (auto* obj = object->gui.get()) { + obj->untoggleObject(); + } + } } void Canvas::updateSidebarSelection() diff --git a/Source/Objects/ButtonObject.h b/Source/Objects/ButtonObject.h index cd020d68ed..0b256db3d7 100644 --- a/Source/Objects/ButtonObject.h +++ b/Source/Objects/ButtonObject.h @@ -37,29 +37,30 @@ class ButtonObject : public ObjectBase { repaint(); } - /* TODO: finish this! void toggleObject(Point position) override { + if (!alreadyTriggered) { - if (!alreadyBanged) { - - auto* button = ptr.get(); - outlet_float(button->x_obj.ob_outlet, 1); - update(); - alreadyBanged = true; + if(auto button = ptr.get()) { + outlet_float(button->x_obj.ob_outlet, 1); + } + state = true; + repaint(); + alreadyTriggered = true; } } void untoggleObject() override { - - if(alreadyBanged) + if(alreadyTriggered) { - auto* button = ptr.get(); - outlet_float(button->x_obj.ob_outlet, 0); - update(); + if(auto button = ptr.get()) { + outlet_float(button->x_obj.ob_outlet, 0); + } + state = false; + repaint(); + alreadyTriggered = false; } - alreadyBanged = false; - }*/ + } Rectangle getPdBounds() override { From f10a76bcec80ff0f031af5e5b37dcbd3096c539e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 18 Feb 2024 13:08:57 +0100 Subject: [PATCH 0235/1030] Small fix --- Source/Utility/OSUtils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Utility/OSUtils.cpp b/Source/Utility/OSUtils.cpp index 65c774f14b..36b78f41b4 100644 --- a/Source/Utility/OSUtils.cpp +++ b/Source/Utility/OSUtils.cpp @@ -312,7 +312,7 @@ juce::Array OSUtils::iterateDirectory(juce::File const& directory, b if (maximum > 0 && result.size() >= maximum) break; } - } catch (fs::filesystem_error e) { + } catch (fs::filesystem_error& e) { std::cerr << "Error while iterating over directory: " << e.path1() << std::endl; } } else { @@ -326,7 +326,7 @@ juce::Array OSUtils::iterateDirectory(juce::File const& directory, b if (maximum > 0 && result.size() >= maximum) break; } - } catch (fs::filesystem_error e) { + } catch (fs::filesystem_error& e) { std::cerr << "Error while iterating over directory: " << e.path1() << std::endl; } } From cf2fb0c7ac51c590001d6423be1472be644a6f3e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 18 Feb 2024 13:16:59 +0100 Subject: [PATCH 0236/1030] Fix for file dnd on canvas --- Source/PluginEditor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index f9b95cc8bc..1d9229bab5 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -600,7 +600,7 @@ void PluginEditor::filesDropped(StringArray const& files, int x, int y) auto file = File(path); if (file.exists()) { auto position = cnv->getLocalPoint(this, Point(x, y)); - auto filePath = file.getFullPathName().replaceCharacter('\\', '/').replace(" ", "\ "); + auto filePath = file.getFullPathName().replaceCharacter('\\', '/').replace(" ", "\\ "); auto* object = cnv->objects.add(new Object(cnv, "msg " + filePath, position)); object->hideEditor(); } From 650acc8f1116ae968270985397924bcd41cd5aab Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 18 Feb 2024 14:54:40 +0100 Subject: [PATCH 0237/1030] Fixed macOS warnings --- CMakeLists.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b8f7ae5530..3d85aeb4c1 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,7 @@ if(RUN_CLANG_TIDY) endif() if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "Release" CACHE STRING + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Default build type: Release" FORCE) endif() @@ -56,7 +56,7 @@ if(("${CMAKE_SYSTEM_NAME}" MATCHES "Linux" AND ${CMAKE_SYSTEM_PROCESSOR} MATCHES set(ENABLE_SFIZZ OFF) endif() -if(UNIX AND NOT LINUX AND NOT APPLE) # on BSD +if(BSD) message(STATUS "Disabled sfizz") message(STATUS "Disabled Gem") set(ENABLE_GEM OFF) @@ -197,7 +197,7 @@ set(JUCE_COMPILE_DEFINITIONS JUCE_REPORT_APP_USAGE=0 JUCE_LOG_ASSERTIONS=1 JUCE_STRICT_REFCOUNTEDPOINTER=1 - JUCE_WEB_BROWSER=0 + JUCE_WEB_BROWSER=0 JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1 JUCE_USE_COREIMAGE_LOADER=0 JUCE_SILENCE_XCODE_15_LINKER_WARNING=1 @@ -226,9 +226,9 @@ if(ENABLE_GEM) endif() add_library(juce STATIC) -target_compile_definitions(juce - PUBLIC - ${JUCE_COMPILE_DEFINITIONS} +target_compile_definitions(juce + PUBLIC + ${JUCE_COMPILE_DEFINITIONS} INTERFACE $ ) @@ -256,7 +256,7 @@ if(UNIX AND NOT APPLE) endif() # Fixes BSD compilation -if(UNIX AND NOT LINUX) +if(BSD) include_directories(/usr/local/include) link_directories(/usr/local/lib) endif() From 7e399a1db8a55079bbffa5c49ab1cdf7a2c5d5e1 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 18 Feb 2024 16:17:43 +0100 Subject: [PATCH 0238/1030] Fixed various cmake, compile and link warnings --- CMakeLists.txt | 3 ++- Libraries/CMakeLists.txt | 12 +++++++----- Libraries/Gem | 2 +- Libraries/pd-else | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d85aeb4c1..a73af7045f 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,8 @@ option(ENABLE_GEM "" ON) option(ENABLE_ASAN "" OFF) option(VERBOSE "" OFF) -set (CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_EXTENSIONS OFF) # Visiblity needs to be hidden for all plugin targets, otherwise loading both plugdata and plugdata-fx will cause problems. We later undo this for the standalone build, so that externals can load set(CMAKE_CXX_VISIBILITY_PRESET hidden) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index c406c56987..ce3ad6dce0 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -40,7 +40,7 @@ option(PD_UTILS "Compile libpd utilities" OFF) option(PD_EXTRA "Compile extras" ON) option(PD_LOCALE "Set the LC_NUMERIC number format to the default C locale" ON) - + # ------------------------------------------------------------------------------# # SOURCES # ------------------------------------------------------------------------------# @@ -306,11 +306,11 @@ if(CMAKE_C_COMPILER_ID MATCHES "Clang|GNU") # These are the warning flags I need to get pd to compile withoout warnings # Would be nice if these warnings were solved instead add_compile_options(-Wno-format -Wno-format-security -Wno-conversion -Wno-unused-variable -Wno-int-to-pointer-cast -Wno-parentheses -Wno-unused-value -Wno-unknown-pragmas) - + if(CMAKE_C_COMPILER_ID MATCHES "Clang") add_compile_options(-Wno-compare-distinct-pointer-types -Wno-implicit-int-conversion) endif() - + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-int-conversion -Wno-pointer-sign -Wno-pointer-to-int-cast -Wno-incompatible-pointer-types") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} -fno-finite-math-only -ffast-math -funroll-loops -fomit-frame-pointer -O3") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} -g -O0") @@ -345,7 +345,7 @@ list(APPEND SFONT_SOURCES if(MSVC) -list(APPEND SFONT_SOURCES +list(APPEND SFONT_SOURCES ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/vorbisenc.c ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/info.c ${FLUIDLITE_DIR}/libvorbis-1.3.5/lib/analysis.c @@ -406,6 +406,7 @@ target_compile_definitions(pd-src PRIVATE ${LIBPD_COMPILE_DEFINITIONS}) if(UNIX) target_compile_options(pd-src PRIVATE -fvisibility=default) + target_compile_options(externals PRIVATE -fvisibility=default) endif() add_library(pd-src-multi STATIC ${SOURCE_FILES}) @@ -415,6 +416,7 @@ if(MSVC) target_compile_definitions(pd-src-multi PRIVATE PTW32_STATIC_LIB=1 "EXTERN= ") endif() +set_target_properties(externals-multi PROPERTIES CXX_VISIBILITY_PRESET hidden) set_target_properties(pd-src-multi PROPERTIES CXX_VISIBILITY_PRESET hidden) set_target_properties(pd-src PROPERTIES POSITION_INDEPENDENT_CODE ON) @@ -461,4 +463,4 @@ endif() # PTHREAD # ------------------------------------------------------------------------------# set(THREADS_PREFER_PTHREAD_FLAG On) -set(CMAKE_THREAD_PREFER_PTHREAD True) \ No newline at end of file +set(CMAKE_THREAD_PREFER_PTHREAD True) diff --git a/Libraries/Gem b/Libraries/Gem index 46c6af801b..2771b908d7 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 46c6af801bc304ebab9fbf35d7465ce653651df4 +Subproject commit 2771b908d7ee7cffcb4417dd2f1f46e7e6bfd08b diff --git a/Libraries/pd-else b/Libraries/pd-else index 04893fcb59..21d3f396ba 160000 --- a/Libraries/pd-else +++ b/Libraries/pd-else @@ -1 +1 @@ -Subproject commit 04893fcb59f5f5649bd280e832d41dc8a0345499 +Subproject commit 21d3f396ba8bb79ae844645a24dfbca289562513 From 2c4974de122a22bd83c693e906c818699c588012 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 18 Feb 2024 16:35:12 +0100 Subject: [PATCH 0239/1030] Fixed Linux cmake warnings --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 2771b908d7..f8df4977e7 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 2771b908d7ee7cffcb4417dd2f1f46e7e6bfd08b +Subproject commit f8df4977e729c9417c2aa029721af25058629c15 From 95443601ee1d7147700e891d5759ed7ab0f61fbe Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 18 Feb 2024 16:50:00 +0100 Subject: [PATCH 0240/1030] Enable Jack support on Linux by default --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a73af7045f..00f2e73f40 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -207,7 +207,7 @@ set(JUCE_COMPILE_DEFINITIONS ) if(LINUX) - list(APPEND JUCE_COMPILE_DEFINITIONS JUCE_ALSA=1) + list(APPEND JUCE_COMPILE_DEFINITIONS JUCE_ALSA=1 JUCE_JACK=1 JUCE_JACK_CLIENT_NAME="plugdata") elseif(UNIX AND NOT APPLE) # BSD list(APPEND JUCE_COMPILE_DEFINITIONS JUCE_JACK=1 JUCE_JACK_CLIENT_NAME="plugdata") endif() From 471219abb92efb1bbe0b7cb8efd9732bdffee611 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 01:23:15 +0100 Subject: [PATCH 0241/1030] Fixed multi-line editing for text objects --- Source/Object.cpp | 61 +++++++++++++++++++++++++++++-------- Source/Objects/TextObject.h | 37 +++++++++++++++++++++- 2 files changed, 85 insertions(+), 13 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index 32ca363007..58a45035c7 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -28,9 +28,9 @@ extern "C" { #include -#include +//#include -int is_gem_object(const char* sym); +//int is_gem_object(const char* sym); } Object::Object(Canvas* parent, String const& name, Point position) @@ -63,7 +63,7 @@ Object::Object(pd::WeakReference object, Canvas* parent) initialise(); - setType("", std::move(object)); + setType("", object); } Object::~Object() @@ -167,7 +167,7 @@ void Object::valueChanged(Value& v) repaint(); } -bool Object::checkIfHvccCompatible() +bool Object::checkIfHvccCompatible() const { if (gui) { auto typeName = gui->getType(); @@ -1226,8 +1226,13 @@ void Object::hideEditor() outgoingEditor->removeListener(cnv->suggestor.get()); + // Get entered text, remove extra spaces at the end auto newText = outgoingEditor->getText().trimEnd(); + + newText = newText.replace("\n", " "); + newText = newText.replace(";", " ;"); + outgoingEditor.reset(); repaint(); @@ -1264,15 +1269,18 @@ void Object::openNewObjectEditor() editor->setColour(TextEditor::outlineColourId, Colours::transparentBlack); editor->setColour(TextEditor::focusedOutlineColourId, Colours::transparentBlack); + editor->setScrollToShowCursor(false); editor->setAlwaysOnTop(true); - editor->setMultiLine(false); + editor->setMultiLine(true); editor->setReturnKeyStartsNewLine(false); + editor->setScrollbarsShown(false); editor->setBorder(BorderSize(1, 6, 2, 2)); editor->setIndents(0, 0); editor->setJustification(Justification::centredLeft); editor->setBounds(getLocalBounds().reduced(Object::margin)); editor->addListener(this); + editor->addKeyListener(this); // Allow cancelling object creation with escape editor->onEscapeKey = [this]() { @@ -1314,6 +1322,34 @@ void Object::textEditorReturnKeyPressed(TextEditor& ed) } } +bool Object::keyPressed(KeyPress const& key, Component* component) +{ + if(auto* editor = newObjectEditor.get()) { + if (key.getKeyCode() == KeyPress::returnKey && editor && key.getModifiers().isShiftDown()) { + int caretPosition = editor->getCaretPosition(); + auto text = editor->getText(); + + if (!editor->getHighlightedRegion().isEmpty()) + return false; + if (text[caretPosition - 1] == ';') { + text = text.substring(0, caretPosition) + "\n" + text.substring(caretPosition); + caretPosition += 1; + } else { + text = text.substring(0, caretPosition) + ";\n" + text.substring(caretPosition); + caretPosition += 2; + } + + editor->setText(text); + editor->setCaretPosition(caretPosition); + cnv->hideSuggestions(); + + return true; + } + } + + return false; +} + void Object::updateOverlays(int overlay) { if (cnv->isGraph) @@ -1332,18 +1368,19 @@ void Object::updateOverlays(int overlay) void Object::textEditorTextChanged(TextEditor& ed) { String currentText; - if (cnv->suggestor && !cnv->suggestor->getText().isEmpty()) { + if (cnv->suggestor && !cnv->suggestor->getText().isEmpty() && !ed.getText().containsChar('\n')) { currentText = cnv->suggestor->getText(); } else { currentText = ed.getText(); } - + // For resize-while-typing behaviour - auto width = Font(15).getStringWidth(currentText) + 14.0f; - - width += Object::doubleMargin; - - setSize(width, getHeight()); + auto newWidth = CachedStringWidth<15>::calculateStringWidth(currentText) + 14.0f; + newWidth += Object::doubleMargin; + + auto numLines = StringArray::fromLines(currentText.trimEnd()).size(); + auto newHeight = std::max((numLines * 15) + 5 + Object::doubleMargin, height); + setSize(newWidth, newHeight); } ComponentBoundsConstrainer* Object::getConstrainer() const diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index 9e1bd80438..33ba405d0d 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -195,7 +195,7 @@ struct TextObjectHelper { // Text base class that text objects with special implementation details can derive from class TextBase : public ObjectBase - , public TextEditor::Listener { + , public TextEditor::Listener, public KeyListener { protected: std::unique_ptr editor; @@ -451,6 +451,7 @@ class TextBase : public ObjectBase editor->setBounds(getLocalBounds()); editor->setText(objectText, false); editor->addListener(this); + editor->addKeyListener(this); editor->selectAll(); addAndMakeVisible(editor.get()); @@ -498,7 +499,41 @@ class TextBase : public ObjectBase object->updateBounds(); } } + + bool keyPressed(KeyPress const& key, Component* component) override + { + /* + if (key == KeyPress::rightKey && editor && !editor->getHighlightedRegion().isEmpty()) { + editor->setCaretPosition(editor->getHighlightedRegion().getEnd()); + return true; + } + if (key == KeyPress::leftKey && editor && !editor->getHighlightedRegion().isEmpty()) { + editor->setCaretPosition(editor->getHighlightedRegion().getStart()); + return true; + } */ + if (key.getKeyCode() == KeyPress::returnKey && editor && key.getModifiers().isShiftDown()) { + int caretPosition = editor->getCaretPosition(); + auto text = editor->getText(); + + if (!editor->getHighlightedRegion().isEmpty()) + return false; + if (text[caretPosition - 1] == ';') { + text = text.substring(0, caretPosition) + "\n" + text.substring(caretPosition); + caretPosition += 1; + } else { + text = text.substring(0, caretPosition) + ";\n" + text.substring(caretPosition); + caretPosition += 2; + } + + editor->setText(text); + editor->setCaretPosition(caretPosition); + return true; + } + + return false; + } + void resized() override { if (editor) { From 8f12c725c6c5880e66d7f1e744b581fd1d344081 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 01:23:46 +0100 Subject: [PATCH 0242/1030] Large cleanup based on clang-tidy suggestions --- Source/Canvas.cpp | 6 +-- Source/Canvas.h | 2 - Source/Components/BouncingViewport.h | 2 +- Source/Components/CheckedTooltip.h | 2 +- Source/Constants.h | 10 ----- Source/Dialogs/AddObjectMenu.h | 4 +- Source/Dialogs/AlignmentTools.h | 4 +- Source/Dialogs/AudioSettingsPanel.h | 8 +--- Source/Dialogs/ConnectionMessageDisplay.h | 19 ++++------ Source/Dialogs/Dialogs.cpp | 26 ++++++------- Source/Dialogs/Dialogs.h | 15 ++------ Source/Dialogs/HelpDialog.h | 4 +- Source/Dialogs/ObjectBrowserDialog.h | 15 +++----- Source/Dialogs/ObjectReferenceDialog.h | 2 +- Source/Dialogs/PathsAndLibrariesPanel.h | 2 +- Source/Dialogs/SaveDialog.h | 2 +- Source/Dialogs/SettingsDialog.h | 6 +-- Source/Dialogs/SnapSettings.h | 2 +- Source/Heavy/DaisyExporter.h | 9 ++--- Source/Heavy/ExporterBase.h | 1 - Source/Heavy/HeavyExportDialog.cpp | 17 +-------- Source/Iolet.cpp | 15 -------- Source/Iolet.h | 1 - Source/LookAndFeel.cpp | 44 +++++++++++----------- Source/LookAndFeel.h | 2 +- Source/Object.h | 8 ++-- Source/ObjectGrid.cpp | 2 +- Source/ObjectGrid.h | 6 +-- Source/Objects/AtomHelper.h | 31 --------------- Source/Objects/BicoeffObject.h | 3 +- Source/Objects/Gem.h | 6 +-- Source/Objects/IEMHelper.h | 15 -------- Source/Objects/ImplementationBase.h | 3 +- Source/Objects/KnobObject.h | 16 -------- Source/Objects/ListObject.h | 4 -- Source/Objects/LuaObject.h | 2 +- Source/Objects/MessageObject.h | 8 ---- Source/Objects/MidiObjects.h | 4 +- Source/Objects/PdTildeObject.h | 10 ++--- Source/Objects/SubpatchObject.h | 4 -- Source/Objects/TextDefineObject.h | 2 + Source/Pd/Instance.h | 12 +++--- Source/Pd/Interface.h | 12 +++--- Source/Pd/Library.cpp | 31 +-------------- Source/Pd/Library.h | 22 ++++++++--- Source/Pd/MessageListener.h | 2 +- Source/Pd/Patch.cpp | 40 +++----------------- Source/Pd/Patch.h | 3 -- Source/Pd/Setup.cpp | 23 ++++++----- Source/Pd/Setup.h | 2 +- Source/PluginEditor.cpp | 5 ++- Source/PluginMode.h | 2 +- Source/PluginProcessor.cpp | 4 +- Source/PluginProcessor.h | 6 +-- Source/Sidebar/AutomationPanel.h | 8 ++-- Source/Sidebar/Console.h | 4 +- Source/Sidebar/DocumentationBrowser.h | 11 ++---- Source/Sidebar/Inspector.h | 13 +------ Source/Sidebar/PaletteItem.h | 6 +-- Source/Sidebar/Palettes.h | 2 + Source/Sidebar/Sidebar.cpp | 20 ---------- Source/Sidebar/Sidebar.h | 8 ++-- Source/Statusbar.cpp | 8 ++-- Source/Tabbar/ResizableTabbedComponent.cpp | 8 ++-- Source/Tabbar/ResizableTabbedComponent.h | 2 +- Source/Tabbar/SplitViewResizer.cpp | 10 +---- Source/Tabbar/SplitViewResizer.h | 9 ----- Source/Tabbar/TabBarButtonComponent.cpp | 8 +--- Source/Tabbar/TabBarButtonComponent.h | 4 +- Source/Tabbar/Tabbar.cpp | 8 +--- Source/Tabbar/Tabbar.h | 5 --- Source/Utility/AudioMidiFifo.h | 15 -------- Source/Utility/Config.h | 18 ++++++--- Source/Utility/ModifierKeyListener.h | 2 +- Source/Utility/OSUtils.mm | 6 +-- Source/Utility/ObjectThemeManager.h | 2 +- Source/Utility/StackShadow.h | 4 +- Source/Utility/ValueTreeNodeBranchLine.h | 2 +- Source/Utility/ValueTreeViewer.h | 2 +- 79 files changed, 208 insertions(+), 485 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 5b5cdb78b4..825adc7120 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -814,7 +814,7 @@ void Canvas::updateSidebarSelection() if (!object->gui) continue; auto parameters = object->gui ? object->gui->getParameters() : ObjectParameters(); - auto showOnSelect = object->gui ? object->gui->showParametersWhenSelected() : false; + auto showOnSelect = object->gui && object->gui->showParametersWhenSelected(); if (showOnSelect) { allParameters.add(parameters); } @@ -1516,8 +1516,8 @@ void Canvas::alignObjects(Align alignment) auto getSpacerX = [selectedBounds](Array& objects) -> float { auto totalWidths = 0; - for (int i = 0; i < objects.size(); i++) { - totalWidths += objects[i]->getWidth() - (Object::margin * 2); + for (auto* object : objects) { + totalWidths += object->getWidth() - (Object::margin * 2); } auto selectedBoundsNoMargin = selectedBounds.getWidth() - (Object::margin * 2); auto spacer = (selectedBoundsNoMargin - totalWidths) / static_cast(objects.size() - 1); diff --git a/Source/Canvas.h b/Source/Canvas.h index b431a41bc7..69fa030ddd 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -184,7 +184,6 @@ class Canvas : public Component bool connectionsBehind = true; bool isGraph = false; - bool hasParentCanvas = false; bool isDraggingLasso = false; bool needsSearchUpdate = false; @@ -201,7 +200,6 @@ class Canvas : public Component ObjectGrid objectGrid = ObjectGrid(this); Point const canvasOrigin; - Point viewportPositionBeforeMiddleDrag = { 0, 0 }; std::unique_ptr graphArea; diff --git a/Source/Components/BouncingViewport.h b/Source/Components/BouncingViewport.h index 23d7395ac6..cc8b4c7155 100644 --- a/Source/Components/BouncingViewport.h +++ b/Source/Components/BouncingViewport.h @@ -10,7 +10,7 @@ class BouncingViewportAttachment : public MouseListener , public Timer { public: - BouncingViewportAttachment(Viewport* vp) + explicit BouncingViewportAttachment(Viewport* vp) : viewport(vp) { viewport->setScrollOnDragMode(Viewport::ScrollOnDragMode::never); diff --git a/Source/Components/CheckedTooltip.h b/Source/Components/CheckedTooltip.h index 7f3abe6861..e454c9557f 100644 --- a/Source/Components/CheckedTooltip.h +++ b/Source/Components/CheckedTooltip.h @@ -14,7 +14,7 @@ class CheckedTooltip : public TooltipWindow { public: - CheckedTooltip( + explicit CheckedTooltip( Component* target, std::function checkTooltip = [](Component*) { return true; }, int timeout = 500) : TooltipWindow(target, timeout) , checker(std::move(checkTooltip)) diff --git a/Source/Constants.h b/Source/Constants.h index 0b5eb0d6e9..b5467c5b69 100644 --- a/Source/Constants.h +++ b/Source/Constants.h @@ -407,16 +407,6 @@ enum Overlay { Behind = 128 }; -enum OverlayItem { - OverlayOrigin = 0, - OverlayBorder, - OverlayIndex, - OverlayActivationState, - OverlayDirection, - OverlayOrder, - OverlayConnectionsBehind -}; - enum Align { Left = 0, Right, diff --git a/Source/Dialogs/AddObjectMenu.h b/Source/Dialogs/AddObjectMenu.h index 2a21eae474..49c5b74b56 100644 --- a/Source/Dialogs/AddObjectMenu.h +++ b/Source/Dialogs/AddObjectMenu.h @@ -207,7 +207,7 @@ class ObjectList : public Component { } }, { "UI", { - // GUI object default settings are in OjbectManager.h + // GUI object default settings are in ObjectManager.h { Icons::GlyphBang, "bng", "(@keypress) Bang", "Bang", NewBang }, { Icons::GlyphToggle, "tgl", "(@keypress) Toggle", "Toggle", NewToggle }, { Icons::GlyphButton, "button", "Button", "Button", OtherObject }, @@ -668,7 +668,7 @@ class AddObjectMenu : public Component { if (pinButton.toggleState) { animator.animateComponent(currentCalloutBox, currentCalloutBox->getBounds(), shouldHide ? 0.1f : 1.0f, 300, false, 0.0f, 0.0f); } - // Otherwise, fade the panel on drag start: calling dismiss or setVisible will lead the the drag event getting lost, so we just set alpha instead + // Otherwise, fade the panel on drag start: calling dismiss or setVisible will lead to the drag event getting lost, so we just set alpha instead // Ditto for calling animator.fadeOut because that will also call setVisible(false) else if (shouldHide) { animator.animateComponent(currentCalloutBox, currentCalloutBox->getBounds(), 0.0f, 300, false, 0.0f, 0.0f); diff --git a/Source/Dialogs/AlignmentTools.h b/Source/Dialogs/AlignmentTools.h index 4866911ae9..b0bc386d6c 100644 --- a/Source/Dialogs/AlignmentTools.h +++ b/Source/Dialogs/AlignmentTools.h @@ -175,7 +175,7 @@ class AlignmentTools : public Component { isShowing = true; auto alignmentTools = std::make_unique(); - alignmentTools->pluginEditor = static_cast(editor); + alignmentTools->pluginEditor = dynamic_cast(editor); CallOutBox::launchAsynchronously(std::move(alignmentTools), bounds, editor); } @@ -185,7 +185,7 @@ class AlignmentTools : public Component { } private: - PluginEditor* pluginEditor; + PluginEditor* pluginEditor = nullptr; static inline bool isShowing = false; diff --git a/Source/Dialogs/AudioSettingsPanel.h b/Source/Dialogs/AudioSettingsPanel.h index 78a00e9e17..64063da34c 100644 --- a/Source/Dialogs/AudioSettingsPanel.h +++ b/Source/Dialogs/AudioSettingsPanel.h @@ -240,9 +240,7 @@ class StandaloneAudioSettings : public SettingsDialogPanel StringArray sampleRateStrings; for (auto& rate : sampleRates) { auto rateAsString = String(rate); - if (::getValue(showAllAudioDeviceValues)) { - sampleRateStrings.add(rateAsString); - } else if (standardSampleRates.contains(rateAsString)) { + if (::getValue(showAllAudioDeviceValues) || standardSampleRates.contains(rateAsString)) { sampleRateStrings.add(rateAsString); } } @@ -265,9 +263,7 @@ class StandaloneAudioSettings : public SettingsDialogPanel StringArray bufferSizeStrings; for (auto& size : bufferSizes) { auto sizeAsString = String(size); - if (::getValue(showAllAudioDeviceValues)) { - bufferSizeStrings.add(sizeAsString); - } else if (standardBufferSizes.contains(sizeAsString)) { + if (::getValue(showAllAudioDeviceValues) || standardBufferSizes.contains(sizeAsString)) { bufferSizeStrings.add(sizeAsString); } } diff --git a/Source/Dialogs/ConnectionMessageDisplay.h b/Source/Dialogs/ConnectionMessageDisplay.h index fba82997dd..b7a10e7666 100644 --- a/Source/Dialogs/ConnectionMessageDisplay.h +++ b/Source/Dialogs/ConnectionMessageDisplay.h @@ -314,7 +314,7 @@ class ConnectionMessageDisplay for (int x = channelBounds.getX() + 1; x < channelBounds.getRight(); x++) { auto index = jmap(x, channelBounds.getX(), channelBounds.getRight(), 0, samplesPerCycle); - // linearl interpolation, especially needed for high-frequency signals + // linear interpolation, especially needed for high-frequency signals auto roundedIndex = static_cast(index); auto currentSample = lastSamples[ch][roundedIndex]; auto nextSample = roundedIndex == 1023 ? lastSamples[ch][roundedIndex] : lastSamples[ch][roundedIndex + 1]; @@ -347,15 +347,12 @@ class ConnectionMessageDisplay g.drawText(text, textBounds.toNearestInt(), Justification::centred); } } else { - int startPostionX = 8 + 4; + int startPositionX = 8 + 4; for (auto const& item : messageItemsWithFormat) { - Fonts::drawStyledText(g, item.text, startPostionX, 0, item.width, getHeight(), findColour(PlugDataColour::panelTextColourId), item.fontStyle, 14, Justification::centredLeft); - startPostionX += item.width + 4; + Fonts::drawStyledText(g, item.text, startPositionX, 0, item.width, getHeight(), findColour(PlugDataColour::panelTextColourId), item.fontStyle, 14, Justification::centredLeft); + startPositionX += item.width + 4; } } - - // used for cached background shadow - previousBounds = getBounds(); } static inline bool isShowing = false; @@ -385,27 +382,25 @@ class ConnectionMessageDisplay Point circlePosition = { 8.0f + 4.0f, 36.0f / 2.0f }; - Rectangle previousBounds; - struct SignalBlock { SignalBlock() : numChannels(0) { } - SignalBlock(float* input, int channels) + SignalBlock(float const* input, int channels) : numChannels(channels) { std::copy(input, input + (numChannels * DEFDACBLKSIZE), samples); } - SignalBlock(SignalBlock&& toMove) + SignalBlock(SignalBlock&& toMove) noexcept { numChannels = toMove.numChannels; std::copy(toMove.samples, toMove.samples + (numChannels * DEFDACBLKSIZE), samples); } - SignalBlock& operator=(SignalBlock&& toMove) + SignalBlock& operator=(SignalBlock&& toMove) noexcept { if (&toMove != this) { numChannels = toMove.numChannels; diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index d1c923e7ee..53cac35970 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -190,7 +190,7 @@ void Dialogs::showMainMenu(PluginEditor* editor, Component* centre) }); } -void Dialogs::showOkayCancelDialog(std::unique_ptr* target, Component* parent, String const& title, std::function const& callback, StringArray options) +void Dialogs::showOkayCancelDialog(std::unique_ptr* target, Component* parent, String const& title, std::function const& callback, StringArray const& options) { class OkayCancelDialog : public Component { @@ -198,7 +198,7 @@ void Dialogs::showOkayCancelDialog(std::unique_ptr* target, Component* p TextLayout layout; public: - OkayCancelDialog(Dialog* dialog, String const& title, std::function const& callback, StringArray& options) + OkayCancelDialog(Dialog* dialog, String const& title, std::function const& callback, StringArray const& options) : label("", title) { auto attributedTitle = AttributedString(title); @@ -349,7 +349,7 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent struct QuickActionsBar : public PopupMenu::CustomComponent { struct QuickActionButton : public TextButton { - QuickActionButton(String buttonText) + explicit QuickActionButton(const String& buttonText) : TextButton(buttonText) { } @@ -376,7 +376,7 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent std::unique_ptr tooltipWindow; - QuickActionsBar(PluginEditor* editor) + explicit QuickActionsBar(PluginEditor* editor) { // If the tooltip has it's own window, it should also have its own TooltipWindow! if (ProjectInfo::canUseSemiTransparentWindows()) { @@ -433,7 +433,7 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent // We have a custom function for this, instead of the default JUCE way, because the default JUCE way is broken on Linux // It will not find a target to apply the command to once the popupmenu grabs focus... - auto addCommandItem = [editor = cnv->editor](PopupMenu& menu, CommandID const commandID, String displayName = "") { + auto addCommandItem = [editor = cnv->editor](PopupMenu& menu, CommandID const commandID, String const& displayName = "") { if (auto* registeredInfo = editor->commandManager.getCommandForID(commandID)) { ApplicationCommandInfo info(*registeredInfo); editor->getCommandInfo(commandID, info); @@ -539,8 +539,8 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent cnv->grabKeyboardFocus(); // Make sure that iolets don't hang in hovered state - for (auto* object : cnv->objects) { - for (auto* iolet : object->iolets) + for (auto* o : cnv->objects) { + for (auto* iolet : o->iolets) reinterpret_cast(iolet)->repaint(); } @@ -582,9 +582,9 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent // The FORWARD double for loop makes sure that they keep their original order cnv->patch.startUndoSequence("ToFront"); - for (auto& object : objects) { + for (auto& o : objects) { for (auto* selectedBox : selectedBoxes) { - if (object == selectedBox->getPointer()) { + if (o == selectedBox->getPointer()) { selectedBox->toFront(false); if (selectedBox->gui) selectedBox->gui->moveToFront(); @@ -600,9 +600,9 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent // The FORWARD double for loop makes sure that they keep their original order cnv->patch.startUndoSequence("MoveForward"); - for (auto& object : objects) { + for (auto& o : objects) { for (auto* selectedBox : selectedBoxes) { - if (object == selectedBox->getPointer()) { + if (o == selectedBox->getPointer()) { selectedBox->toFront(false); if (selectedBox->gui) selectedBox->gui->moveForward(); @@ -670,7 +670,7 @@ void Dialogs::showObjectMenu(PluginEditor* editor, Component* target) AddObjectMenu::show(editor, editor->getLocalArea(target, target->getLocalBounds())); } -void Dialogs::showOpenDialog(std::function callback, bool canSelectFiles, bool canSelectDirectories, String const& extension, String const& lastFileId, Component* parentComponent) +void Dialogs::showOpenDialog(std::function const& callback, bool canSelectFiles, bool canSelectDirectories, String const& extension, String const& lastFileId, Component* parentComponent) { bool nativeDialog = SettingsFile::getInstance()->wantsNativeDialog(); auto initialFile = lastFileId.isNotEmpty() ? SettingsFile::getInstance()->getLastBrowserPathForId(lastFileId) : ProjectInfo::appDataDir; @@ -703,7 +703,7 @@ void Dialogs::showOpenDialog(std::function callback, bool canSelectFi }); } -void Dialogs::showSaveDialog(std::function callback, String const& extension, String const& lastFileId, Component* parentComponent, bool directoryMode) +void Dialogs::showSaveDialog(std::function const& callback, String const& extension, String const& lastFileId, Component* parentComponent, bool directoryMode) { bool nativeDialog = SettingsFile::getInstance()->wantsNativeDialog(); auto initialFile = lastFileId.isNotEmpty() ? SettingsFile::getInstance()->getLastBrowserPathForId(lastFileId) : ProjectInfo::appDataDir; diff --git a/Source/Dialogs/Dialogs.h b/Source/Dialogs/Dialogs.h index 6a08951f89..ec46b23024 100644 --- a/Source/Dialogs/Dialogs.h +++ b/Source/Dialogs/Dialogs.h @@ -52,7 +52,7 @@ class Dialog : public Component { } } - ~Dialog() + ~Dialog() override { if (auto* window = dynamic_cast(getTopLevelComponent())) { if (ProjectInfo::isStandalone) { @@ -74,11 +74,6 @@ class Dialog : public Component { resized(); } - Component* getViewedComponent() const - { - return viewedComponent.get(); - } - bool wantsRoundedCorners() const; void paint(Graphics& g) override @@ -188,7 +183,7 @@ struct Dialogs { static void showMainMenu(PluginEditor* editor, Component* centre); - static void showOkayCancelDialog(std::unique_ptr* target, Component* parent, String const& title, std::function const& callback, StringArray options = { "Okay", "Cancel " }); + static void showOkayCancelDialog(std::unique_ptr* target, Component* parent, String const& title, std::function const& callback, StringArray const& options = { "Okay", "Cancel " }); static void showHeavyExportDialog(std::unique_ptr* target, Component* parent); @@ -202,11 +197,9 @@ struct Dialogs { static void showDeken(PluginEditor* editor); static void showPatchStorage(PluginEditor* editor); - static PopupMenu createObjectMenu(PluginEditor* parent); - - static void showOpenDialog(std::function callback, bool canSelectFiles, bool canSelectDirectories, String const& lastFileId, String const& extension, Component* parentComponent); + static void showOpenDialog(std::function const& callback, bool canSelectFiles, bool canSelectDirectories, String const& lastFileId, String const& extension, Component* parentComponent); - static void showSaveDialog(std::function callback, String const& extension, String const& lastFileId, Component* parentComponent = nullptr, bool directoryMode = false); + static void showSaveDialog(std::function const& callback, String const& extension, String const& lastFileId, Component* parentComponent = nullptr, bool directoryMode = false); static inline std::unique_ptr fileChooser = nullptr; }; diff --git a/Source/Dialogs/HelpDialog.h b/Source/Dialogs/HelpDialog.h index d36d08c522..85632ea909 100644 --- a/Source/Dialogs/HelpDialog.h +++ b/Source/Dialogs/HelpDialog.h @@ -17,7 +17,7 @@ class HelpDialog : public Component class IndexComponent : public Component { public: - IndexComponent(std::function loadFile) + explicit IndexComponent(std::function loadFile) { for (auto const& file : OSUtils::iterateDirectory(manualPath, false, true)) { if (file.hasFileExtension(".md")) { @@ -79,7 +79,7 @@ class HelpDialog : public Component PluginProcessor* pd; MarkupDisplay::MarkupDisplayComponent markupDisplay; - HelpDialog(PluginProcessor* instance) + explicit HelpDialog(PluginProcessor* instance) : resizer(this, &constrainer) //, index([this](File const& file) { markupDisplay.setMarkdownString(file.loadFileAsString()); }) , pd(instance) diff --git a/Source/Dialogs/ObjectBrowserDialog.h b/Source/Dialogs/ObjectBrowserDialog.h index e69845c3fe..5b12a3542d 100644 --- a/Source/Dialogs/ObjectBrowserDialog.h +++ b/Source/Dialogs/ObjectBrowserDialog.h @@ -81,7 +81,7 @@ class ObjectsListBox : public ListBox , objectDescription(description) , rowIsSelected(isSelected) , objectsListBox(parent) - , dismissMenu(dismissDialog) + , dismissMenu(std::move(dismissDialog)) { } @@ -157,11 +157,6 @@ class ObjectsListBox : public ListBox return objectName + String(" object"); } - String getItemName() const - { - return objectName; - } - void refresh(String name, String description, int rowNumber, bool isSelected) { objectName = name; @@ -172,7 +167,7 @@ class ObjectsListBox : public ListBox } private: - int row; + int row = 0; String objectName; String objectDescription; bool rowIsSelected = false; @@ -217,7 +212,7 @@ class ObjectsListBox : public ListBox changeCallback(objects[row]); } - virtual void paintListBoxItem(int rowNumber, Graphics& g, int width, int height, bool rowIsSelected) override + void paintListBoxItem(int rowNumber, Graphics& g, int width, int height, bool rowIsSelected) override { } @@ -278,7 +273,7 @@ class ObjectViewerDragArea : public ObjectDragAndDrop { setBufferedToImage(true); } - ~ObjectViewerDragArea() { } + ~ObjectViewerDragArea() override { } void setObjectName(String name) { @@ -341,7 +336,7 @@ class ObjectViewer : public Component { public: ObjectViewer(PluginEditor* editor, ObjectReferenceDialog& objectReference, std::function dismissMenu) - : objectDragArea(dismissMenu) + : objectDragArea(std::move(dismissMenu)) , library(*editor->pd->objectLibrary) , reference(objectReference) { diff --git a/Source/Dialogs/ObjectReferenceDialog.h b/Source/Dialogs/ObjectReferenceDialog.h index 1b52ce9505..1bd3a626cc 100644 --- a/Source/Dialogs/ObjectReferenceDialog.h +++ b/Source/Dialogs/ObjectReferenceDialog.h @@ -91,7 +91,7 @@ class ObjectInfoPanel : public Component { categoriesHolder.setVisible(true); } - void showObject(ValueTree objectInfo) + void showObject(ValueTree const& objectInfo) { categories.clear(); diff --git a/Source/Dialogs/PathsAndLibrariesPanel.h b/Source/Dialogs/PathsAndLibrariesPanel.h index f0386ec034..47f08fb24c 100644 --- a/Source/Dialogs/PathsAndLibrariesPanel.h +++ b/Source/Dialogs/PathsAndLibrariesPanel.h @@ -8,7 +8,7 @@ #pragma once -// Draws a trigger button in the style of the PropertiesPanel, though it's meant to be used outside of the PropertiesPanel itself +// Draws a trigger button in the style of the PropertiesPanel, though it's meant to be used outside PropertiesPanel itself class ActionButton : public Component { bool mouseIsOver = false; diff --git a/Source/Dialogs/SaveDialog.h b/Source/Dialogs/SaveDialog.h index aa9fc08089..849a889a17 100644 --- a/Source/Dialogs/SaveDialog.h +++ b/Source/Dialogs/SaveDialog.h @@ -6,7 +6,7 @@ class SaveDialogButton : public TextButton { public: - SaveDialogButton(String buttonText) + explicit SaveDialogButton(String const& buttonText) : TextButton(buttonText) { } diff --git a/Source/Dialogs/SettingsDialog.h b/Source/Dialogs/SettingsDialog.h index 55697f01f6..5be8fe49c8 100644 --- a/Source/Dialogs/SettingsDialog.h +++ b/Source/Dialogs/SettingsDialog.h @@ -96,10 +96,10 @@ class SettingsDialog : public Component { panels.add(new AdvancedSettingsPanel(editor)); Array propertiesPanels; - for (int i = 0; i < panels.size(); i++) { - addChildComponent(panels[i]); + for (auto* i : panels) { + addChildComponent(i); - if (auto* panel = panels[i]->getPropertiesPanel()) { + if (auto* panel = i->getPropertiesPanel()) { propertiesPanels.add(panel); } } diff --git a/Source/Dialogs/SnapSettings.h b/Source/Dialogs/SnapSettings.h index 37022afd49..74679bfc54 100644 --- a/Source/Dialogs/SnapSettings.h +++ b/Source/Dialogs/SnapSettings.h @@ -226,7 +226,7 @@ class SnapSettings : public Component { ToggledButtonOn = 1 }; - MouseInteraction mouseInteraction; + MouseInteraction mouseInteraction = ToggledButtonOff; private: static inline bool isShowing = false; diff --git a/Source/Heavy/DaisyExporter.h b/Source/Heavy/DaisyExporter.h index 1fe079ee39..0de99a9b39 100644 --- a/Source/Heavy/DaisyExporter.h +++ b/Source/Heavy/DaisyExporter.h @@ -137,12 +137,9 @@ class DaisyExporter : public ExporterBase { int patchSize = getValue(patchSizeValue); appTypeProperty->setEnabled(patchSize == 4); - if (patchSize == 1) { - appTypeValue.setValue(0); - } else if (patchSize == 2) { - appTypeValue.setValue(1); - } else if (patchSize == 3) { - appTypeValue.setValue(2); + if(patchSize <= 3) + { + appTypeValue.setValue(patchSize - 1); } if (v.refersToSameSourceAs(targetBoardValue)) { diff --git a/Source/Heavy/ExporterBase.h b/Source/Heavy/ExporterBase.h index fdc69df409..91386e5384 100644 --- a/Source/Heavy/ExporterBase.h +++ b/Source/Heavy/ExporterBase.h @@ -36,7 +36,6 @@ struct ExporterBase : public Component ExportingProgressView* exportingView; - int labelWidth = 180; bool shouldQuit = false; PluginEditor* editor; diff --git a/Source/Heavy/HeavyExportDialog.cpp b/Source/Heavy/HeavyExportDialog.cpp index 94cb39a1a2..381be39468 100644 --- a/Source/Heavy/HeavyExportDialog.cpp +++ b/Source/Heavy/HeavyExportDialog.cpp @@ -60,12 +60,12 @@ class ExporterSettingsPanel : public Component restoreState(); } - ~ExporterSettingsPanel() + ~ExporterSettingsPanel() override { saveState(); } - ValueTree getState() + ValueTree getState() const { ValueTree stateTree("HeavySelect"); stateTree.setProperty("listBox", listBox.getSelectedRow(), nullptr); @@ -161,11 +161,6 @@ class ExporterSettingsPanel : public Component return items.size(); } - StringArray getExports() const - { - return items; - } - void paintListBoxItem(int row, Graphics& g, int width, int height, bool rowIsSelected) override { if (isPositiveAndBelow(row, items.size())) { @@ -180,14 +175,6 @@ class ExporterSettingsPanel : public Component } } - int getBestHeight(int preferredHeight) - { - auto extra = listBox.getOutlineThickness() * 2; - return jmax(listBox.getRowHeight() * 2 + extra, - jmin(listBox.getRowHeight() * getNumRows() + extra, - preferredHeight)); - } - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ExporterSettingsPanel) }; diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index 2716a35590..43b0207bc5 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -396,21 +396,6 @@ void Iolet::createConnection() } } -void Iolet::clearConnections() -{ - auto* cnv = object->cnv; - for (auto* c : getConnections()) { - auto* checkedOutObj = pd::Interface::checkObject(c->outobj->getPointer()); - auto* checkedInObj = pd::Interface::checkObject(c->inobj->getPointer()); - if (checkedInObj && checkedOutObj && cnv->patch.hasConnection(checkedOutObj, c->outIdx, checkedInObj, c->inIdx)) { - // Delete connection from pd if we haven't done that yet - cnv->patch.removeConnection(checkedOutObj, c->outIdx, checkedInObj, c->inIdx, c->getPathState()); - } - - cnv->connections.removeObject(c); - } -} - Array Iolet::getConnections() { Array result; diff --git a/Source/Iolet.h b/Source/Iolet.h index 84464fae72..dff2610005 100644 --- a/Source/Iolet.h +++ b/Source/Iolet.h @@ -36,7 +36,6 @@ class Iolet : public Component void setHidden(bool hidden); - void clearConnections(); Array getConnections(); Rectangle getCanvasBounds(); diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index 2754ddfb15..9dba63461c 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -72,7 +72,7 @@ class PlugData_DocumentWindowButton_macOS : public Button buttonColour = bgColour; } - ~PlugData_DocumentWindowButton_macOS() + ~PlugData_DocumentWindowButton_macOS() override { Desktop::getInstance().removeFocusChangeListener(this); } @@ -82,13 +82,13 @@ class PlugData_DocumentWindowButton_macOS : public Button owner = window; } - void globalFocusChanged(Component* focusedComponent) + void globalFocusChanged(Component* focusedComponent) override { buttonColour = getParentComponent()->hasKeyboardFocus(true) ? bgColour : Colours::lightgrey; repaint(); } - void paintButton(Graphics& g, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) + void paintButton(Graphics& g, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override { auto rect = Justification(Justification::centred).appliedToRectangle(Rectangle(getHeight(), getHeight()), getLocalBounds()).toFloat(); auto reducedRect = rect.reduced(getHeight() * 0.22f); @@ -135,21 +135,21 @@ class PlugData_DocumentWindowButton_macOS : public Button } } - void mouseEnter(MouseEvent const& e) + void mouseEnter(MouseEvent const& e) override { for (auto* button : getAllButtons()) button->repaint(); Button::mouseEnter(e); } - void mouseExit(MouseEvent const& e) + void mouseExit(MouseEvent const& e) override { for (auto* button : getAllButtons()) button->repaint(); Button::mouseExit(e); } - void mouseDrag(MouseEvent const& e) + void mouseDrag(MouseEvent const& e) override { for (auto* button : getAllButtons()) button->repaint(); @@ -188,7 +188,7 @@ class PlugData_DocumentWindowButton_macOS : public Button class PlugData_DocumentWindowButton : public Button { public: - PlugData_DocumentWindowButton(int buttonType) + explicit PlugData_DocumentWindowButton(int buttonType) : Button("") { auto crossThickness = 0.2f; @@ -222,12 +222,14 @@ class PlugData_DocumentWindowButton : public Button { PathStrokeType(30.0f).createStrokedPath(toggledShape, toggledShape); break; } + default: + break; } setName(name); setButtonText(name); } - void paintButton(Graphics& g, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) + void paintButton(Graphics& g, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override { auto circleColour = findColour(PlugDataColour::toolbarHoverColourId); if (shouldDrawButtonAsHighlighted) @@ -571,7 +573,7 @@ Button* PlugDataLook::createTabBarExtrasButton() setTriggeredOnMouseDown(true); } - void moved() + void moved() override { static bool insideMove = false; if (insideMove) @@ -585,7 +587,7 @@ Button* PlugDataLook::createTabBarExtrasButton() } } - void resized() + void resized() override { // Try to force the size to 28x28, while also not allowing any recursion // JUCE gives us very little control over the position/size otherwise... @@ -597,7 +599,7 @@ Button* PlugDataLook::createTabBarExtrasButton() insideResize = false; } - void paint(Graphics& g) + void paint(Graphics& g) override { bool hiddenTabSelected = false; if (auto* tabbar = findParentComponentOfClass()) { @@ -620,11 +622,9 @@ Button* PlugDataLook::createTabBarExtrasButton() g.setColour(findColour(PlugDataColour::tabTextColourId)); g.drawText(getButtonText(), getLocalBounds().reduced(3), Justification::centred); - - return; } - void mouseDown(MouseEvent const& e) + void mouseDown(MouseEvent const& e) override { class HiddenTabMenuItem : public PopupMenu::CustomComponent { @@ -634,7 +634,7 @@ Button* PlugDataLook::createTabBarExtrasButton() int index; TabbedButtonBar& tabbar; - HiddenTabMenuItem(String text, int idx, TabbedButtonBar& buttonBar) + HiddenTabMenuItem(String const& text, int idx, TabbedButtonBar& buttonBar) : tabTitle(text) , index(idx) , tabbar(buttonBar) @@ -655,18 +655,18 @@ Button* PlugDataLook::createTabBarExtrasButton() addChildComponent(closeTabButton); } - void resized() + void resized() override { closeTabButton.setTopLeftPosition(getWidth() - 26, -2); } - void getIdealSize(int& idealWidth, int& idealHeight) + void getIdealSize(int& idealWidth, int& idealHeight) override { idealWidth = 150; idealHeight = 24; } - void mouseDown(MouseEvent const& e) + void mouseDown(MouseEvent const& e) override { if (e.originalComponent == &closeTabButton) return; @@ -675,17 +675,17 @@ Button* PlugDataLook::createTabBarExtrasButton() triggerMenuItem(); } - void mouseEnter(MouseEvent const& e) + void mouseEnter(MouseEvent const& e) override { closeTabButton.setVisible(true); } - void mouseExit(MouseEvent const& e) + void mouseExit(MouseEvent const& e) override { closeTabButton.setVisible(false); } - void paint(Graphics& g) + void paint(Graphics& g) override { bool isActive = tabbar.getCurrentTabIndex() == index; @@ -1458,7 +1458,7 @@ void PlugDataLook::drawAlertBox (Graphics& g, AlertWindow& alert, textLayout.draw (g, alertBounds.toFloat()); } -void PlugDataLook::setDefaultFont(String fontName) +void PlugDataLook::setDefaultFont(String const& fontName) { auto& lnf = dynamic_cast(getDefaultLookAndFeel()); if (fontName.isEmpty() || fontName == "Inter") { diff --git a/Source/LookAndFeel.h b/Source/LookAndFeel.h index 7e5eab7e0c..6c7a020048 100644 --- a/Source/LookAndFeel.h +++ b/Source/LookAndFeel.h @@ -188,7 +188,7 @@ struct PlugDataLook : public LookAndFeel_V4 { void setColours(std::map colours); - static void setDefaultFont(String fontName); + static void setDefaultFont(String const& fontName); static String const defaultThemesXml; diff --git a/Source/Object.h b/Source/Object.h index 4f9ef7c64d..a117465ae2 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -25,9 +25,10 @@ class Object : public Component , public Value::Listener , public ChangeListener , public Timer + , public KeyListener , private TextEditor::Listener { public: - Object(Canvas* parent, String const& name = "", Point position = { 100, 100 }); + explicit Object(Canvas* parent, String const& name = "", Point position = { 100, 100 }); Object(pd::WeakReference object, Canvas* parent); @@ -41,6 +42,8 @@ class Object : public Component void paint(Graphics&) override; void paintOverChildren(Graphics&) override; void resized() override; + + bool keyPressed(KeyPress const& key, Component* component) override; void updateIolets(); @@ -117,13 +120,12 @@ class Object : public Component void openNewObjectEditor(); - bool checkIfHvccCompatible(); + bool checkIfHvccCompatible() const; void setSelected(bool shouldBeSelected); bool selectedFlag = false; bool selectionStateChanged = false; - bool createEditorOnMouseDown = false; bool wasLockedOnMouseDown = false; bool indexShown = false; bool isHvccCompatible = true; diff --git a/Source/ObjectGrid.cpp b/Source/ObjectGrid.cpp index 9d1a26ea7f..de722a78da 100644 --- a/Source/ObjectGrid.cpp +++ b/Source/ObjectGrid.cpp @@ -32,7 +32,7 @@ Array ObjectGrid::getSnappableObjects(Object* draggedObject) Array snappable; auto scaleFactor = std::sqrt(std::abs(cnv->getTransform().getDeterminant())); - auto viewBounds = cnv->viewport.get()->getViewArea() / scaleFactor; + auto viewBounds = cnv->viewport->getViewArea() / scaleFactor; for (auto* object : cnv->objects) { if (draggedObject == object || object->isSelected() || !viewBounds.intersects(object->getBounds())) diff --git a/Source/ObjectGrid.h b/Source/ObjectGrid.h index 55dc2bd8f9..7690bd2c8b 100644 --- a/Source/ObjectGrid.h +++ b/Source/ObjectGrid.h @@ -14,7 +14,7 @@ struct ObjectGrid : public SettingsFileListener { int gridSize = 20; - ObjectGrid(Canvas* cnv); + explicit ObjectGrid(Canvas* cnv); Point performResize(Object* toDrag, Point dragOffset, Rectangle newResizeBounds); Point performMove(Object* toDrag, Point dragOffset); @@ -33,11 +33,11 @@ struct ObjectGrid : public SettingsFileListener { void propertyChanged(String const& name, var const& value) override; - Array getSnappableObjects(Object* draggedObject); + static Array getSnappableObjects(Object* draggedObject); void setIndicator(int idx, Line line, float lineScale); - Line getObjectIndicatorLine(Side side, Rectangle b1, Rectangle b2); + static Line getObjectIndicatorLine(Side side, Rectangle b1, Rectangle b2); static constexpr int objectTolerance = 6; static constexpr int connectionTolerance = 9; diff --git a/Source/Objects/AtomHelper.h b/Source/Objects/AtomHelper.h index 8ef645065a..afb8f7ded8 100644 --- a/Source/Objects/AtomHelper.h +++ b/Source/Objects/AtomHelper.h @@ -463,35 +463,4 @@ class AtomHelper { pd_bind(&atom->a_text.te_pd, canvas_realizedollar(atom->a_glist, atom->a_symfrom)); } } - - /* prepend "-" as necessary to avoid empty strings, so we can - use them in Pd messages. */ - t_symbol* gatom_escapit(t_symbol* s) - { - if (!*s->s_name) - return (pd->generateSymbol("-")); - else if (*s->s_name == '-') { - char shmo[100]; - shmo[0] = '-'; - strncpy(shmo + 1, s->s_name, 99); - shmo[99] = 0; - return (pd->generateSymbol(shmo)); - } else - return (s); - } - - /* undo previous operation: strip leading "-" if found. This is used - both to restore send, etc., names when loading from a file, and to - set them from the properties dialog. In the former case, since before - version 0.52 '$" was aliases to "#", we also bash any "#" characters - to "$". This is unnecessary when reading files saved from 0.52 or later, - and really we should test for that and only bash when necessary, just - in case someone wants to have a "#" in a name. */ - t_symbol* gatom_unescapit(t_symbol* s) - { - if (*s->s_name == '-') - return (pd->generateSymbol(String::fromUTF8(s->s_name + 1))); - else - return (iemgui_raute2dollar(s)); - } }; diff --git a/Source/Objects/BicoeffObject.h b/Source/Objects/BicoeffObject.h index 7ea00e4bcf..68aadb3e03 100644 --- a/Source/Objects/BicoeffObject.h +++ b/Source/Objects/BicoeffObject.h @@ -12,7 +12,7 @@ class BicoeffGraph : public Component { float filterWidth, filterCentre; float filterX1, filterX2; - float lastCentre, lastX1, lastX2, lastGain; + float lastX1, lastX2, lastGain; Object* object; @@ -149,7 +149,6 @@ class BicoeffGraph : public Component { if (!e.mods.isLeftButtonDown()) return; - lastCentre = filterCentre; lastX1 = filterX1; lastX2 = filterX2; lastGain = filterGain; diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 56ced6f47b..566c382294 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -30,12 +30,12 @@ class GemJUCEWindow final : public Component, public Timer setSizeLimits (50, 50, 30000, 30000); } - void resizeStart() + void resizeStart() override { beginResize(); } - void resizeEnd() + void resizeEnd() override { endResize(); } @@ -221,7 +221,7 @@ void gemWinUnsetCurrent() { } // window/context creation&destruction -bool initGemWin(void) +bool initGemWin() { return true; } diff --git a/Source/Objects/IEMHelper.h b/Source/Objects/IEMHelper.h index 774a98c846..64d0fcf213 100644 --- a/Source/Objects/IEMHelper.h +++ b/Source/Objects/IEMHelper.h @@ -463,21 +463,6 @@ class IEMHelper { return ""; } - String getLabelText() const - { - if (auto iemgui = ptr.get()) { - t_symbol const* sym = iemgui->x_lab_unexpanded; - if (sym) { - auto text = String::fromUTF8(sym->s_name); - if (text.isNotEmpty() && text != "empty") { - return text; - } - } - } - - return ""; - } - static Colour convertFromIEMColour(int const color) { uint32 const c = (uint32)(color << 8 | 0xFF); diff --git a/Source/Objects/ImplementationBase.h b/Source/Objects/ImplementationBase.h index cd025f53ca..a198ebe26f 100644 --- a/Source/Objects/ImplementationBase.h +++ b/Source/Objects/ImplementationBase.h @@ -29,7 +29,6 @@ class ImplementationBase { void closeOpenedSubpatchers(); Canvas* getMainCanvas(t_canvas* patchPtr, bool alsoSearchRoot = false) const; - Array getEditors() const; PluginProcessor* pd; pd::WeakReference ptr; @@ -45,7 +44,7 @@ class ObjectImplementationManager : public AsyncUpdater { void updateObjectImplementations(); void clearObjectImplementationsForPatch(t_canvas* patch); - void handleAsyncUpdate(); + void handleAsyncUpdate() override; private: Array getImplementationsForPatch(t_canvas* patch); diff --git a/Source/Objects/KnobObject.h b/Source/Objects/KnobObject.h index 7f51ade0e2..b5e0e71a7b 100644 --- a/Source/Objects/KnobObject.h +++ b/Source/Objects/KnobObject.h @@ -223,22 +223,6 @@ class KnobObject : public ObjectBase { knob.repaint(); } - void setCircular(Slider::SliderStyle style) - { - if (auto knob = ptr.get()) { - knob->x_circular = style == Slider::SliderStyle::RotaryHorizontalVerticalDrag; - } - } - - bool isCircular() - { - if (auto knob = ptr.get()) { - return knob->x_circular; - } - - return false; - } - void update() override { auto currentValue = getValue(); diff --git a/Source/Objects/ListObject.h b/Source/Objects/ListObject.h index b820f38172..3a847285e6 100644 --- a/Source/Objects/ListObject.h +++ b/Source/Objects/ListObject.h @@ -126,10 +126,6 @@ class ListObject final : public ObjectBase { } } - ~ListObject() override - { - } - void resized() override { listLabel.setFont(listLabel.getFont().withHeight(getHeight() - 6)); diff --git a/Source/Objects/LuaObject.h b/Source/Objects/LuaObject.h index 3b3e3935b4..f387684984 100644 --- a/Source/Objects/LuaObject.h +++ b/Source/Objects/LuaObject.h @@ -27,7 +27,7 @@ struct LuaGuiMessage { t_atom data[8]; int size; - LuaGuiMessage() {}; + LuaGuiMessage() = default; LuaGuiMessage(t_symbol* sym, int argc, t_atom* argv) : symbol(sym) diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index 122c1dc988..186eba0d80 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -371,14 +371,6 @@ class MessageObject final : public ObjectBase bool keyPressed(KeyPress const& key, Component* component) override { - if (key == KeyPress::rightKey && editor && !editor->getHighlightedRegion().isEmpty()) { - editor->setCaretPosition(editor->getHighlightedRegion().getEnd()); - return true; - } - if (key == KeyPress::leftKey && editor && !editor->getHighlightedRegion().isEmpty()) { - editor->setCaretPosition(editor->getHighlightedRegion().getStart()); - return true; - } if (key.getKeyCode() == KeyPress::returnKey && editor && key.getModifiers().isShiftDown()) { int caretPosition = editor->getCaretPosition(); auto text = editor->getText(); diff --git a/Source/Objects/MidiObjects.h b/Source/Objects/MidiObjects.h index 9c41d91956..e395feaa61 100644 --- a/Source/Objects/MidiObjects.h +++ b/Source/Objects/MidiObjects.h @@ -57,7 +57,7 @@ class MidiObject final : public TextBase { if (midiInput) { int port = 1; - for (auto input : midiDeviceManager->getInputDevices()) { + for (auto const& input : midiDeviceManager->getInputDevices()) { PopupMenu subMenu; for (int ch = 1; ch < 17; ch++) { int portNumber = ch + (port << 4); @@ -75,7 +75,7 @@ class MidiObject final : public TextBase { } } else { int port = 1; - for (auto output : midiDeviceManager->getOutputDevices()) { + for (auto const& output : midiDeviceManager->getOutputDevices()) { PopupMenu subMenu; for (int ch = 1; ch < 17; ch++) { int portNumber = ch + (port << 4); diff --git a/Source/Objects/PdTildeObject.h b/Source/Objects/PdTildeObject.h index 33a6462c03..c37b2ddaa7 100644 --- a/Source/Objects/PdTildeObject.h +++ b/Source/Objects/PdTildeObject.h @@ -43,11 +43,11 @@ class PdTildeObject final : public TextBase { return; } #else - if (result.isDirectory()) { - pdLocation = result; - } else { - return; - } + if (result.isDirectory()) { + pdLocation = result; + } else { + return; + } #endif if (auto pdTilde = ptr.get()) { auto pdPath = pdLocation.getFullPathName(); diff --git a/Source/Objects/SubpatchObject.h b/Source/Objects/SubpatchObject.h index 8c095450c8..bb3f5daa39 100644 --- a/Source/Objects/SubpatchObject.h +++ b/Source/Objects/SubpatchObject.h @@ -85,10 +85,6 @@ class SubpatchObject final : public TextBase { return subpatch; } - void checkGraphState() - { - } - void valueChanged(Value& v) override { if (v.refersToSameSourceAs(isGraphChild)) { diff --git a/Source/Objects/TextDefineObject.h b/Source/Objects/TextDefineObject.h index 93ba3ed330..a9a3493113 100644 --- a/Source/Objects/TextDefineObject.h +++ b/Source/Objects/TextDefineObject.h @@ -121,6 +121,8 @@ class TextFileObject final : public TextBase { case hash("close"): { textEditor.reset(nullptr); } + default: + break; } } diff --git a/Source/Pd/Instance.h b/Source/Pd/Instance.h index 2d6417f297..1820790c94 100644 --- a/Source/Pd/Instance.h +++ b/Source/Pd/Instance.h @@ -147,7 +147,7 @@ class Instance { : object(ref, instance) , destination(dest) , selector(sel) - , list(atoms) + , list(std::move(atoms)) { } @@ -158,7 +158,7 @@ class Instance { }; public: - Instance(String const& symbol); + explicit Instance(String const& symbol); Instance(Instance const& other) = delete; virtual ~Instance(); @@ -169,12 +169,12 @@ class Instance { void performDSP(float const* inputs, float* outputs); int getBlockSize() const; - void sendNoteOn(int channel, int const pitch, int velocity) const; - void sendControlChange(int channel, int const controller, int value) const; + void sendNoteOn(int channel, int pitch, int velocity) const; + void sendControlChange(int channel, int controller, int value) const; void sendProgramChange(int channel, int value) const; void sendPitchBend(int channel, int value) const; void sendAfterTouch(int channel, int value) const; - void sendPolyAfterTouch(int channel, int const pitch, int value) const; + void sendPolyAfterTouch(int channel, int pitch, int value) const; void sendSysEx(int port, int byte) const; void sendSysRealTime(int port, int byte) const; void sendMidiByte(int port, int byte) const; @@ -331,7 +331,7 @@ class Instance { struct ConsoleHandler : public Timer { Instance* instance; - ConsoleHandler(Instance* parent) + explicit ConsoleHandler(Instance* parent) : instance(parent) , fastStringWidth(Font(14)) { diff --git a/Source/Pd/Interface.h b/Source/Pd/Interface.h index 4e494b2b2d..a9aff4f4a7 100644 --- a/Source/Pd/Interface.h +++ b/Source/Pd/Interface.h @@ -50,7 +50,7 @@ struct Interface { static t_canvas* createCanvas(char const* name, char const* path) { - t_canvas* cnv = (t_canvas*)libpd_openfile(name, path); + auto* cnv = static_cast(libpd_openfile(name, path)); if (cnv) { canvas_vis(cnv, 1.f); canvas_rename(cnv, gensym(name), gensym(path)); @@ -274,7 +274,7 @@ struct Interface { static void redo(t_canvas* cnv) { // Temporary fix... might cause us to miss a loadbang when recreating a canvas - libpd_this_instance()->pd_newest = 0; + libpd_this_instance()->pd_newest = nullptr; if (!cnv->gl_editor) return; @@ -313,7 +313,7 @@ struct Interface { } canvas_setcurrent(cnv); - pd_typedmess((t_pd*)cnv, gensym("duplicate"), 0, NULL); + pd_typedmess((t_pd*)cnv, gensym("duplicate"), 0, nullptr); canvas_unsetcurrent(cnv); } @@ -425,7 +425,7 @@ struct Interface { glist_deselect(cnv, obj); - cnv->gl_editor->e_textedfor = 0; + cnv->gl_editor->e_textedfor = nullptr; cnv->gl_editor->e_textdirty = 0; canvas_editmode(cnv, wasEditMode); @@ -655,7 +655,7 @@ struct Interface { } // If there is an object after ours - t_gobj* oldy_next = obj->g_next ? obj->g_next : NULL; + t_gobj* oldy_next = obj->g_next ? obj->g_next : nullptr; if (to_front) { @@ -802,7 +802,7 @@ struct Interface { the glist to reselect. */ if (cnv->gl_editor->e_textedfor) { // t_gobj *selwas = x->gl_editor->e_selection->sel_what; - libpd_this_instance()->pd_newest = 0; + libpd_this_instance()->pd_newest = nullptr; glist_noselect(cnv); if (libpd_this_instance()->pd_newest) { for (y = cnv->gl_list; y; y = y->g_next) diff --git a/Source/Pd/Library.cpp b/Source/Pd/Library.cpp index cd1a2c01ae..bdaeae3514 100644 --- a/Source/Pd/Library.cpp +++ b/Source/Pd/Library.cpp @@ -99,33 +99,9 @@ Library::Library(pd::Instance* instance) MemoryInputStream instream(BinaryData::Documentation_bin, BinaryData::Documentation_binSize, false); documentationTree = ValueTree::readFromStream(instream); - for (auto object : documentationTree) { - auto categories = object.getChildWithName("categories"); - if (!categories.isValid()) - continue; - - for (auto category : categories) { - allCategories.addIfNotAlreadyThere(category.getProperty("name").toString()); - } - } - watcher.addFolder(ProjectInfo::appDataDir); watcher.addListener(this); - // Paths to search - // First, only search vanilla, then search all documentation - // Lastly, check the deken folder - helpPaths = { - ProjectInfo::appDataDir.getChildFile("Documentation"), - ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("5.reference"), - ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("9.else"), - ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("10.cyclone"), - ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("11.heavylib"), - ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("13.pdlua"), - ProjectInfo::appDataDir.getChildFile("Extra"), - ProjectInfo::appDataDir.getChildFile("Externals") - }; - // This is unfortunately necessary to make Windows LV2 turtle dump work // Let's hope its not harmful MessageManager::callAsync([this, instance = juce::WeakReference(instance)]() { @@ -273,17 +249,12 @@ StringArray Library::getAllObjects() return allObjects; } -StringArray Library::getAllCategories() -{ - return allCategories; -} - void Library::filesystemChanged() { updateLibrary(); } -File Library::findHelpfile(t_gobj* obj, File const& parentPatchFile) const +File Library::findHelpfile(t_gobj* obj, File const& parentPatchFile) { String helpName; String helpDir; diff --git a/Source/Pd/Library.h b/Source/Pd/Library.h index e7a42e2e38..ceff3fe9e3 100644 --- a/Source/Pd/Library.h +++ b/Source/Pd/Library.h @@ -16,7 +16,7 @@ class Instance; class Library : public FileSystemWatcher::Listener { public: - Library(pd::Instance* instance); + explicit Library(pd::Instance* instance); ~Library() override { @@ -33,16 +33,27 @@ class Library : public FileSystemWatcher::Listener { void filesystemChanged() override; - File findHelpfile(t_gobj* obj, File const& parentPatchFile) const; + static File findHelpfile(t_gobj* obj, File const& parentPatchFile); ValueTree getObjectInfo(String const& name); StringArray getAllObjects(); - StringArray getAllCategories(); - - Array helpPaths; std::function appDirChanged; + + // Paths to search for helpfiles + // First, only search vanilla, then search all documentation + // Lastly, check the deken folder + static inline Array const helpPaths = { + ProjectInfo::appDataDir.getChildFile("Documentation"), + ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("5.reference"), + ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("9.else"), + ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("10.cyclone"), + ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("11.heavylib"), + ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("13.pdlua"), + ProjectInfo::appDataDir.getChildFile("Extra"), + ProjectInfo::appDataDir.getChildFile("Externals") + }; static inline Array const defaultPaths = { ProjectInfo::appDataDir.getChildFile("Abstractions").getChildFile("else"), @@ -59,7 +70,6 @@ class Library : public FileSystemWatcher::Listener { private: StringArray allObjects; - StringArray allCategories; std::recursive_mutex libraryLock; diff --git a/Source/Pd/MessageListener.h b/Source/Pd/MessageListener.h index 81a123432f..a9ada112da 100644 --- a/Source/Pd/MessageListener.h +++ b/Source/Pd/MessageListener.h @@ -31,7 +31,7 @@ class MessageDispatcher : private AsyncUpdater { t_atom data[8]; int size; - Message() {}; + Message() = default; Message(void* ptr, t_symbol* sym, int argc, t_atom* argv) : target(ptr) diff --git a/Source/Pd/Patch.cpp b/Source/Pd/Patch.cpp index da556a9116..834ad6e9a0 100644 --- a/Source/Pd/Patch.cpp +++ b/Source/Pd/Patch.cpp @@ -140,15 +140,6 @@ bool Patch::isSubpatch() return false; } -bool Patch::isAbstraction() -{ - if (auto patch = ptr.get()) { - return canvas_isabstraction(patch.get()); - } - - return false; -} - void Patch::updateUndoRedoState() { if (auto patch = ptr.get()) { @@ -243,7 +234,7 @@ t_gobj* Patch::createObject(int x, int y, String const& name) { StringArray tokens; - tokens.addTokens(name, false); + tokens.addTokens(name.replace("\\ ", "__%SPACE%__"), true); // Prevent "/ " from being tokenised PluginEditor::getObjectManager()->formatObject(tokens); @@ -304,13 +295,14 @@ t_gobj* Patch::createObject(int x, int y, String const& name) for (int i = 0; i < tokens.size(); i++) { // check if string is a valid number - auto charptr = tokens[i].getCharPointer(); + auto token = tokens[i].replace("__%SPACE%__", "\\ "); + auto charptr = token.getCharPointer(); auto ptr = charptr; CharacterFunctions::readDoubleValue(ptr); // This will read the number and increment the pointer to be past the number - if (ptr - charptr == tokens[i].getNumBytesAsUTF8()) { - SETFLOAT(argv.data() + i + 2, tokens[i].getFloatValue()); + if (ptr - charptr == token.getNumBytesAsUTF8()) { + SETFLOAT(argv.data() + i + 2, token.getFloatValue()); } else { - SETSYMBOL(argv.data() + i + 2, instance->generateSymbol(tokens[i])); + SETSYMBOL(argv.data() + i + 2, instance->generateSymbol(token)); } } @@ -764,25 +756,5 @@ bool Patch::objectWasDeleted(t_gobj* objectPtr) const return true; } -bool Patch::connectionWasDeleted(t_outconnect* connectionPtr) const -{ - t_outconnect* oc; - t_linetraverser t; - - if (auto patch = ptr.get()) { - // Get connections from pd - linetraverser_start(&t, patch.get()); - - while ((oc = linetraverser_next(&t))) { - if (oc == connectionPtr) { - return false; - } - } - - return true; - } - - return true; -} } // namespace pd diff --git a/Source/Pd/Patch.h b/Source/Pd/Patch.h index cb500feedf..a7c9b40067 100644 --- a/Source/Pd/Patch.h +++ b/Source/Pd/Patch.h @@ -51,7 +51,6 @@ class Patch : public ReferenceCountedObject { void deselectAll(); bool isSubpatch(); - bool isAbstraction(); void setVisible(bool shouldVis); @@ -91,7 +90,6 @@ class Patch : public ReferenceCountedObject { void updateUndoRedoState(); bool objectWasDeleted(t_gobj* ptr) const; - bool connectionWasDeleted(t_outconnect* ptr) const; bool hasConnection(t_object* src, int nout, t_object* sink, int nin); bool canConnect(t_object* src, int nout, t_object* sink, int nin); @@ -140,7 +138,6 @@ class Patch : public ReferenceCountedObject { WeakReference ptr; friend class Instance; - friend class Gui; friend class Object; int undoQueueSize = 0; diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index 8e060ca25d..ec114fa48b 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -81,7 +81,7 @@ typedef struct _plugdata_midi { static void plugdata_noteon(int channel, int pitch, int velocity) { - t_plugdata_midi* x = (t_plugdata_midi*)gensym("#plugdata_midi")->s_thing; + auto* x = (t_plugdata_midi*)gensym("#plugdata_midi")->s_thing; if (x && x->x_hook_noteon) { x->x_hook_noteon(x->x_ptr, channel, pitch, velocity); } @@ -89,7 +89,7 @@ static void plugdata_noteon(int channel, int pitch, int velocity) static void plugdata_controlchange(int channel, int controller, int value) { - t_plugdata_midi* x = (t_plugdata_midi*)gensym("#plugdata_midi")->s_thing; + auto* x = (t_plugdata_midi*)gensym("#plugdata_midi")->s_thing; if (x && x->x_hook_controlchange) { x->x_hook_controlchange(x->x_ptr, channel, controller, value); } @@ -97,7 +97,7 @@ static void plugdata_controlchange(int channel, int controller, int value) static void plugdata_programchange(int channel, int value) { - t_plugdata_midi* x = (t_plugdata_midi*)gensym("#plugdata_midi")->s_thing; + auto* x = (t_plugdata_midi*)gensym("#plugdata_midi")->s_thing; if (x && x->x_hook_programchange) { x->x_hook_programchange(x->x_ptr, channel, value); } @@ -105,7 +105,7 @@ static void plugdata_programchange(int channel, int value) static void plugdata_pitchbend(int channel, int value) { - t_plugdata_midi* x = (t_plugdata_midi*)gensym("#plugdata_midi")->s_thing; + auto* x = (t_plugdata_midi*)gensym("#plugdata_midi")->s_thing; if (x && x->x_hook_pitchbend) { x->x_hook_pitchbend(x->x_ptr, channel, value); } @@ -113,7 +113,7 @@ static void plugdata_pitchbend(int channel, int value) static void plugdata_aftertouch(int channel, int value) { - t_plugdata_midi* x = (t_plugdata_midi*)gensym("#plugdata_midi")->s_thing; + auto* x = (t_plugdata_midi*)gensym("#plugdata_midi")->s_thing; if (x && x->x_hook_aftertouch) { x->x_hook_aftertouch(x->x_ptr, channel, value); } @@ -121,7 +121,7 @@ static void plugdata_aftertouch(int channel, int value) static void plugdata_polyaftertouch(int channel, int pitch, int value) { - t_plugdata_midi* x = (t_plugdata_midi*)gensym("#plugdata_midi")->s_thing; + auto* x = (t_plugdata_midi*)gensym("#plugdata_midi")->s_thing; if (x && x->x_hook_polyaftertouch) { x->x_hook_polyaftertouch(x->x_ptr, channel, pitch, value); } @@ -129,7 +129,7 @@ static void plugdata_polyaftertouch(int channel, int pitch, int value) static void plugdata_midibyte(int port, int byte) { - t_plugdata_midi* x = (t_plugdata_midi*)gensym("#plugdata_midi")->s_thing; + auto* x = (t_plugdata_midi*)gensym("#plugdata_midi")->s_thing; if (x && x->x_hook_midibyte) { x->x_hook_midibyte(x->x_ptr, port, byte); } @@ -1329,7 +1329,7 @@ void* Setup::createReceiver(void* ptr, char const* s, t_plugdata_listhook hook_list, t_plugdata_messagehook hook_message) { - t_plugdata_receiver* x = (t_plugdata_receiver*)pd_new(plugdata_receiver_class); + auto* x = (t_plugdata_receiver*)pd_new(plugdata_receiver_class); if (x) { sys_lock(); x->x_sym = gensym(s); @@ -1357,7 +1357,7 @@ void Setup::initialisePdLuaInstance() void* Setup::createPrintHook(void* ptr, t_plugdata_printhook hook_print) { - t_plugdata_print* x = (t_plugdata_print*)pd_new(plugdata_print_class); + auto* x = (t_plugdata_print*)pd_new(plugdata_print_class); if (x) { sys_lock(); t_symbol* s = gensym("#plugdata_print"); @@ -1379,7 +1379,7 @@ void* Setup::createMIDIHook(void* ptr, t_plugdata_midibytehook hook_midibyte) { - t_plugdata_midi* x = (t_plugdata_midi*)pd_new(plugdata_midi_class); + auto* x = (t_plugdata_midi*)pd_new(plugdata_midi_class); if (x) { sys_lock(); t_symbol* s = gensym("#plugdata_midi"); @@ -1407,7 +1407,6 @@ void Setup::parseArguments(char const** argv, size_t argc, t_namelist** sys_open sys_argparse(argc, argv); // TODO: some args need to be parsed manually still! sys_unlock(); - return; } void Setup::initialiseELSE() @@ -1731,7 +1730,7 @@ void Setup::initialiseELSE() fm_tilde_setup(); } -void Setup::initialiseGem(std::string gemPluginPath) +void Setup::initialiseGem(std::string const& gemPluginPath) { #if ENABLE_GEM Gem_setup(gensym(gemPluginPath.c_str())); diff --git a/Source/Pd/Setup.h b/Source/Pd/Setup.h index 94934bef01..6bdc00b5c9 100644 --- a/Source/Pd/Setup.h +++ b/Source/Pd/Setup.h @@ -36,7 +36,7 @@ struct Setup { static void initialisePdLuaInstance(); static void initialiseELSE(); static void initialiseCyclone(); - static void initialiseGem(std::string gemPluginPath); + static void initialiseGem(std::string const& gemPluginPath); static void* createMIDIHook(void* ptr, t_plugdata_noteonhook hook_noteon, diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 1d9229bab5..5e331952d0 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -601,6 +601,7 @@ void PluginEditor::filesDropped(StringArray const& files, int x, int y) if (file.exists()) { auto position = cnv->getLocalPoint(this, Point(x, y)); auto filePath = file.getFullPathName().replaceCharacter('\\', '/').replace(" ", "\\ "); + auto* object = cnv->objects.add(new Object(cnv, "msg " + filePath, position)); object->hideEditor(); } @@ -1638,7 +1639,7 @@ bool PluginEditor::perform(InvocationInfo const& info) } else { pd->startDSP(); - }; + } return true; } @@ -1774,7 +1775,7 @@ void PluginEditor::showTouchSelectionHelper(bool shouldBeShown) auto touchHelperBounds = getLocalBounds().removeFromBottom(48).withSizeKeepingCentre(192, 48).translated(0, -54); touchSelectionHelper->setBounds(touchHelperBounds); } -}; +} // Finds an object, then centres and selects it, to indicate it's the target of a search action // If you set "openNewTabIfNeeded" to true, it will open a new tab if the object you're trying to highlight is not currently visible diff --git a/Source/PluginMode.h b/Source/PluginMode.h index 519cbfc908..cc2f67a298 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -118,7 +118,7 @@ class PluginMode : public Component { setWidthAndHeight(1.0f); } - ~PluginMode() = default; + ~PluginMode() override = default; void setWidthAndHeight(float scale) { diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index eee1e579a5..8236cd29bc 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1539,7 +1539,7 @@ void PluginProcessor::showTextEditor(unsigned long ptr, Rectangle bounds, S if (result == 2) { lockAudioThread(); - pd_typedmess(reinterpret_cast(ptr), gensym("clear"), 0, NULL); + pd_typedmess(reinterpret_cast(ptr), gensym("clear"), 0, nullptr); unlockAudioThread(); // remove repeating spaces @@ -1598,7 +1598,7 @@ void PluginProcessor::showTextEditor(unsigned long ptr, Rectangle bounds, S lockAudioThread(); // pd_typedmess(reinterpret_cast(ptr), generateSymbol("path"), 1, &fake_path); - pd_typedmess(reinterpret_cast(ptr), generateSymbol("end"), 0, NULL); + pd_typedmess(reinterpret_cast(ptr), generateSymbol("end"), 0, nullptr); unlockAudioThread(); textEditorDialogs[ptr].reset(nullptr); diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index cdb36f94e3..0f38b34fef 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -32,7 +32,7 @@ class PluginProcessor : public AudioProcessor public: PluginProcessor(); - ~PluginProcessor(); + ~PluginProcessor() override; static AudioProcessor::BusesProperties buildBusesProperties(); @@ -66,7 +66,7 @@ class PluginProcessor : public AudioProcessor void getStateInformation(MemoryBlock& destData) override; void setStateInformation(void const* data, int sizeInBytes) override; - void receiveNoteOn(int channel, int pitch, int const velocity) override; + void receiveNoteOn(int channel, int pitch, int velocity) override; void receiveControlChange(int channel, int controller, int value) override; void receiveProgramChange(int channel, int value) override; void receivePitchBend(int channel, int value) override; @@ -162,8 +162,6 @@ class PluginProcessor : public AudioProcessor // Zero means no oversampling std::atomic oversampling = 0; - int lastLeftTab = -1; - int lastRightTab = -1; std::unique_ptr internalSynth; std::atomic enableInternalSynth = false; diff --git a/Source/Sidebar/AutomationPanel.h b/Source/Sidebar/AutomationPanel.h index 0743e838b9..9a900377a3 100644 --- a/Source/Sidebar/AutomationPanel.h +++ b/Source/Sidebar/AutomationPanel.h @@ -296,7 +296,7 @@ class AutomationItem : public ObjectDragAndDrop String getPatchStringName() override { - return String("param object"); + return "param object"; } void valueChanged(Value& v) override @@ -321,7 +321,7 @@ class AutomationItem : public ObjectDragAndDrop } } - bool isEnabled() + bool isEnabled() const { return param->isEnabled(); } @@ -398,7 +398,7 @@ class AutomationItem : public ObjectDragAndDrop class AlphaAnimator : public Timer { public: - AlphaAnimator() { } + AlphaAnimator() = default; void fadeIn(Component* component, double duration) { @@ -414,7 +414,6 @@ class AlphaAnimator : public Timer { void animate(Component* component, double duration, float startAlpha, float endAlpha) { componentToAnimate = component; - animationDuration = duration; animationSteps = static_cast(duration / timerInterval); currentStep = 0; startAlphaValue = startAlpha; @@ -437,7 +436,6 @@ class AlphaAnimator : public Timer { } Component* componentToAnimate = nullptr; - double animationDuration = 0.0; int animationSteps = 0; int currentStep = 0; float startAlphaValue = 0.0f; diff --git a/Source/Sidebar/Console.h b/Source/Sidebar/Console.h index 1a25530f46..46d9126846 100644 --- a/Source/Sidebar/Console.h +++ b/Source/Sidebar/Console.h @@ -168,7 +168,7 @@ class Console : public Component parent.addAndMakeVisible(this); } - void mouseDown(MouseEvent const& e) + void mouseDown(MouseEvent const& e) override { if (!e.mods.isShiftDown() && !e.mods.isCommandDown()) { console.selectedItems.clear(); @@ -191,7 +191,7 @@ class Console : public Component console.repaint(); } - void paint(Graphics& g) + void paint(Graphics& g) override { auto isSelected = console.selectedItems.contains(this); auto showMessages = getValue(console.settingsValues[2]); diff --git a/Source/Sidebar/DocumentationBrowser.h b/Source/Sidebar/DocumentationBrowser.h index f732072cf2..44c63081af 100644 --- a/Source/Sidebar/DocumentationBrowser.h +++ b/Source/Sidebar/DocumentationBrowser.h @@ -125,7 +125,7 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri addAndMakeVisible(fileList); } - ~DocumentationBrowser() + ~DocumentationBrowser() override { stopThread(-1); } @@ -248,7 +248,7 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri if (threadShouldExit() || !directory.exists() || !directory.isDirectory() || directory == versionDataDir || directory == toolchainDir) { - return ValueTree(); + return {}; } ValueTree rootNode("Folder"); @@ -285,7 +285,7 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri } struct { - int compareElements (const ValueTree& first, const ValueTree& second) + static int compareElements (const ValueTree& first, const ValueTree& second) { if(first.getProperty("Icon") == Icons::File && second.getProperty("Icon") == Icons::Folder) { @@ -304,11 +304,6 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri return rootNode; } - bool isSearching() - { - return searchInput.hasKeyboardFocus(false); - } - bool hitTest(int x, int y) override { if (x < 5) diff --git a/Source/Sidebar/Inspector.h b/Source/Sidebar/Inspector.h index da0eb0b89c..4108d34ca6 100644 --- a/Source/Sidebar/Inspector.h +++ b/Source/Sidebar/Inspector.h @@ -16,7 +16,7 @@ class PropertyRedirector : public Value::Listener { baseValue.addListener(this); } - ~PropertyRedirector() + ~PropertyRedirector() override { baseValue.removeListener(this); } @@ -35,7 +35,6 @@ class PropertyRedirector : public Value::Listener { class Inspector : public Component { PropertiesPanel panel; - String title; TextButton resetButton; Array properties; OwnedArray redirectors; @@ -69,16 +68,6 @@ class Inspector : public Component { panel.setContentWidth(getWidth() - 16); } - void setTitle(String const& name) - { - title = name; - } - - String getTitle() - { - return title; - } - static PropertiesPanelProperty* createPanel(int type, String const& name, Value* value, StringArray& options) { switch (type) { diff --git a/Source/Sidebar/PaletteItem.h b/Source/Sidebar/PaletteItem.h index 1a5e68e427..32e6f8df05 100644 --- a/Source/Sidebar/PaletteItem.h +++ b/Source/Sidebar/PaletteItem.h @@ -12,7 +12,7 @@ class ReorderButton; class PaletteItem : public ObjectDragAndDrop { public: PaletteItem(PluginEditor* e, PaletteDraggableList* parent, ValueTree tree); - ~PaletteItem(); + ~PaletteItem() override; void paint(Graphics& g) override; void resized() override; @@ -30,9 +30,7 @@ class PaletteItem : public ObjectDragAndDrop { void deleteItem(); - bool isSubpatchOrAbstraction(String const& patchAsString); - - Image patchToTempImage(String const& patch); + static bool isSubpatchOrAbstraction(String const& patchAsString); std::pair, std::vector> countIolets(String const& patchAsString); diff --git a/Source/Sidebar/Palettes.h b/Source/Sidebar/Palettes.h index 6843c0c1fe..d6ca659901 100644 --- a/Source/Sidebar/Palettes.h +++ b/Source/Sidebar/Palettes.h @@ -837,6 +837,7 @@ class Palettes : public Component { "6 operator FM", "#X obj 0 0 palette/pm6.m~" }, { "signal generator", "#X obj 0 0 palette/sig.m~" }, { "noise osc", "#X obj 0 0 palette/noiseosc.m~" }, + { "gendyn osc", "#X obj 0 0 palette/gendyn.m~" }, } }, { "Filters", { @@ -878,6 +879,7 @@ class Palettes : public Component { "organ", "#X obj 0 0 palette/organ.m~" }, { "vca", "#X obj 0 0 palette/vca.m~" }, { "pluck", "#X obj 0 0 palette/pluck.m~" }, + { "brane", "#X obj 0 0 palette/brane.m~" }, } }, }; diff --git a/Source/Sidebar/Sidebar.cpp b/Source/Sidebar/Sidebar.cpp index ad622e7975..e20ee3b1f4 100644 --- a/Source/Sidebar/Sidebar.cpp +++ b/Source/Sidebar/Sidebar.cpp @@ -354,21 +354,6 @@ void Sidebar::updateExtraSettingsButton() resized(); } -void Sidebar::showParameters() -{ - inspector->loadParameters(lastParameters); - - if (!pinned) { - inspector->setVisible(true); - console->setVisible(false); - browser->setVisible(false); - searchPanel->setVisible(false); - automationPanel->setVisible(false); - } - - updateExtraSettingsButton(); - repaint(); -} void Sidebar::hideParameters() { if (!pinned) { @@ -386,11 +371,6 @@ void Sidebar::hideParameters() repaint(); } -bool Sidebar::isShowingConsole() const -{ - return console->isVisible(); -} - void Sidebar::clearConsole() { console->clear(); diff --git a/Source/Sidebar/Sidebar.h b/Source/Sidebar/Sidebar.h index 55beda8e10..3e409660e5 100644 --- a/Source/Sidebar/Sidebar.h +++ b/Source/Sidebar/Sidebar.h @@ -23,19 +23,19 @@ class Instance; class SidebarSelectorButton : public TextButton { public: - SidebarSelectorButton(String const& icon) + explicit SidebarSelectorButton(String const& icon) : TextButton(icon) { } - void mouseDown(MouseEvent const& e) + void mouseDown(MouseEvent const& e) override { numNotifications = 0; hasWarning = false; TextButton::mouseDown(e); } - void paint(Graphics& g) + void paint(Graphics& g) override { bool active = isMouseOver() || isMouseButtonDown() || getToggleState(); @@ -96,7 +96,6 @@ class Sidebar : public Component void mouseExit(MouseEvent const& e) override; void showParameters(String const& name, Array& params); - void showParameters(); void hideParameters(); bool isShowingBrowser(); @@ -105,7 +104,6 @@ class Sidebar : public Component void showPanel(int panelToShow); - bool isShowingConsole() const; void showSidebar(bool show); void pinSidebar(bool pin); diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 10aa93bc91..80420314a0 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -30,7 +30,7 @@ class OversampleSelector : public TextButton { std::function onChange = [](int) {}; std::function onClose = []() {}; - OversampleSettingsPopup(int currentSelection) + explicit OversampleSettingsPopup(int currentSelection) { title.setText("Oversampling factor", dontSendNotification); title.setFont(Fonts::getBoldFont().withHeight(14.0f)); @@ -67,7 +67,7 @@ class OversampleSelector : public TextButton { setSize(180, 50); } - ~OversampleSettingsPopup() + ~OversampleSettingsPopup() override { onClose(); } @@ -96,7 +96,7 @@ class OversampleSelector : public TextButton { }; public: - OversampleSelector(PluginProcessor* pd) + explicit OversampleSelector(PluginProcessor* pd) { onClick = [this, pd]() { auto selection = log2(getButtonText().upToLastOccurrenceOf("x", false, false).getIntValue()); @@ -455,7 +455,7 @@ class CPUMeterPopup : public Component { setSize(212, 177); } - ~CPUMeterPopup() + ~CPUMeterPopup() override { onClose(); } diff --git a/Source/Tabbar/ResizableTabbedComponent.cpp b/Source/Tabbar/ResizableTabbedComponent.cpp index a0d311489e..0ca2d9372a 100644 --- a/Source/Tabbar/ResizableTabbedComponent.cpp +++ b/Source/Tabbar/ResizableTabbedComponent.cpp @@ -174,11 +174,11 @@ void ResizableTabbedComponent::createNewSplit(DropZones activeZone, Canvas* canv void ResizableTabbedComponent::moveTabToNewSplit(SourceDetails const& dragSourceDetails) { // get the dragging tab - auto* sourceTabButton = static_cast(dragSourceDetails.sourceComponent.get()); + auto* sourceTabButton = dynamic_cast(dragSourceDetails.sourceComponent.get()); int sourceTabIndex = sourceTabButton->getIndex(); auto sourceTabContent = sourceTabButton->getTabComponent(); int sourceNumTabs = sourceTabContent->getNumTabs(); - bool shouldDelete = (sourceNumTabs - 1) == 0 ? true : false; + bool shouldDelete = (sourceNumTabs - 1) == 0; bool dropZoneCentre = (activeZone == DropZones::Centre) ? true : false; if (dropZoneCentre) { @@ -353,10 +353,10 @@ void ResizableTabbedComponent::paintOverChildren(Graphics& g) highlight = getLocalBounds(); } break; - }; + } g.fillRect(highlight); - }; + } } void ResizableTabbedComponent::itemDragEnter(SourceDetails const& dragSourceDetails) diff --git a/Source/Tabbar/ResizableTabbedComponent.h b/Source/Tabbar/ResizableTabbedComponent.h index ae989494dd..1600434d0d 100644 --- a/Source/Tabbar/ResizableTabbedComponent.h +++ b/Source/Tabbar/ResizableTabbedComponent.h @@ -64,7 +64,7 @@ class ResizableTabbedComponent : public Component void moveTabToNewSplit(SourceDetails const& source); - String getZoneName(int zone); + static String getZoneName(int zone); int splitWidth = 0; diff --git a/Source/Tabbar/SplitViewResizer.cpp b/Source/Tabbar/SplitViewResizer.cpp index 5b1e989783..30413e4a06 100644 --- a/Source/Tabbar/SplitViewResizer.cpp +++ b/Source/Tabbar/SplitViewResizer.cpp @@ -82,14 +82,6 @@ void SplitViewResizer::paint(Graphics& g) #endif } -void SplitViewResizer::mouseDown(MouseEvent const& e) -{ - dragPositionX = e.position.x; - dragPositionY = e.getPosition().getY(); - - leftBounds = getParentComponent()->getBounds().getX(); -} - bool SplitViewResizer::setResizerPosition(float newPosition, bool checkLeft) { auto left = splits[0]->resizerLeft; @@ -107,7 +99,7 @@ bool SplitViewResizer::setResizerPosition(float newPosition, bool checkLeft) // if we hit a resizer that is null, this means we are at an edge // check all to left - if ((newPosition / getWidth()) - leftPos < (minimumWidth / getWidth()) && checkLeft == true) { + if ((newPosition / getWidth()) - leftPos < (minimumWidth / getWidth()) && checkLeft) { if (splits[0]->resizerLeft) { hitEdge = splits[0]->resizerLeft->setResizerPosition(newPosition - minimumWidth); diff --git a/Source/Tabbar/SplitViewResizer.h b/Source/Tabbar/SplitViewResizer.h index 51d93c8307..8e68599d75 100644 --- a/Source/Tabbar/SplitViewResizer.h +++ b/Source/Tabbar/SplitViewResizer.h @@ -18,8 +18,6 @@ class SplitViewResizer : public Component { SplitViewResizer(ResizableTabbedComponent* left, ResizableTabbedComponent* right, Split::SplitMode mode = Split::SplitMode::None, int flipped = -1); - void setSplitMode(Split::SplitMode mode); - bool setResizerPosition(float newPosition, bool checkLeft = true); MouseCursor getMouseCursor() override; @@ -35,20 +33,13 @@ class SplitViewResizer : public Component { ResizableTabbedComponent* splits[2] = { nullptr }; private: - void mouseDown(MouseEvent const& e) override; void mouseDrag(MouseEvent const& e) override; float minimumWidth = 100.0f; - float minimumHeight = 100.0f; bool hitEdge = false; - int leftBounds = 0; - float dragPositionX = 0; - int dragPositionY = 0; - bool draggingSplitview = false; - RateReducer rateReducer = RateReducer(60); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SplitViewResizer) diff --git a/Source/Tabbar/TabBarButtonComponent.cpp b/Source/Tabbar/TabBarButtonComponent.cpp index 3afa8f8d52..f07ecb64f1 100644 --- a/Source/Tabbar/TabBarButtonComponent.cpp +++ b/Source/Tabbar/TabBarButtonComponent.cpp @@ -159,10 +159,6 @@ void TabBarButtonComponent::mouseExit(MouseEvent const& e) repaint(); } -void TabBarButtonComponent::tabTextChanged(String const& newCurrentTabName) -{ -} - void TabBarButtonComponent::lookAndFeelChanged() { closeTabButton->setColour(TextButton::textColourOffId, findColour(PlugDataColour::toolbarTextColourId)); @@ -205,7 +201,7 @@ ScaledImage TabBarButtonComponent::generateTabBarButtonImage() g.drawRect(bounds.toFloat(), 1.0f); #endif - return ScaledImage(image, scale); + return {image, scale}; } void TabBarButtonComponent::mouseDown(MouseEvent const& e) @@ -264,7 +260,7 @@ void TabBarButtonComponent::mouseDown(MouseEvent const& e) newCanvas->getTabbar()->setCurrentTabIndex(newCanvas->getTabIndex()); }); } - }; + } tabMenu.addSubMenu("Parent patches", parentPatchMenu, parentPatchMenu.getNumItems()); diff --git a/Source/Tabbar/TabBarButtonComponent.h b/Source/Tabbar/TabBarButtonComponent.h index e531887805..47ec9ab19f 100644 --- a/Source/Tabbar/TabBarButtonComponent.h +++ b/Source/Tabbar/TabBarButtonComponent.h @@ -16,7 +16,7 @@ class TabBarButtonComponent : public TabBarButton public: TabBarButtonComponent(TabComponent* tabComponent, String const& name, TabbedButtonBar& bar); - ~TabBarButtonComponent(); + ~TabBarButtonComponent() override; TabComponent* getTabComponent(); @@ -36,8 +36,6 @@ class TabBarButtonComponent : public TabBarButton void setTabText(String const& text); - void tabTextChanged(String const& newCurrentTabName); - void setFocusForTabSplit(); void drawTabButton(Graphics& g, Rectangle customBounds = Rectangle()); diff --git a/Source/Tabbar/Tabbar.cpp b/Source/Tabbar/Tabbar.cpp index d3780d0f65..3da4ef7798 100644 --- a/Source/Tabbar/Tabbar.cpp +++ b/Source/Tabbar/Tabbar.cpp @@ -167,7 +167,7 @@ class WelcomePanel : public Component { class ButtonBar::GhostTab : public Component { public: - GhostTab(PlugDataLook& lnfRef) + explicit GhostTab(PlugDataLook& lnfRef) : lnf(lnfRef) { } @@ -536,11 +536,6 @@ void TabComponent::openProject() editor->openProject(); } -void TabComponent::onTabMoved() -{ - editor->pd->savePatchTabPositions(); -} - void TabComponent::onTabChange(int tabIndex) { editor->updateCommandStatus(); @@ -552,7 +547,6 @@ void TabComponent::onTabChange(int tabIndex) welcomePanel->show(); } else { tabs->setVisible(true); - // static_cast(getTabbedButtonBar().getTabButton(newCurrentTabIndex))->tabTextChanged(newCurrentTabName); welcomePanel->hide(); setTabBarDepth(30); // we need to update the dropzones, because no resize will be automatically triggered when there is a tab added from welcome screen diff --git a/Source/Tabbar/Tabbar.h b/Source/Tabbar/Tabbar.h index ab57820c27..c6d867c400 100644 --- a/Source/Tabbar/Tabbar.h +++ b/Source/Tabbar/Tabbar.h @@ -64,7 +64,6 @@ class TabComponent : public Component TabComponent(PluginEditor* editor); ~TabComponent() override; - void onTabMoved(); void onTabChange(int tabIndex); void newTab(); void addTab(String const& tabName, Component* contentComponent, int insertIndex); @@ -108,11 +107,7 @@ class TabComponent : public Component Image tabSnapshot; ScaledImage tabSnapshotScaled; - Rectangle tabSnapshotBounds; - Rectangle currentTabBounds; - private: - Point scalePos; int tabDepth = 30; diff --git a/Source/Utility/AudioMidiFifo.h b/Source/Utility/AudioMidiFifo.h index 30fad9c9c8..e80b384f5a 100644 --- a/Source/Utility/AudioMidiFifo.h +++ b/Source/Utility/AudioMidiFifo.h @@ -33,21 +33,6 @@ class AudioMidiFifo { int getNumSamplesAvailable() { return fifo.getNumReady(); } int getNumSamplesFree() { return fifo.getFreeSpace(); } - void writeSilence(int numSamples) - { - jassert(getNumSamplesFree() >= numSamples); - - int start1, size1, start2, size2; - fifo.prepareToWrite(numSamples, start1, size1, start2, size2); - - if (size1 > 0) - audioBuffer.clear(start1, size1); - if (size2 > 0) - audioBuffer.clear(start2, size2); - - fifo.finishedWrite(size1 + size2); - } - void writeAudioAndMidi(dsp::AudioBlock const& audioSrc, MidiBuffer const& midiSrc) { jassert(getNumSamplesFree() >= audioSrc.getNumSamples()); diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index b761a6cf5d..c242c85e15 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -72,15 +72,21 @@ static inline String convertURLtoUTF8(String const& input) String output; for (int i = 0; i < tokens.size(); ++i) { - String token = tokens[i]; + String const& token = tokens[i]; if (token.startsWithChar('#')) { // Extract the hex value and convert it to a character - auto hexString = token.substring(1); - int hexValue; - sscanf(hexString.toRawUTF8(), "%x", &hexValue); - output += String::charToString(static_cast(hexValue)); - output += token.substring(3); + auto hexString = token.substring(1) + "\0"; + char *endptr; + int hexValue = strtoul(hexString.toRawUTF8(), &endptr, 16); + if(*endptr == '\0') { + output += String::charToString(static_cast(hexValue)); + output += token.substring(3); + } + else { + jassertfalse; + output += token; + } } else { output += token; } diff --git a/Source/Utility/ModifierKeyListener.h b/Source/Utility/ModifierKeyListener.h index 49d000d506..4684ec7d70 100644 --- a/Source/Utility/ModifierKeyListener.h +++ b/Source/Utility/ModifierKeyListener.h @@ -107,7 +107,7 @@ class ModifierKeyBroadcaster : private Timer { void callListeners(Modifier mod, bool down) { - for (auto listener : listeners) { + for (auto const& listener : listeners) { if (!listener) continue; switch (mod) { diff --git a/Source/Utility/OSUtils.mm b/Source/Utility/OSUtils.mm index 218e847ac5..b3e2238e74 100644 --- a/Source/Utility/OSUtils.mm +++ b/Source/Utility/OSUtils.mm @@ -27,7 +27,7 @@ int getStyleMask(bool nativeTitlebar) { void OSUtils::enableInsetTitlebarButtons(void* nativeHandle, bool enable) { - NSView* view = static_cast(nativeHandle); + auto* view = static_cast(nativeHandle); if(!view) return; @@ -49,7 +49,7 @@ int getStyleMask(bool nativeTitlebar) { void OSUtils::HideTitlebarButtons(void* view, bool hideMinimiseButton, bool hideMaximiseButton, bool hideCloseButton) { - NSView* nsView = (NSView*)view; + auto* nsView = (NSView*)view; NSWindow* nsWindow = [nsView window]; NSButton *minimizeButton = [nsWindow standardWindowButton:NSWindowMiniaturizeButton]; NSButton *maximizeButton = [nsWindow standardWindowButton:NSWindowZoomButton]; @@ -66,7 +66,7 @@ int getStyleMask(bool nativeTitlebar) { OSUtils::KeyboardLayout OSUtils::getKeyboardLayout() { TISInputSourceRef source = TISCopyCurrentKeyboardInputSource(); - NSString *s = (__bridge NSString *)(TISGetInputSourceProperty(source, kTISPropertyInputSourceID)); + auto *s = (__bridge NSString *)(TISGetInputSourceProperty(source, kTISPropertyInputSourceID)); auto const* locale = [[s substringWithRange:NSMakeRange(20, [s length]-20)] UTF8String]; if(!strcmp(locale, "Belgian") || !strcmp(locale, "French") || !strcmp(locale, "ABC-AZERTY")) { diff --git a/Source/Utility/ObjectThemeManager.h b/Source/Utility/ObjectThemeManager.h index b3749499e3..6ec7ad3146 100644 --- a/Source/Utility/ObjectThemeManager.h +++ b/Source/Utility/ObjectThemeManager.h @@ -8,7 +8,7 @@ class ObjectThemeManager : public Component { public: - ObjectThemeManager() { } + ObjectThemeManager() = default; void lookAndFeelChanged() override { diff --git a/Source/Utility/StackShadow.h b/Source/Utility/StackShadow.h index 58828ef8b3..0a75f8ad90 100644 --- a/Source/Utility/StackShadow.h +++ b/Source/Utility/StackShadow.h @@ -12,9 +12,9 @@ struct StackShadow : public juce::DeletedAtShutdown { StackShadow(); - ~StackShadow(); + ~StackShadow() override; - static void renderDropShadow(juce::Graphics& g, juce::Path const& path, juce::Colour color, int const radius = 1, juce::Point const offset = { 0, 0 }, int spread = 0); + static void renderDropShadow(juce::Graphics& g, juce::Path const& path, juce::Colour color, int radius = 1, juce::Point offset = { 0, 0 }, int spread = 0); melatonin::DropShadow* dropShadow; diff --git a/Source/Utility/ValueTreeNodeBranchLine.h b/Source/Utility/ValueTreeNodeBranchLine.h index e2d10adc14..f2ce59773d 100644 --- a/Source/Utility/ValueTreeNodeBranchLine.h +++ b/Source/Utility/ValueTreeNodeBranchLine.h @@ -39,7 +39,7 @@ class ValueTreeNodeBranchLine : public Component, public SettableTooltipClient void mouseMove(const MouseEvent& e) override { - if (isHover != true) { + if (!isHover) { isHover = true; repaint(); } diff --git a/Source/Utility/ValueTreeViewer.h b/Source/Utility/ValueTreeViewer.h index 1994db87d9..aa21b7c795 100644 --- a/Source/Utility/ValueTreeViewer.h +++ b/Source/Utility/ValueTreeViewer.h @@ -17,7 +17,7 @@ struct ValueTreeOwnerView : public Component class ValueTreeNodeComponent : public Component { public: - ValueTreeNodeComponent(const ValueTree& node, ValueTreeNodeComponent* parentNode, String prepend = String()) : valueTreeNode(node), parent(parentNode) + ValueTreeNodeComponent(const ValueTree& node, ValueTreeNodeComponent* parentNode, String const& prepend = String()) : valueTreeNode(node), parent(parentNode) { nodeBranchLine = std::make_unique(this); addAndMakeVisible(nodeBranchLine.get()); From 249289f1a10a49bff2a0865315e4b3f737f8fd5c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 01:41:21 +0100 Subject: [PATCH 0243/1030] Updated JUCE --- Libraries/JUCE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index ab6ce865b0..b7c04b4c34 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit ab6ce865b017c831f36f15377e42ce16513d5c39 +Subproject commit b7c04b4c34d440f8a4226a888b1cfba89ee5fc57 From e6c531d3b27228d70e8355a2733671a981ef3e77 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 01:49:04 +0100 Subject: [PATCH 0244/1030] Fixed crash when trying to resize an object while being created --- Source/ObjectGrid.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/ObjectGrid.cpp b/Source/ObjectGrid.cpp index de722a78da..ed9f5aa37e 100644 --- a/Source/ObjectGrid.cpp +++ b/Source/ObjectGrid.cpp @@ -224,7 +224,11 @@ Point ObjectGrid::performResize(Object* toDrag, Point dragOffset, Rect // Returns non-zero if the object has a fixed ratio - auto ratio = toDrag->getConstrainer()->getFixedAspectRatio(); + auto ratio = 0.0; + if(auto* constrainer = toDrag->getConstrainer()) + { + ratio = constrainer->getFixedAspectRatio(); + } auto desiredBounds = newResizeBounds.reduced(Object::margin); auto actualBounds = toDrag->getBounds().reduced(Object::margin); From 96f79210555c91a59bae6e5f23e1802c7fc0ed8c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 02:35:53 +0100 Subject: [PATCH 0245/1030] Fixed message box crash --- Source/Objects/MessageObject.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index 186eba0d80..4041237a79 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -265,6 +265,8 @@ class MessageObject final : public ObjectBase std::unique_ptr outgoingEditor; std::swap(outgoingEditor, editor); + cnv->hideSuggestions(); + auto newText = outgoingEditor->getText(); newText = TextObjectHelper::fixNewlines(newText); @@ -272,7 +274,7 @@ class MessageObject final : public ObjectBase if (objectText != newText) { objectText = newText; } - + outgoingEditor.reset(); object->updateBounds(); // Recalculate bounds From 5861bb76a4b3cb6426fec1ba34ba84a8979f8d3a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 02:48:20 +0100 Subject: [PATCH 0246/1030] Fixed some missing runner dependencies --- .github/workflows/cmake.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index d35855d7c8..9f6f910b27 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -23,7 +23,7 @@ jobs: key: macos - name: Create Build Environment - run: cmake -E make_directory ${{github.workspace}}/build + run: cmake -E make_directory ${{github.workspace}}/build - name: Configure CMake working-directory: ${{github.workspace}}/build @@ -42,7 +42,7 @@ jobs: - name: Import Code-Signing Certificates uses: figleafteam/import-codesign-certs@v2 if: ${{ steps.secret-check.outputs.available == 'true' }} - with: + with: p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }} p12-password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }} @@ -87,7 +87,7 @@ jobs: fetch-depth: 0 - name: Create Build Environment - run: cmake -E make_directory ${{github.workspace}}/build + run: cmake -E make_directory ${{github.workspace}}/build - name: Configure CMake shell: bash @@ -215,7 +215,7 @@ jobs: steps: - name: Install Dependencies (dnf) if: ${{ matrix.pacman == 'dnf' }} - run: dnf install -y git cmake alsa-lib-devel libXinerama-devel freetype-devel curl libcurl-devel wget bzip2 gcc-c++ libXi-devel libXcomposite-devel freeglut-devel libXrandr-devel libXcursor-devel xz ccache python python3-pip + run: dnf install -y git cmake alsa-lib-devel libXinerama-devel freetype-devel curl libcurl-devel wget bzip2 gcc-c++ libXi-devel libXcomposite-devel freeglut-devel libXrandr-devel libXcursor-devel xz ccache python python3-pip jack-audio-connection-kit-devel - name: Install Dependencies (apt) if: ${{ matrix.pacman == 'apt' }} @@ -227,7 +227,7 @@ jobs: - name: Install Dependencies (pacman) if: ${{ matrix.pacman == 'pacman' }} - run: pacman -Sy && pacman -S --noconfirm cmake wget bzip2 git alsa-lib freetype2 libx11 libxcursor libxi libxext libxinerama libxrandr libxrender webkit2gtk cmake make gcc pkgconf python python-pip curl ccache freeglut mesa glfw-x11 glew && pacman --noconfirm -Syu + run: pacman -Sy && pacman -S --noconfirm cmake wget bzip2 git alsa-lib freetype2 libx11 libxcursor libxi libxext libxinerama libxrandr libxrender webkit2gtk cmake make gcc pkgconf python python-pip curl ccache freeglut mesa glfw-x11 glew jack2 && pacman --noconfirm -Syu - uses: actions/checkout@v3 with: @@ -274,4 +274,4 @@ jobs: with: prerelease: true draft: true - files: plugdata-${{ matrix.name }} \ No newline at end of file + files: plugdata-${{ matrix.name }} From 00f8d78b9808f2c2abeeac00ec3dc054c3283527 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 03:08:28 +0100 Subject: [PATCH 0247/1030] Fixed Gem plugins for macOS --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index f8df4977e7..d86717d402 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit f8df4977e729c9417c2aa029721af25058629c15 +Subproject commit d86717d40238baff93c54561b625fd83e078078e From 5015a811cbfa44f383289c2c7ba4955d31ed9e7c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 03:09:04 +0100 Subject: [PATCH 0248/1030] Increment version suffix --- Source/Utility/Config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index c242c85e15..f43f56b7ec 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -38,7 +38,7 @@ struct ProjectInfo { static inline File const appDataDir = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory).getChildFile("plugdata"); - static inline String const versionSuffix = "-5"; + static inline String const versionSuffix = "-6"; static inline File const versionDataDir = appDataDir.getChildFile("Versions").getChildFile(ProjectInfo::versionString + versionSuffix); }; From b6d35ec26e24fe4f9d8255a4e743ffd67f033c5e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 03:12:39 +0100 Subject: [PATCH 0249/1030] Windows compilation fixes --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index d86717d402..425d297c87 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit d86717d40238baff93c54561b625fd83e078078e +Subproject commit 425d297c871f8a33cd1029788f1a52a6a62da778 From 92fcaa84ad539fa258a228f77ee95337afac2fe6 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 03:55:32 +0100 Subject: [PATCH 0250/1030] Allow listbox to drag floats if shift is down --- Source/Components/DraggableNumber.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index d1ab2f5b23..fdc5e1e9a2 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -452,6 +452,7 @@ class DraggableNumber : public Label struct DraggableListNumber : public DraggableNumber { int numberStartIdx = 0; int numberEndIdx = 0; + int mouseDownX; bool targetFound = false; @@ -473,6 +474,7 @@ struct DraggableListNumber : public DraggableNumber { numberStartIdx = numberStart; numberEndIdx = numberEnd; dragValue = numberValue; + mouseDownX = e.x; targetFound = numberStart != -1; if (targetFound) { @@ -490,8 +492,6 @@ struct DraggableListNumber : public DraggableNumber { if (isBeingEdited() || !targetFound) return; - updateListHoverPosition(e.x); - // Hide cursor and set unbounded mouse movement setMouseCursor(MouseCursor::NoCursor); updateMouseCursor(); @@ -499,10 +499,10 @@ struct DraggableListNumber : public DraggableNumber { auto mouseSource = Desktop::getInstance().getMainMouseSource(); mouseSource.enableUnboundedMouseMovement(true, true); - double const increment = 1.; double const deltaY = (e.y - e.mouseDownPosition.y) * 0.7; - - double newValue = dragValue + std::floor(increment * -deltaY); + double const increment = e.mods.isShiftDown() ? (0.01 * std::floor(-deltaY)) : std::floor(-deltaY); + + double newValue = dragValue + increment; newValue = limitValue(newValue); @@ -516,6 +516,7 @@ struct DraggableListNumber : public DraggableNumber { // In case the length of the number changes if (length != replacement.length()) { numberEndIdx = replacement.length() + numberStartIdx; + updateListHoverPosition(mouseDownX); } setText(newText, dontSendNotification); From 656ff61ba9b6c3d3311a910eb2247de6e259398f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 04:00:25 +0100 Subject: [PATCH 0251/1030] Listbox dragging fix --- Source/Components/DraggableNumber.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index fdc5e1e9a2..b5decebe3e 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -452,7 +452,6 @@ class DraggableNumber : public Label struct DraggableListNumber : public DraggableNumber { int numberStartIdx = 0; int numberEndIdx = 0; - int mouseDownX; bool targetFound = false; @@ -474,7 +473,6 @@ struct DraggableListNumber : public DraggableNumber { numberStartIdx = numberStart; numberEndIdx = numberEnd; dragValue = numberValue; - mouseDownX = e.x; targetFound = numberStart != -1; if (targetFound) { @@ -491,7 +489,7 @@ struct DraggableListNumber : public DraggableNumber { { if (isBeingEdited() || !targetFound) return; - + // Hide cursor and set unbounded mouse movement setMouseCursor(MouseCursor::NoCursor); updateMouseCursor(); @@ -516,11 +514,12 @@ struct DraggableListNumber : public DraggableNumber { // In case the length of the number changes if (length != replacement.length()) { numberEndIdx = replacement.length() + numberStartIdx; - updateListHoverPosition(mouseDownX); } setText(newText, dontSendNotification); onValueChange(0); + + updateListHoverPosition(e.getMouseDownX()); } void mouseUp(MouseEvent const& e) override From 896882e3640ff4543afa2c907aa33d7843621071 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 04:04:51 +0100 Subject: [PATCH 0252/1030] Another listbox fix --- Source/Components/DraggableNumber.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index b5decebe3e..7c81d5a314 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -579,7 +579,7 @@ struct DraggableListNumber : public DraggableNumber { auto const textArea = getBorderSize().subtractedFrom(getBounds()); GlyphArrangement glyphs; - glyphs.addFittedText(getFont(), getText(), textArea.getX(), 0., textArea.getWidth(), textArea.getHeight(), Justification::centredLeft, 1, getMinimumHorizontalScale()); + glyphs.addFittedText(getFont(), getText(), textArea.getX(), 0., 99999, textArea.getHeight(), Justification::centredLeft, 1, getMinimumHorizontalScale()); auto text = getText(); // Loop to find start of item From 68ffa7f6e9d711410017f4b636580ae5c6ccc634 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 13:24:16 +0100 Subject: [PATCH 0253/1030] Fixed sfizz linker warning --- Libraries/pd-else | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/pd-else b/Libraries/pd-else index 21d3f396ba..f9a4014442 160000 --- a/Libraries/pd-else +++ b/Libraries/pd-else @@ -1 +1 @@ -Subproject commit 21d3f396ba8bb79ae844645a24dfbca289562513 +Subproject commit f9a401444212d1c452c9cbb2c6bb6aac63ea1e63 From dab3603b2e7fb2477d767d6e91a8974b19af7724 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 13:54:30 +0100 Subject: [PATCH 0254/1030] Trying to fix ubuntu compilation --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 425d297c87..0238e1517c 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 425d297c871f8a33cd1029788f1a52a6a62da778 +Subproject commit 0238e1517c4228977749aa2d79a9210df71a63d8 From 0908b8ddb4991c520f350ab81a40a3910297ba7f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 15:11:58 +0100 Subject: [PATCH 0255/1030] Fixed links in help panel opening multiple times --- Source/Utility/MarkupDisplay.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Utility/MarkupDisplay.h b/Source/Utility/MarkupDisplay.h index 69ea277b60..bc4b137cd9 100644 --- a/Source/Utility/MarkupDisplay.h +++ b/Source/Utility/MarkupDisplay.h @@ -369,6 +369,8 @@ class Block : public Component { void updateLinkBounds(TextLayout& layout) { + linkBounds.clear(); + // Look for clickable links for (auto& [link, start, end] : links) { int offset = 0; @@ -383,11 +385,11 @@ class Block : public Component { auto lineBounds = Rectangle(glyph.width, 14).withPosition((glyph.anchor + line.lineOrigin)); currentLinkBounds = linkBounds.isEmpty() ? lineBounds : currentLinkBounds.getUnion(lineBounds); } - - linkBounds.add({link, currentLinkBounds.translated(0, -11)}); offset += run->glyphs.size(); } } + + linkBounds.add({link, currentLinkBounds.translated(0, -11)}); } } From 1d80c6a68686f3750f2ff94ec2691895e34761f4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 15:12:26 +0100 Subject: [PATCH 0256/1030] More cleaning up --- Source/Canvas.cpp | 2 +- Source/Components/TouchSelectionHelper.h | 1 + Source/Connection.cpp | 1 - Source/Connection.h | 1 - Source/Dialogs/ObjectBrowserDialog.h | 1 - Source/Heavy/ExportingProgressView.h | 1 - Source/Object.cpp | 5 +++++ Source/Object.h | 2 ++ Source/Pd/Patch.h | 2 +- Source/PluginEditor.cpp | 14 ++++++++------ Source/PluginEditor.h | 4 +--- Source/Utility/SettingsFile.h | 1 - 12 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 825adc7120..b228d0ac98 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -823,7 +823,7 @@ void Canvas::updateSidebarSelection() if (!allParameters.isEmpty() || editor->sidebar->isPinned()) { String objectName = "(multiple)"; if (lassoSelection.size() == 1 && lassoSelection.getFirst()) { - objectName = lassoSelection.getFirst()->gui->getType(); + objectName = lassoSelection.getFirst()->getType(); } editor->sidebar->showParameters(objectName, allParameters); diff --git a/Source/Components/TouchSelectionHelper.h b/Source/Components/TouchSelectionHelper.h index f7f6e31a89..d4c594f04b 100644 --- a/Source/Components/TouchSelectionHelper.h +++ b/Source/Components/TouchSelectionHelper.h @@ -10,6 +10,7 @@ #include "Buttons.h" #include "PluginEditor.h" +#include "Objects/ObjectBase.h" class TouchSelectionHelper : public Component { diff --git a/Source/Connection.cpp b/Source/Connection.cpp index a88f1fbdd6..95fe6a1c42 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -75,7 +75,6 @@ Connection::Connection(Canvas* parent, Iolet* s, Iolet* e, t_outconnect* oc) cnv->addAndMakeVisible(this); setAlwaysOnTop(true); - // Update position (TODO: don't invoke virtual functions from constructor!) componentMovedOrResized(*outlet, true, true); componentMovedOrResized(*inlet, true, true); diff --git a/Source/Connection.h b/Source/Connection.h index e1e1545d59..c3dc5355f1 100644 --- a/Source/Connection.h +++ b/Source/Connection.h @@ -164,7 +164,6 @@ class Connection : public Component JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Connection) }; -// TODO: hide behind Connection interface to reduce includes! class ConnectionBeingCreated : public Component { SafePointer iolet; Component* cnv; diff --git a/Source/Dialogs/ObjectBrowserDialog.h b/Source/Dialogs/ObjectBrowserDialog.h index 5b12a3542d..fd5a22dbd4 100644 --- a/Source/Dialogs/ObjectBrowserDialog.h +++ b/Source/Dialogs/ObjectBrowserDialog.h @@ -235,7 +235,6 @@ class ObjectsListBox : public ListBox void removeAliasedDuplicates(StringArray& objectsToShow) { - // TODO: this is inefficient! StringArray gemObjects; for(auto& object : objectsToShow) { diff --git a/Source/Heavy/ExportingProgressView.h b/Source/Heavy/ExportingProgressView.h index 0f2b4b2337..79cec243a5 100644 --- a/Source/Heavy/ExportingProgressView.h +++ b/Source/Heavy/ExportingProgressView.h @@ -166,7 +166,6 @@ class ExportingProgressView : public Component g.setColour(findColour(PlugDataColour::sidebarBackgroundColourId)); g.fillRoundedRectangle(console.getBounds().expanded(2).toFloat(), Corners::defaultCornerRadius); - // TODO: use panel colour IDs? if (state == Busy) { Fonts::drawStyledText(g, "Exporting...", 0, 25, getWidth(), 40, findColour(PlugDataColour::panelTextColourId), Bold, 32, Justification::centred); diff --git a/Source/Object.cpp b/Source/Object.cpp index 58a45035c7..bb678eb509 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -471,6 +471,11 @@ void Object::paintOverChildren(Graphics& g) } } +String Object::getType() const +{ + return gui ? gui->getType() : String(); +} + void Object::triggerOverlayActiveState() { if (!showActiveState) diff --git a/Source/Object.h b/Source/Object.h index a117465ae2..1935701583 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -54,6 +54,8 @@ class Object : public Component void showEditor(); void hideEditor(); bool isInitialEditorShown(); + + String getType() const; Rectangle getSelectableBounds(); Rectangle getObjectBounds(); diff --git a/Source/Pd/Patch.h b/Source/Pd/Patch.h index a7c9b40067..2b8199f49c 100644 --- a/Source/Pd/Patch.h +++ b/Source/Pd/Patch.h @@ -6,7 +6,7 @@ #pragma once extern "C" { -#include "Pd/Interface.h" // TODO: we only need t_object +#include "Pd/Interface.h" } #include "WeakReference.h" diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 5e331952d0..07069d5fbc 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -19,9 +19,11 @@ #include "Sidebar/Palettes.h" #include "Utility/Autosave.h" +#include "Utility/RateReducer.h" +#include "Utility/StackShadow.h" + #include "Canvas.h" #include "Connection.h" -#include "Objects/ObjectBase.h" // TODO: We shouldn't need this! #include "Dialogs/ConnectionMessageDisplay.h" #include "Dialogs/Dialogs.h" #include "Statusbar.h" @@ -1254,7 +1256,7 @@ void PluginEditor::getCommandInfo(CommandID const commandID, ApplicationCommandI if (auto* cnv = getCurrentCanvas()) { auto selection = cnv->getSelectionOfType(); - bool enabled = selection.size() == 1 && selection[0]->gui && selection[0]->gui->getType().isNotEmpty(); + bool enabled = selection.size() == 1 && selection[0]->getType().isNotEmpty(); result.setActive(enabled); } else { result.setActive(false); @@ -1267,7 +1269,7 @@ void PluginEditor::getCommandInfo(CommandID const commandID, ApplicationCommandI if (auto* cnv = getCurrentCanvas()) { auto selection = cnv->getSelectionOfType(); - bool enabled = selection.size() == 1 && selection[0]->gui && selection[0]->gui->getType().isNotEmpty(); + bool enabled = selection.size() == 1 && selection[0]->getType().isNotEmpty(); result.setActive(enabled); } else { result.setActive(false); @@ -1604,11 +1606,11 @@ bool PluginEditor::perform(InvocationInfo const& info) case CommandIDs::ShowReference: { if (auto* cnv = getCurrentCanvas()) { auto selection = cnv->getSelectionOfType(); - if (selection.size() != 1 || !selection[0]->gui || selection[0]->gui->getType().isEmpty()) { + if (selection.size() != 1 || selection[0]->getType().isEmpty()) { return false; } - Dialogs::showObjectReferenceDialog(&openedDialog, this, selection[0]->gui->getType()); + Dialogs::showObjectReferenceDialog(&openedDialog, this, selection[0]->getType()); return true; } @@ -1618,7 +1620,7 @@ bool PluginEditor::perform(InvocationInfo const& info) case CommandIDs::ShowHelp: { if (auto* cnv = getCurrentCanvas()) { auto selection = cnv->getSelectionOfType(); - if (selection.size() != 1 || !selection[0]->gui || selection[0]->gui->getType().isEmpty()) { + if (selection.size() != 1 || selection[0]->getType().isEmpty()) { return false; } selection[0]->openHelpPatch(); diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 3959f33535..83548b6286 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -9,15 +9,13 @@ #include #include "Utility/Fonts.h" -#include "Utility/RateReducer.h" // TODO: move to impl #include "Utility/ModifierKeyListener.h" #include "Components/CheckedTooltip.h" -#include "Utility/StackShadow.h" // TODO: move to impl #include "Components/ZoomableDragAndDropContainer.h" #include "Utility/OfflineObjectRenderer.h" #include "Utility/WindowDragger.h" -#include "Tabbar/SplitView.h" // TODO: move to impl +#include "Tabbar/SplitView.h" #include "Dialogs/OverlayDisplaySettings.h" #include "Dialogs/SnapSettings.h" diff --git a/Source/Utility/SettingsFile.h b/Source/Utility/SettingsFile.h index 25b8a04c76..f72d27d689 100644 --- a/Source/Utility/SettingsFile.h +++ b/Source/Utility/SettingsFile.h @@ -28,7 +28,6 @@ class SettingsFile : public ValueTree::Listener SettingsFile* initialise(); - // TODO: instead of exposing these trees, try to encapsulate most of the interaction with those trees in functions ValueTree getKeyMapTree(); ValueTree getColourThemesTree(); ValueTree getPathsTree(); From e3f7211b2ba11e381dc65e9f85f9fd999359b08e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 19 Feb 2024 16:15:07 +0100 Subject: [PATCH 0257/1030] Heavy help dialog fixes --- Source/Dialogs/HelpDialog.h | 29 ++++++++++++++++------------- Source/Utility/MarkupDisplay.h | 5 +++-- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Source/Dialogs/HelpDialog.h b/Source/Dialogs/HelpDialog.h index 85632ea909..2b11b9bfd4 100644 --- a/Source/Dialogs/HelpDialog.h +++ b/Source/Dialogs/HelpDialog.h @@ -6,12 +6,11 @@ #include -class HelpDialog : public Component +class HelpDialog : public TopLevelWindow , public MarkupDisplay::FileSource { - ResizableBorderComponent resizer; std::unique_ptr
*o<9dw4=<8lZxKKT&CV`DJ8T;UsdE#nQ8mH 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

O|Ivk#ZH3Q!X<sL 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 C1@FWF diff --git a/Resources/Documentation/Manual/images/one-midiout.png b/Resources/Documentation/Manual/images/one-midiout.png deleted file mode 100644 index b5c53835fee10c896e2cb2006dfd4ae5a17e4289..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 135097 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

tk~v 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�1#Gb$_ihKEj6wUg%xplm9ph75oUgDCCLz~5TS{W$Ph zK!Ucz#|k%*sa9?{%hoYq>US`rh0HmLRxyPn6zpNIpDG8zeW(i+e7HZ?HrxLp?Q_u< zkeXj!Imwrw3{a1IkH-`2(|q>Y3f&PcbF=Fqn*A0(m(MMB<{M4P8nB};aicL!Mgnhe z5UZ+Mf2woQC*YLFKPCR@NM6=$NGCW?|%lHqg79yKwNz z2IO0>#s6^F3X)!fR=5R?HnYBUvkN>FJidT^$zpr%5#CjXF< zMlFd(L|?q>bDpYBjs5{i4%0F-nf7TVXxp zK_fr|YYDbW*ta-1@JianK30$X9}_Ks zon;B?^cTC{p0GyKm#@^$G9o?f(DI0tLSBJy-p8EYj&bRhX-GPq54BZM}Wfh}qH{0Z*pqrqX z(pfY3r#iU0gQ3gY<8EMPxA{Vyk9S=5!iQmROTo|Pb=ryzm6cGPvn$sv%hSXGx-J+% z%A&wt!``%MbMD>nb+}|bUEp3#wE?|KmZfO6zeg%Fd%N0o#?@CpTy`d5{$tyNE|XE( z3=NYTBea(&W%}$OX%DeN=1hup@TSbFGDopMLnQZy{uj$Z2w+3~v*QT6j+rZLc}H)t zdv7mTvbYpuz>wdxO+(t1<>+p0g?{C7ysq;Ob}&gHBm$PfAsa$d_~|Cm^*%^-&R;CQ zqY2mhKJZ)Glcvr^{{i$vEi^dae{m$;18_L?s9XTfk7Llp(n4Aueqyy3??izyTE*z| z$DYsE3yoMuRkg1(M{AV(dk?w_iw4@=LkvdxhLLPL-SDIrEjis;K70!|!}wL7p_2pH zM@XaO-D^fBZI0+ckDuvbtFGn?2kydZOr~Am=PFG&r^9eXk>`B7iC+uw0t@Ghvoz$US6d|2C~1?_x_831ALot_iIZm~V9b73MX>5moAz zA2l-~lio1J;9ESNW+mE;_aLS8rS9AbEIxvz?{L8G)2)tam+hlsJVwL117FSsoN8Yf zyY5L3eyFJl=LMQ*@A*!omna+=a6?1lMVk8Tr+#;R?R1&vHo8e=F7mACJC1AWi3G(r z)*S-%OIfPi*V34wj19m0PjhyS!|w85d>rxQKP&wp4381vd@tysK6HQA1`~EK2l8Mr z!v3*p-X7Zc?C%9?RC#d5oTf4LJpqQy*zaE$I@P~d@7ya&hupP)+BgP4*cKrk(KxyJ zJBBR|X36&@xjY6lcfFbWr)mYI@YS-A)zcdP!$5<+PxfRgZq*jX8~O6YIea@!0DZdt zXePKP;EF>smUp!!yW9)hZAvvg@GL{8+QhM?{pxff^TE2?J|k~SN2UFnnCQ9k*Mm!u ztEH6KRKOdkKn$_T*@p{E4EqVmue!;{?A?fVp|c)fvj@5l9_u@*O&-6Y3bJ&T3YIEM zycvEFra@oOh2sg?~dQJ}B2R9^QIH#J^J+VAbi@Uuvf_zHhhe^2?yNf~+Kxd-4VozO$_gln@!8q)w$oBH5ydyeh zQ(pJ8E9i)nSn`D3vB_rmFD?HKj~z|M4Qi%Ki{2# zha$>lP+|Ey=2?1Pl$G&P)3lHH%+-$HIDDmPwEt}4vbi+M$M{Fph!5}{2p7Fp$`y5P z)}PzB5uQ$&D+vGp82jqDs(|p>%i0 zMWyosm++F`#+mu%dER&4nfZ_3<(z%?UTf{u!S?#)p*YLs_-@0RE@DDGM}@9asZd5A6;kz8;@y)?0)k$eNg`?I#LX0z(u#&aHi0M&42#UKH0`PT3$9>r9Jz z9G~BN)r0#gcuqX{D(1NJ4NpvtakZQjy-FhmznNbiDG8~7UcEWtGzCc?o#T!hJDY-d zW_HZg*!|6yZd*DU#*Kc?W>czc^*;qIR|M@u^}-FHI3m9cL6x!e4oEz) zxho|gV~F=er4;X|W}8;$wbP(Rp2or?Fc4OH^3rS52$c7|d)}?h+>({kfbBdaN;NHI z^d^Eg>~}sIXspdQ5=;mocWyk~92sD$o7lVn#C}j>5_?c@q5#F9!|!In`|)&6q3{gk zYs`r=d8bXI9HRL;gbD71Aq-_z{fi2wZbNyiwP z3njKGiZXA}qUBZ9RNVvSk50|@V_r-5re;5#BnUfg_^7*I{=}2_z=vyod%G0IBfL{Ox4fgPzt+8efLKxgAQH9@oayOzH?o*)^ zN?+>UlYrcjFlGOQ@Zi!AlYa4|42->PV9DbiNm3mj*K#1Nx!gb|_?;K!`_tBX@xGT! zat+liy!&mn)(chyA8O}4k_At@X&iRl*Lv5ZUwkygDfWGSvAAekO?b>yDpN}?%wB)9 z8mH8K(?Un@+fV|>*wTR1*(SrX@0HMEz_aR2o}BuR@p3HNZCv@i5otnaBrZxtiWhG% z*rK{FPZFwlg6EKPMSBs+Hs{IOZu=cI%}kpAplPB>2&b%sPcrmn#JaDN$A4n85DTtv{v#-ls>=NxVk)6tKUg??IFy5 zPAx9?I@!Ie1XgsuXo!#*=hS>m9tAxc;-38SHHt|H=KZZjIPdIu!#Mm%XN4~tTV*Bd zQ++4b+lFKE;nLdAw;VEZdOMPBo5o1KtRo1C2ZY`SM280QNFex(XYXz$DzQc}tK1bj z6w7!lt>2hDz%g2W5;eu)~-ewUEp+KXT0S~?{gNA zVo6AB_92?ClXRz7EHifkz4?3QrO%o7J4dpSLJxdC)zwp8g;Zw3;AIp_h=XkQ%rknaa#!y!@gjPlR+ zqoAUum&dmWN`AaCF_s-O6>`z>I@brvZ_2O3VQ=_Y=ISYed0A1Zk7Vkxn)~2J5G^i> zG&7P;$J8gWDO}P^>)QG4kM;1%k2pNuop(bq4odph;R1i+ZLVkB>A*~obku!%%zwKi4Ktoul=6&QK+F{`1&q_CvKjJ*= zK7AK0#Ia0f+|WXLDtVydQWVX^@;;RB>9~V#lhk;=)*@WOxo9p-4RI=A=u@<&T4Aw* zK4Zi(G-*3eizRga;lgY>En1$O)pg!Eyt+tD{>4WdA>Cq4XASGl({l{=S@R8OQ*w0# zkfpIqYLO&7mYES)4kMdcn9k{fQ!EY&Cr;TC>C{6aXD=g04Pr;bv^=X*($BYywB*;o zh~B8PChRIg8+RNmaKU4Ql%L3oW4V! zNCLi81(~bXm&Em)EW+8{;X5Zn_Dl5M@TMcovM~lKvfRc=+tIqra%CH1@r}g}|4~cZ z#vO&;E%(siVsszYQo>;p8`s|PjmMooAuwOsQz~(J%Cyox1^VTdv$2!K#Va>;j?(fU zm6fjQ;*ROMLSFcA))0t?Y+-2L?P|IcdD|qnZC}VP$(5D+;eK9`1gg%1C0Z3ftOvtG za5fo&6b1DH_uD@$FQhR%hwMx6xlI8S8eS8JF%PWfJLC>1Mm6rIZQiabh<(R@6?bj2;#*>dF$ z84B%y49U6IVEc*gWAEvTX8YdPbwH@Ber+DgF7n-YXWDPktTJgGR_p1lQ@jQ{rJpBf z*QuG;?7BJ|6tGgM&YEic@$!^L@=CHS$Q`ebM^{nKCM_qIJWQtvlm>e@sAfvvQ|PeL z^k7XtHxlUX6L?J+8yjk-Ud_DP`$;H}qGO~x==04_QG@bX?4qw$yqc+>D5CdhmD)#} zWToHRn(9qb(E#BRLd&Bq^-I+fEA1|qjkLYV77NBad*g}FVx~2+Ti!(k2ZIySkmHpskmrM4+$dn;t z`pc3xm6eq%BVCuK+DJYIfyJr^z@V2@FU5sA5}Aq2Bd}-#1W`pPy^uFs^_DueelXCR z>nbpgdWiR|czNM<_x4uUuQ-NEbbT5~k_mVr`vcN;$o3sA#mS(#!lIyDG>k4ES9d{l z3$_qQ;oX`Yqn2n9J3rTMZj(IIYnVtZt2I7AzqdAOtT@uBR;s$Q=V>cc>FrswUhgKo z`SiOB9)-Xwi>aDWZD)k|vG<}BJ^7YGzb$XeMCIBQRa=;_v>Xo`WNW;{n83e4orPtC z=VvmS<7#Tv#m5&TB3A3KA(R>`mze|lpQwfIT;FBd~%T(P3 zFA3-UsG6M|QQaKpNYRX(VB{26G*(Tc=Q#;erx>zk!7kRCGM;#ttx`8b*U)5T`h~<| z;YlRKwU@vGGB|tmdt=70rk`$-QitZWqh5or2FFE${tg;`V1uGmp^e$ ztW%9GM!BGFIqrP>ESFWYvAz9+xoO|w)|}R5g1}gpA~e4Uy+%?M!FMD!<8yX_m4)Rl z>mioP}ubrht-I|H z_6nPY+v&U83-uG42J{W1Q>L$wFsW5~dF4bHLdRT4JI=q0#L&bNWM~X4$7%O(B?FD;rBAtnvs2EvLO z->XQaHA43bF&@_>4>N9_w)nJlJ|(jG?9gl(E*xbk*gz|`tt&Bd6i|Nqz9-Cka_lXq z%nN43>#1jaaEh0=5#V~Sqc}Z{X0uaVX{2ky`jdXoai_}-1GQZ`R3;rt^|OHw&M^Mx z4vUZsIx-1+U8q8w<>}App==VRntC70$s#I}pjSH6XL+E)U#p|BCO>FGRGRVX$pa$U z*X8b4(0ZjijHdWzNynr&i3VhHxUa%*9USGriU>+Iai&UC#@reg43?}53+?W``$p?L z(=^!L3)R zV5A8n-gL*xUUB-`-#cNMga;PqX7h?GlLACK4bK%!a2*ztZz+;%I1DOWIa2GCc>zDa&&J4zAdDZA)gC^6p z)zZ$g%U*)P%vMx4{25T)7ATOt<#~n>HmhU}rCtX!Ypa2MF7B_lg;sB=I;VH?CQQ8H zUGNHS^8K0e;m0@N9|Pl{1XN<5?p|J0IK%1k$Lp4hXnIwY#Sci_zo&xU*Jr8@vt_!+ zOCr75Y<1$w{PIkalzNOU+27C{D6tKZ3mONx(~V@G2w${2I>a38&vy~Ba?}e5;qufR1!$@h}QO`MQ zwGKSke=4ds8(x3xq0uZTV=g*EQMWXa zcX80w9=3iLn44AvaDOQKDzRAlYevfzCU>86=Hn_RiHQWew+Ql_Jhj-)PHA<5IuT;t zg`9QN_Bm&F5SHKZ8((QyYF+SsYntK_1%?)DX_Ae!u7@icUtMZLUa=L+-O9I2>+_Ey z@p}TRNQHZNh3p?>XdQL#^*gek)_rXOO<94i?a{d}|25==O6e*Ki@J5|sCd$Nkak;5 zfvF!=z#S5d)Au#$PxM+yVfb>RWq1-s+_lEG=MaJ0_F>yU<~!${@Apm@PFhn7JT8iX zW|tn|PYEg-;G)WKEEd8t{Ibuxie2w-O$eZ;cz*AQ7Z+`*r=80c3Y#48Mxv66 z%4DXkwVAOhK9RPNbk;hG5Sp9p%PP9(Fj5@eg9akqLt)mraT@j7l@99Ru~8(?KGp;) zHb37V<3o{-5v68Wk->izLdxFTs@x`QHo9gNnpACd|HFM(w0&&98z>-@Lyb1?Jk$i* z(_LxiN)v2M=WB_EM~kCp-au)kePs69=R@dF%lVW3q+7vPAfsc$fTdPtX6CcPP15KY zi1Jz(*> zGKE*^6NM7I^6Sg=okGl7Az}Rj{Dd%nLWca4+tsRz)t0rc1WU3832W+LA;uvV%@HTF zh3IIVb(hor#h2Zi&7B6#xt~crWF6NAPx31b-YMKY8MpgDko#W4>l^Hr@z1hM&ds-x z$DZ7)r^()%opVncwF5I9%xB?lJxRwORR%4|RC+DR89C5mKHnBsyYf0l#M_I>5ggD0 z4QL;AJ)GjGPJtwfX|>jBLGg!}^|*dSk)Gf{U8x?;Q1eml13$>lbKgon+ny`|ArPBJl z{@n(rEk<)X6K*u*a=Hlp#=wF%lC0>a{Yhlbj&PrJc8CkChAr#LV}QWnqzUU|L+wS0z%5#uKGt zi3%HQY6S$+w>E~l(tWal(+lQ2&^1&=|Y!z-mS z)ARa5{Rr?w_7DA^R_YfDUR-_E+Xb5+=0a^xx*?5F$9o|RbS~I0EEiqHHPwSD6bzqnd$gq{fTgK!>mOrkd|s8Z4oW#)a;A z3SZscbgL=nW2~s@`Sw*GW5@8JD1ZMv^;}=7cro3>q%p6X6K^)#I(c%kz;#>5wb#M0 zL#x(>RsN+F>t|`XYL}L?<~u@1$UC27_hwAfn9S*_b`>NrAE8eOcanHygLZ8Lnhcg? zN0)_!hdImC-`os`5h_GGU!!z=gX3AMsc@AM+isUX2UzG7Z7F3*c%uSgEpxiO9%K`b z$;EHx@_#xvr%yL49VjfVQ&%-AuF%y>=CQUa6|t5mofB^INZ2%9UQt`z!^z&U5GZ&% z713|8Q@T--kw;v_Z@^bMKX9Vw-y{nq{ilbc&5s^+n3SCa7Q&q`5o^ko9NAx1%C3m?=m zX11o`PP1cGZS%YP^l2W0&0BW8m9jp_?(T?^nt)K{SkJSNscx0RscsOq=L+V>^bGf! zZC`EPq2SfL_1VL9eLORjAUmiX*htSxNF8I%LTsxW)x`51L&;;_SX&5K>=Y69@q`#C z7=9?6v!e&y4aL*Ar|22$QJt(7<$B|XP8#(NV!62_-xBk^>ux0aN}p$mNYnepvu}8o_9Z8?A*-@NEA-xH0Zo(6 zNYTw_))p}rQ1j!I#axRCrmPZps>|Uym`|`g(v=<+TR7=P6kpn$X4FFDA*)uREX9-r z^Z(R%(_MJw9IN@_yD9%gjKbryDaNr2`MD+AmwL5UP$rN}S5loab?c_w=p`5ig;J?b z*Vki!frN@e`RbF@i>){EIX7LaOqzAOmBokl;IN3Douz-b1j=aOC&w3>6XSN z3l-V#B8xMw(A(}g&k(*}&taLhn-P%WXS=QP1PwnoCY{&UegmcLGu5lRD3c|+=33_1 zcQA;9_3OBikN_t)N3)tQdu-md+WBR4%v;ADLRG1|@FeTIL=!=#O}Dd=nfBUaYfj^E zg>UEOuOpYSwV+RoCRqGxdo$Ig|Qq@R**8(QVJy_bKgwq{U;BV;9&C zKe3TS=|C8nfF!}vq%Oh1b;8nf`yMhQ$y{nkPN|qm#nw@re#~lLC~shg%w-kKHsI^j zEgI0RJu-VZ2mrXf=Oo+;?#w%kr!6RxSTQh{oLvn^k8jX@W{h zoxQ$WHN_Pk)n`LWIz|&v7Kh=O*y_>aIr5lR-;}LK)59kxtL`4q#fCwzr+)p^fE z)9X+u-|hd`nEmrt)R&Yzc8}|SQRX)~q|kdX69=udWN$ITzd*uo>htd#@nc7FbL z;Qz&M{?Fb0PW9k?F&@bI@U=_TO{yE$KZ=^umf5!XkMdb{K3GGkDNZL{6UzCeP*6so zqSRQ=SwQNY`&OT?vw6S0-mgC;%JJ?1n!|gm{}0cI`zXELZ`;?+LOk3bS&E0y(!W=~ zLUb7jbF7ByKKt{_{{!ED0Pfc=-r$OIq-Wqs)c%fw|2pHJaQfpNN)FVF1o;s6toVO; z@b|ArWB`#$zurD#{r`9UKQ73BZ&UIvGQ%pKVcPn`-)NB0ztP)MXJh7Tz?}|OjS?qR zDge1i6wKvSLJhhYJgs>c2qJHK-WKP|6b6 z$>9AA{|+V@Z&7nFiMeXtCVkTQcc=6dJ;3{7AdFZ`!1FR!oDl4P_YUPNXx|C>WEV@4-WD}?Nwswv~DhK6T6*z&x^xt&xpBoVM!Un+m#nt}r zhyH_fMWb*TUURf~o|Mp%F}3}1UB6J-QL+!sRL`X}@e6l7_?z#6rM{6Pv8;l2Pk+0w z|NJBpAY%+ls+<1~GS}aJp26Rz0Y*9u%ucp!!!;9Ym$3l9fI8@Mu_G-o|ia8}C=|!>Xo;2>^tGjR8y*!>0s^ ze*?-0E5M&2+}ZKkpfrJ94=Mp78fVK0knGg6MA?k``5}a9rBF4WTH+qR=B!ao1(|l4 zUAfoUz;jPF)?$P9ICyy+g&z0DJ@7R&|H6_+!PnLH@O4Dw276jiT;kBXFX>f`I6&Zb|AkhSJvd#F4 zEs`rG;UEmeBh|TKG|}H<_JxTyo0WJ~55#uvtWaWOp_D*eUVe00ij<&WA32WBw+Biv zcXG(z(PmJ9sI)z|BFXGDP-^}4Z}&34`uvtFjEtW;LlN4dRgls0?F~FfKj8Ld9`13S zJKO|q&h-f?u9X zo8s%U(yZgETr;b;vZ7)s^G*B;S}t}cUk6eHhK=d^TsT*({~%Ia zM#)OVf~jg%TQJh6xxIOrE-co3b$JJ-v>hn=x<8@Nc-RjqDos~KQBl!oZ7}uV0yO@+ zy5yB}aHPS&A$V4akM`_KsAmf)=NqTeabw8WsI@@P_)t>#_XW~43MhB$oV=`TY_d(` zQ!s#yZu2V#JG;Eg&g?@y*%V;>UZh-T(F%Ty35)Y1Jt3)WF&qhFjNF27P}p=!8ojqU zUa*c66q!#>FGZtZr?L}-*b-e?Y@a6yc|9B)C`3>H5^PA>f5PZFjy@s zmlvaXPeC*GNj9FoOK9cy2e5oM@?UV+wH-}KX=)~+nJeG!XcWnK78bi;S=!7{Z83Ss zn2R2_6i&tnk{)Bnh6JaWw7k&mu=Fb))aH79H%y`ACesFGQ`(q&i>k7JezDJLH zTHb8w;n`}bn6LOy{$XnN;h_`%(elXX6pM7zw^kXUwds$nNW8l|wcB;(y2*O%%i)j7U&SCmcX;9fpfkN@|&0Jf0H}SiWd%3>eMy_AETdqSd?V z?(dl)st`oK*5?>7k@HuHU$yuM!SmUhJdYxtR9g~AR8}dVAnCArZREzQ=X&9YW27`6 zMg@apw2cL|oe{0dmASeEc_MD4CP1n6%rFYBA z%e1H*w6>vY@;r(1aNd$0N)s|#&!XZqALac@y7m$Yd89wErOdLF%rN%g5&?pXqO4?O zx#xitzSK)yEmnH(b$l1@7b@oim+rj%8LPuu%Py1V{fAj}-#VKX?-46t(MqAz{kkVa z0pOl2f_rlL{FzAeQCu?Et1oGjiBEL%(IjNF!_B>M@7yt0Jov8pcv)tOE0q7B7i1o@ z#YExY*)8ef1;fI2E8FP(N?sC0EE~rz(X!!V47roYU?(xfy57o4Zh*Laz*O8?W z1k4rveXa3EZSO-@S1iRrdxIclp+g1Z3u4n0DjErLX0i|!?;I(jM=3t18>(?k>tu-8 zxSEfO!ip8+Kt38sW!Nr49X2|M?r(K|2a*!)$@3W^T{r&_-|zg0u0`9&a0P*O&A{M% zQgfA43y-A%w1ac_(n`|_G@kU*CE7Gn=gQ{-Q|)!>E98;EKy=t>eWkTwtCnqFAA1Wm ztmVr8`~p_nU9&T1ed%}EjDcoNcZEXxt<%d5WAYKsX>uq&vHw>I%qjxkWFZN1-*c(0 zR-e8&R);gPD^7i`xEaU6BMJ?Sar9Wge0+F~-p(!>v9A%ztev&NX&RV2ixGRfw}!B6 zNxs8QI5F^eSi#IAU9WwDnI_NL^Mk%phk=AGLALEw&hIjLFX~XTnx@)+2oUbxkIMI| zf@iYKM?}7SnN1u#$KvkuoQt&Cd7^Gl@2YXYq`)RXxdMf{zQ*0B-DZ$F5o;%O?xb-# z^iesjgcZkCcXl}aq@>~eTw%ycbc=C)!$)~;84ur=prtB~ccSXp5Sekn?W2p}&Ykjx zjS05{DCmx-aR408!SD#xzGG02)&=QbNN3)etFBZ543mi`RQ!8fu~w7#+p5L3qS3LO zibMDS?_w`Ioev8da&F{}q0sikBj>HjdpL^}pR7|39V7qMFa9rpg#_&u-Ja zz5;<`hZUxgEj_XR2m^T_o zgi?5BeRL|rM2sA%J0~b&z@-;C7rk&Dz$A7Sjt zhSyp|Ic=F+PdaB0!`({N4MElfV)Ts8~yLVebR&h931_qdK2R9j5s=(pWgT?dG|-)v{` zR`z!|m)(hcP5|EHHKN5C{rvvNW5AIVWCP__aN5GledP;rpo_NkI+tgA7s6}&47z`S zTmC=6jrdSCHno0zz+w1aTVL3JG|@Cozp~Z} zZISsUrTa30ETw?JTtkVgG$zkJ?2uOsV`)aTxWfsjBVNyECB;PRB}$mcB_EhKS8fGu z)X??4xJS&h*=}IL@1B%sJwJs->ZAPyDPN}bdt2a)W29RqIzvqVGqgH;n5)=}%v2U} z`i0j$@U|w5P9R=A+Td+eI0)5+Ns)_wM{*?K%ZtCfIO|ae2I;efPPyI$)q>TR@(+Vbs4-_?-{2cK1|1YoBOrjhogP&L%*sr?83Y%suLi$y8KXR z76X?%omVG)UjWJrF=VYOm%;=lRZFc6P9IwhQx=^T^q!M!&q#+PI>O6!Bv} zJn}%73wqu~uW0X@{JnTR_lcv&*!M~NN9aSNz<)f^udIIL+k{ejg-P5H(8n9z!=?`x z?$O*_;63&)+fvH3L*9JhFpn4hku7QBa30P+%X)<`@*5E{Tt;@829H!+U(v=FoD_f< zQ*TPlepaCGt~X6zc`&=PXmbhyuKh9k@PHpw-zJ;rqG0O#A&<&6?XRzRr24qQ)SRRS z(1={+$UW?eblQ1rp|FD<`t*9{2j==XA3CZB;50>ydyL!1Ue7&-;2X+}!h300`0(7H z&rprmH_@RY&5cU(dLD zje*+aw)vtV0LV*+k1FzQmD=7DvBys)_}0L5hM@jLF8cD@kQ^92>4h>(JO{++P}pGzH=dO<2ssq3|z)G(An%_LF%uT~u^5 z4e!-bjpXxJUE;_#dVmUvPJx zYnZ0$>$G)cJ2cCuL*>+VTt;(jy~4zV5`+yuNpLLISd$_S3QNTeC-7rE65=DE8z8`a z@H<7$;{b|uRr=u(sJ!piu@Wx@8rKwUlXY&ce-EUHT=pQ+*vWyIA**4Jt z{V)33C;{dti%Nm=S56kE5S)0yIrU}p2M{0%`xLWLhC5=DEG7z!Yk`qm;SN}=i7-p# z@a4$aKymFnOF;MoBj$DL4AXQ{cH5>4IWyo2Ao-nxT8pBwb;|+{n$>b2SXXHjBXBIb z@9}zc!9Lu^CMHR@bZaHaD&6)Uz%kJ~ydGJU_l2+O;tja6xX?&j^#Jvf%YoO3uR{9l zG43xmYyrf$g^;IvvrjN^MYvZgtT2}~A>8Ph2 z7ntkaYQW=D%^-4h?wE~BQQGUSqx{X5m7*XQA`$`WLO*tXQj7n2+S6~Ple5xPv-nAt zH2kw{@?+xMF7zP|fnVG+Qgjl1S81P*-R4NjFn-Z-NJp{D%~9dj_3&HdRfXmkaLX9Sm&~s2!5ewW@ck-XQ6rujcN{c$< z%^b3g0qa{J4tEj0UIcUhyzG9~sYDYu7`rD~W4p+*P!YY0I`7dZ0>M)X%eMC-K_q z`g0oL(*nE6+x>WhUNDn0jp8b*5wWUu3nNO21!cbO1VWXqemnU_=<_~c$U~wZ?0D?~ zqf(fyc^#RU!z)3d-?Wc0Z$(SkLuTroPD6rnEIW|CSqUfa@dCVScX>v9x(pswy~W?? z^s1wxp#rAhm`xdA;?VY-7}@exFkpdMHe{+^RBG*|2)KhBK?u=@-HyS2TEIfVay4lO zLT)K-ZO{x?FX+k(lpkTyA6v7O>6cRoO)$RTC@m4ts2Ehe5<2 zh-1Chp9u1f3m-OWw%>7FEEPfFj{6i{b;0lG9=Wym`Zv!_`NIr$meR%p2S&rovjufM z->VBO)saFrC^ryDz`I$VicGQLziPNUU+-`@Or_ZD<-Rdj8-9Lj*$k#^dG6M)O(RC* zhv2IhyEMT8>u4xRSVz*>-CSRRRO*#lt#sPpbl=Mi%(iWek^Bi0GX%yQk$|@QQ37&Y zcrJ>V_FW|+#9K!g*5L)J+6(Qwf^q^lY<;5euRxafI2#JP`PA`J1QW8?1R)L&3l!(v zTop?Lpi+w(8?MjBw4tSL*&lm+>Q0hGPYUf?3^|A;qfo(Ht+A1`>js=5b{e-&eXdbu zLGHbc2Ie4^J-htWqGNhQ9vvDV8!mzfIicLu$6{ifw-gvY30RYO)u^8iwBgpKwRa0W zippPog+ZcdJhh3GfO7Tisf3L=P(n58;4@J$aU4$LYw2`Hu90Dl^s(9Ns|d%_>Q;5W zcer>^Mhde+=&kY8B)Eah!pzXAwM$0UXqNbUXLnf2`BL~Tg<4qmb^5@y-;5zh6ogI> zH)qBO3l%EgtMs4178l{1N=xx~q#Gi(JCkiNoq`$}PytwV_G&>lH3iqjcs-Z=2LcXn zf$O!}rr60p{gYI5b9tsymRV8(NJ>ZwztPbh6ybN7n!(}6BXrd7$go1Qyslbp2fOfZ zrug1#R|xB2px2J`Ukw0mDYf^&V9>RV$k|`+(sqo!XgclorHCjjtyit6tYp1^(Zqe+ z4B7Fqu_;+U@x#|b{tmT7UMOw70N#=3A6bES1mMj8FW4H^W=y<(hBHSV$F_0mTJ3|J zE9B`AVxVi3i>4IJJ16>9TA2M8jC%%TG7zXIX=)Bl@{Mr{{q)e3`dLF2%_{?zY9Fp3 z6Ys=NzK6;M$|IVV`J@~=`Hf~H2KEM3u(?|H&$x>UgDqE=fmyxVQIeOFSH73^grDT^EKHGM551)2@%0cMdns~>Qw5oB+3x#QET!cX!|E_X<%<4UI!EUaD7H#j` zb^2GME%adm`c4^2%k`3!1)d^9SVX4OdNHK%%qT$NTs$@d5*fgvKz*LOC+H139HCF| z{frkDe|ogpGl!=e9ZCw?>>jLG{iNv9yu7?aL8vkqZ6Hti!}H!yc*09LU|6RHslRyn zB4+v-jfqBkI)R`F?pZ^|$yczv3O)90N}>%SrJ6(0>LsFRi%ichFUTw8w0UdI`lABU|z7ChX z@)IpvGcQVa)L=d!Vgqx|M~=fTDH=mRzVAD(%QL2WA7F!{0;se%kapd7ZL?kw|QF}2Z*@sH3(mMBT#jP}Z5WcECTxf8v{=pM=;Yw#y>Ut1nU5Xep?LQPA z06BIb8ROv|sO}2#0rr?51jPx5zI=JXIH)R$X0MhMEl|PX(7e71=Nm#{)}+xX)mJG=SO9nuDh@bpNrE`BuY$)o+i3xx{hCFke32uJ@iY(@^s+n!7U

Q}Fe=tHcqhS&Y$*86iQs>P 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

(~6eEz4eivBz>>%(p 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 bz;&-32QAG+YNw!w{7L5w#0;Fee(!$(u5Ekx diff --git a/Resources/Documentation/Manual/images/pd-midiin.png b/Resources/Documentation/Manual/images/pd-midiin.png deleted file mode 100644 index daae372cf0557bab0c0a48d9658f37ad744df792..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60157 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%0AE!2)l08gE0IE0aZ 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 Y$a$?qJi$G&sZ-ms9epjOUNkPKQ;87}T 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 diff --git a/Resources/Documentation/Manual/images/reaper-midiout.png b/Resources/Documentation/Manual/images/reaper-midiout.png deleted file mode 100644 index ff6d68ec19838d655d8b914d725a636ef3dab34e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 158197 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 LNkvXXu0mjfF_m|^ From 3f3ecb758d300063c93eec80f13b9d19c95a9553 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 16 Apr 2024 00:13:25 +0200 Subject: [PATCH 0579/1030] Fixed knob bugs, fixed text inside graphs appearing blurry --- Source/Object.cpp | 2 +- Source/Objects/KnobObject.h | 3 ++- Source/Objects/ObjectBase.cpp | 7 ++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index 6a8630365e..45724adee9 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -1272,7 +1272,7 @@ void Object::performRender(NVGcontext* nvg) nvgFill(nvg); } - if (gui && gui->isTransparent() && !getValue(locked)) { + if (gui && gui->isTransparent() && !getValue(locked) && !cnv->isGraph) { nvgBeginPath(nvg); nvgFillColor(nvg, convertColour(findColour(PlugDataColour::canvasBackgroundColourId).contrasting(0.35f).withAlpha(0.1f))); nvgRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), Corners::objectCornerRadius); diff --git a/Source/Objects/KnobObject.h b/Source/Objects/KnobObject.h index 00c1294404..36800a2435 100644 --- a/Source/Objects/KnobObject.h +++ b/Source/Objects/KnobObject.h @@ -71,7 +71,7 @@ class Knob : public Slider, public NVGComponent { auto bounds = getLocalBounds().toFloat().reduced(getWidth() * 0.14f); auto const lineThickness = std::max(bounds.getWidth() * 0.09f, 1.5f); - + auto sliderPosProportional = getValue(); auto startAngle = getRotaryParameters().startAngleRadians - (MathConstants::pi * 0.5f); @@ -253,6 +253,7 @@ class KnobObject : public ObjectBase { arcColour = getArcColour().toString(); outline = knb->x_outline; sizeProperty = knb->x_size; + arcStart = knb->x_start; } min = getMinimum(); diff --git a/Source/Objects/ObjectBase.cpp b/Source/Objects/ObjectBase.cpp index af63043afb..47ee8e5cd7 100644 --- a/Source/Objects/ObjectBase.cpp +++ b/Source/Objects/ObjectBase.cpp @@ -432,7 +432,12 @@ void ObjectBase::paint(Graphics& g) } float ObjectBase::getImageScale() { - return cnv->isScrolling ? cnv->getRenderScale() * 2.0f : cnv->getRenderScale() * std::max(1.0f, getValue(cnv->zoomScale)); + Canvas* topLevel = cnv; + while(auto* nextCnv = topLevel->findParentComponentOfClass()) + { + topLevel = nextCnv; + } + return topLevel->isScrolling ? topLevel->getRenderScale() * 2.0f : topLevel->getRenderScale() * std::max(1.0f, getValue(topLevel->zoomScale)); } ObjectParameters ObjectBase::getParameters() From 0c5ec1a5e3cb28fb064d36f6a9103ee28ae60fcc Mon Sep 17 00:00:00 2001 From: alcomposer Date: Tue, 16 Apr 2024 15:47:14 +0930 Subject: [PATCH 0580/1030] fix label update of palette item when palette group isn't active, and opened after font theme change --- Source/Sidebar/PaletteItem.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Sidebar/PaletteItem.cpp b/Source/Sidebar/PaletteItem.cpp index 3eeab774a8..b6d6e78dc3 100644 --- a/Source/Sidebar/PaletteItem.cpp +++ b/Source/Sidebar/PaletteItem.cpp @@ -55,6 +55,8 @@ PaletteItem::PaletteItem(PluginEditor* e, PaletteDraggableList* parent, ValueTre inlets = iolets.first; outlets = iolets.second; } + + lookAndFeelChanged(); } PaletteItem::~PaletteItem() From 459db539a3f7ccff134435fe7dd4b91ee60fa795 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Tue, 16 Apr 2024 17:29:29 +0930 Subject: [PATCH 0581/1030] limit hole in activity shadow to only when object is canvas (GOP) --- Source/Object.cpp | 7 ++++++- Source/Object.h | 2 ++ Source/Utility/StackShadow.cpp | 24 +++++++++++++----------- Source/Utility/StackShadow.h | 2 +- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index 45724adee9..a971f06443 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -481,6 +481,11 @@ String Object::getType() const return gui ? gui->getType() : String(); } +bool Object::isCanvas() const +{ + return getType() == "canvas"; +} + void Object::triggerOverlayActiveState() { if (!showActiveState) @@ -1216,7 +1221,7 @@ void Object::render(NVGcontext* nvg) if(activityOverlayImage) nvgDeleteImage(nvg, activityOverlayImage); Path objectShadow; objectShadow.addRoundedRectangle(getLocalBounds().reduced(Object::margin - 1), Corners::objectCornerRadius); - activityOverlayImage = StackShadow::createActivityDropShadowImage(nvg, getLocalBounds(), objectShadow, findColour(PlugDataColour::dataColourId), 5.5f, { 0, 0 }, 0); + activityOverlayImage = StackShadow::createActivityDropShadowImage(nvg, getLocalBounds(), objectShadow, findColour(PlugDataColour::dataColourId), 5.5f, { 0, 0 }, 0, isCanvas()); activityOverlayDirty = false; } diff --git a/Source/Object.h b/Source/Object.h index 755ee34300..75c7c1d3c2 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -148,6 +148,8 @@ class Object : public Component bool shouldRenderToFramebuffer(); + bool isCanvas() const; + void setSelected(bool shouldBeSelected); bool selectedFlag = false; bool selectionStateChanged = false; diff --git a/Source/Utility/StackShadow.cpp b/Source/Utility/StackShadow.cpp index 3152bfa771..77ddbc91d9 100644 --- a/Source/Utility/StackShadow.cpp +++ b/Source/Utility/StackShadow.cpp @@ -24,22 +24,24 @@ void StackShadow::renderDropShadow(juce::Graphics& g, juce::Path const& path, ju } -int StackShadow::createActivityDropShadowImage(NVGcontext* nvg, juce::Rectangle bounds, juce::Path const& path, juce::Colour color, int radius, juce::Point offset, int spread) +int StackShadow::createActivityDropShadowImage(NVGcontext* nvg, juce::Rectangle bounds, juce::Path const& path, juce::Colour color, int radius, juce::Point offset, int spread, bool isCanvas) { Image shadow(Image::ARGB, bounds.getWidth(), bounds.getHeight(), true); Graphics g(shadow); // make a hole in the middle of the dropshadow so that GOP doesn't render internal activity shadow - Path outside; - outside.addRectangle(0, 0, bounds.getWidth(), bounds.getHeight()); - outside.setUsingNonZeroWinding(false); - Path inside; - auto boundsRounded = bounds.toFloat().reduced(6.5); - // FIXME: we need to offset by -0.5px because the whole shadow is +0.5 px offset incorrectly, remove this and fix alignment of the whole shadow - boundsRounded.translate(-0.5f, -0.5f); - inside.addRoundedRectangle(boundsRounded, radius - 2.5f, radius - 2.5f); - outside.addPath(inside); - g.reduceClipRegion(outside); + if (isCanvas) { + Path outside; + outside.addRectangle(0, 0, bounds.getWidth(), bounds.getHeight()); + outside.setUsingNonZeroWinding(false); + Path inside; + auto boundsRounded = bounds.toFloat().reduced(6.5); + // FIXME: we need to offset by -0.5px because the whole shadow is +0.5 px offset incorrectly, remove this and fix alignment of the whole shadow + boundsRounded.translate(-0.5f, -0.5f); + inside.addRoundedRectangle(boundsRounded, radius - 2.5f, radius - 2.5f); + outside.addPath(inside); + g.reduceClipRegion(outside); + } renderDropShadow(g, path, color, radius, offset, spread); diff --git a/Source/Utility/StackShadow.h b/Source/Utility/StackShadow.h index ceffbd8d09..30e84186f2 100644 --- a/Source/Utility/StackShadow.h +++ b/Source/Utility/StackShadow.h @@ -17,7 +17,7 @@ struct StackShadow : public juce::DeletedAtShutdown static void renderDropShadow(juce::Graphics& g, juce::Path const& path, juce::Colour color, int radius = 1, juce::Point offset = { 0, 0 }, int spread = 0); - static int createActivityDropShadowImage(NVGcontext* nvg, juce::Rectangle bounds, juce::Path const& path, juce::Colour color, int radius = 1, juce::Point offset = { 0, 0 }, int spread = 0); + static int createActivityDropShadowImage(NVGcontext* nvg, juce::Rectangle bounds, juce::Path const& path, juce::Colour color, int radius = 1, juce::Point offset = { 0, 0 }, int spread = 0, bool isCanvas = false); melatonin::DropShadow* dropShadow; From 1d6c0314c4dd153478545b0fc4afac98b0785c44 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Tue, 16 Apr 2024 18:48:21 +0930 Subject: [PATCH 0582/1030] fix native window dialog issue when dragging native window titlebar --- Source/Dialogs/Dialogs.cpp | 84 +++++++++++++++++++++++++----- Source/Dialogs/Dialogs.h | 53 +------------------ Source/Standalone/PlugDataWindow.h | 13 +++++ 3 files changed, 87 insertions(+), 63 deletions(-) diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index c478eb7917..50b63cd6bf 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -38,6 +38,78 @@ #include "Deken.h" // #include "PatchStorage.h" +#include "Standalone/PlugDataWindow.h" + +Dialog::Dialog(std::unique_ptr* ownerPtr, Component* editor, int childWidth, int childHeight, bool showCloseButton, int margin) +: height(childHeight) +, width(childWidth) +, parentComponent(editor) +, owner(ownerPtr) +, backgroundMargin(margin) +{ + addToDesktop(ComponentPeer::windowIsTemporary); + setVisible(true); + + setBounds(parentComponent->getScreenX(), parentComponent->getScreenY(), parentComponent->getWidth(), parentComponent->getHeight()); + parentComponent->addComponentListener(this); + + if(ProjectInfo::isStandalone) { + if (auto* mainWindow = dynamic_cast(parentComponent->getTopLevelComponent())) + mainWindow->dialog = SafePointer(this); + toFront(true); + } + else { + setAlwaysOnTop(true); + } + setWantsKeyboardFocus(true); + + if (showCloseButton) { + closeButton.reset(getLookAndFeel().createDocumentWindowButton(-1)); + addAndMakeVisible(closeButton.get()); + closeButton->onClick = [this]() { + parentComponent->toFront(true); + closeDialog(); + }; + closeButton->setAlwaysOnTop(true); + } + + // Make sure titlebar buttons are greyed out when a dialog is showing + if (auto* window = dynamic_cast(getTopLevelComponent())) { + if (ProjectInfo::isStandalone) { + if (auto* closeButton = window->getCloseButton()) + closeButton->setEnabled(false); + if (auto* minimiseButton = window->getMinimiseButton()) + minimiseButton->setEnabled(false); + if (auto* maximiseButton = window->getMaximiseButton()) + maximiseButton->setEnabled(false); + } + window->repaint(); + } +} + +void Dialog::mouseDrag(MouseEvent const &e) +{ + if (dragging) { + if (auto mainWindow = dynamic_cast(parentComponent->getTopLevelComponent())) { + mainWindow->movedFromDialog = true; + } + dragger.dragWindow(parentComponent->getTopLevelComponent(), e, nullptr); + dragger.dragWindow(this, e, nullptr); + } +} + +bool Dialog::wantsRoundedCorners() const +{ + // Check if the editor wants rounded corners + if (auto* editor = dynamic_cast(parentComponent)) { + return editor->wantsRoundedCorners(); + } + // Otherwise assume rounded corners for the rest of the UI + else { + return true; + } +} + Component* Dialogs::showTextEditorDialog(String const& text, String filename, std::function callback) { auto* editor = new TextEditorDialog(std::move(filename)); @@ -338,18 +410,6 @@ StringArray DekenInterface::getExternalPaths() return searchPaths; } -bool Dialog::wantsRoundedCorners() const -{ - // Check if the editor wants rounded corners - if (auto* editor = dynamic_cast(parentComponent)) { - return editor->wantsRoundedCorners(); - } - // Otherwise assume rounded corners for the rest of the UI - else { - return true; - } -} - void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent, Point position) { #if JUCE_IOS diff --git a/Source/Dialogs/Dialogs.h b/Source/Dialogs/Dialogs.h index 55a4775ea1..b4a523e18d 100644 --- a/Source/Dialogs/Dialogs.h +++ b/Source/Dialogs/Dialogs.h @@ -16,50 +16,7 @@ class Canvas; class Dialog : public Component, public ComponentListener { public: - Dialog(std::unique_ptr* ownerPtr, Component* editor, int childWidth, int childHeight, bool showCloseButton, int margin = 0) - : height(childHeight) - , width(childWidth) - , parentComponent(editor) - , owner(ownerPtr) - , backgroundMargin(margin) - { - addToDesktop(ComponentPeer::windowIsTemporary); - setVisible(true); - - setBounds(parentComponent->getScreenX(), parentComponent->getScreenY(), parentComponent->getWidth(), parentComponent->getHeight()); - parentComponent->addComponentListener(this); - - if(ProjectInfo::isStandalone) { - toFront(true); - } - else { - setAlwaysOnTop(true); - } - setWantsKeyboardFocus(true); - - if (showCloseButton) { - closeButton.reset(getLookAndFeel().createDocumentWindowButton(-1)); - addAndMakeVisible(closeButton.get()); - closeButton->onClick = [this]() { - parentComponent->toFront(true); - closeDialog(); - }; - closeButton->setAlwaysOnTop(true); - } - - // Make sure titlebar buttons are greyed out when a dialog is showing - if (auto* window = dynamic_cast(getTopLevelComponent())) { - if (ProjectInfo::isStandalone) { - if (auto* closeButton = window->getCloseButton()) - closeButton->setEnabled(false); - if (auto* minimiseButton = window->getMinimiseButton()) - minimiseButton->setEnabled(false); - if (auto* maximiseButton = window->getMaximiseButton()) - maximiseButton->setEnabled(false); - } - window->repaint(); - } - } + Dialog(std::unique_ptr* ownerPtr, Component* editor, int childWidth, int childHeight, bool showCloseButton, int margin = 0); ~Dialog() override { @@ -154,13 +111,7 @@ class Dialog : public Component, public ComponentListener { } } - void mouseDrag(MouseEvent const& e) override - { - if (dragging) { - dragger.dragWindow(parentComponent->getTopLevelComponent(), e, nullptr); - dragger.dragWindow(this, e, nullptr); - } - } + void mouseDrag(MouseEvent const& e) override; void mouseUp(MouseEvent const& e) override { diff --git a/Source/Standalone/PlugDataWindow.h b/Source/Standalone/PlugDataWindow.h index 13aad5843a..44fd113f76 100644 --- a/Source/Standalone/PlugDataWindow.h +++ b/Source/Standalone/PlugDataWindow.h @@ -36,6 +36,7 @@ #include "Utility/MidiDeviceManager.h" #include "../PluginEditor.h" #include "../CanvasViewport.h" +#include "Dialogs/Dialogs.h" // For each OS, we have a different approach to rendering the window shadow // macOS: @@ -413,6 +414,9 @@ class PlugDataWindow : public DocumentWindow public: typedef StandalonePluginHolder::PluginInOuts PluginInOuts; + bool movedFromDialog = false; + SafePointer dialog; + /** Creates a window with a given title and colour. The settings object can be a PropertySet that the class should use to store its settings (it can also be null). If takeOwnershipOfSettings is @@ -623,6 +627,15 @@ class PlugDataWindow : public DocumentWindow repaint(); } + void moved() override + { + if (movedFromDialog) { + movedFromDialog = false; + } else if (dialog) { + dialog.getComponent()->closeDialog(); + } + } + void resized() override { ResizableWindow::resized(); From eb0c90e7f71a6d3c14177f0b1bdc3d0afce28743 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 16 Apr 2024 12:17:24 +0200 Subject: [PATCH 0583/1030] Cleaned up --- Source/Object.cpp | 7 +------ Source/Object.h | 2 -- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index a971f06443..b73fa841ee 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -481,11 +481,6 @@ String Object::getType() const return gui ? gui->getType() : String(); } -bool Object::isCanvas() const -{ - return getType() == "canvas"; -} - void Object::triggerOverlayActiveState() { if (!showActiveState) @@ -1221,7 +1216,7 @@ void Object::render(NVGcontext* nvg) if(activityOverlayImage) nvgDeleteImage(nvg, activityOverlayImage); Path objectShadow; objectShadow.addRoundedRectangle(getLocalBounds().reduced(Object::margin - 1), Corners::objectCornerRadius); - activityOverlayImage = StackShadow::createActivityDropShadowImage(nvg, getLocalBounds(), objectShadow, findColour(PlugDataColour::dataColourId), 5.5f, { 0, 0 }, 0, isCanvas()); + activityOverlayImage = StackShadow::createActivityDropShadowImage(nvg, getLocalBounds(), objectShadow, findColour(PlugDataColour::dataColourId), 5.5f, { 0, 0 }, 0, gui && gui->getCanvas()); activityOverlayDirty = false; } diff --git a/Source/Object.h b/Source/Object.h index 75c7c1d3c2..755ee34300 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -148,8 +148,6 @@ class Object : public Component bool shouldRenderToFramebuffer(); - bool isCanvas() const; - void setSelected(bool shouldBeSelected); bool selectedFlag = false; bool selectionStateChanged = false; From 74034454dcffa2f9c14b3291eecb230e8f17c1cf Mon Sep 17 00:00:00 2001 From: Alexander Chalikiopoulos Date: Tue, 16 Apr 2024 14:31:16 +0200 Subject: [PATCH 0584/1030] remove line packaging Manual dir --- Resources/Scripts/package_resources.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index 2f7673845d..f7853c7c62 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -113,7 +113,6 @@ def splitFile(file, num_files): copyDir("../../Libraries/pd-else/Code_source/Compiled/audio/sfont~/sf", "Extra/else/sf") copyDir("../Patches/Presets", "./Extra/Presets") copyDir("../Patches/Palettes", "./Extra/palette") -copyDir("../Documentation/Manual", "./Extra/Manual") globCopy("../../Libraries/pure-data/doc/sound/*", "Extra/else") # pd-lua From 30c307e4011d19ba76d54c1fc4860df5304b31e1 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 16 Apr 2024 14:48:19 +0200 Subject: [PATCH 0585/1030] Silence warning --- Source/Sidebar/Palettes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Sidebar/Palettes.h b/Source/Sidebar/Palettes.h index 919cf7cf7a..f91c9042d7 100644 --- a/Source/Sidebar/Palettes.h +++ b/Source/Sidebar/Palettes.h @@ -317,7 +317,7 @@ class PaletteComponent : public Component { delete paletteDraggableList; } - void lookAndFeelChanged() + void lookAndFeelChanged() override { nameLabel.setFont(Fonts::getCurrentFont().boldened()); } From 256c66d0016fa399b89aeecbe4045c524fc45594 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 16 Apr 2024 18:03:59 +0200 Subject: [PATCH 0586/1030] Fix object/connection theme settings not working --- Source/Dialogs/ThemePanel.h | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/Source/Dialogs/ThemePanel.h b/Source/Dialogs/ThemePanel.h index 2b2ed2da1e..3b13e6ec96 100644 --- a/Source/Dialogs/ThemePanel.h +++ b/Source/Dialogs/ThemePanel.h @@ -517,21 +517,42 @@ class ThemePanel : public SettingsDialogPanel PlugDataLook::setDefaultFont(fontValue.toString()); SettingsFile::getInstance()->setProperty("default_font", fontValue.getValue()); pd->updateAllEditorsLNF(); - return; } + auto themeTree = SettingsFile::getInstance()->getColourThemesTree(); if (v.refersToSameSourceAs(swatches[PlugDataLook::currentTheme]["dashed_signal_connections"]) || v.refersToSameSourceAs(swatches[PlugDataLook::currentTheme]["straight_connections"]) || v.refersToSameSourceAs(swatches[PlugDataLook::currentTheme]["square_iolets"]) || v.refersToSameSourceAs(swatches[PlugDataLook::currentTheme]["square_object_corners"]) || v.refersToSameSourceAs(swatches[PlugDataLook::currentTheme]["thin_connections"])) { - + for (auto theme : themeTree) { + auto themeName = theme.getProperty("theme").toString(); + if (v.refersToSameSourceAs(swatches[themeName]["dashed_signal_connections"])) { + theme.setProperty("dashed_signal_connections", v.toString(), nullptr); + } + else if(v.refersToSameSourceAs(swatches[themeName]["straight_connections"])) + { + theme.setProperty("straight_connections", v.toString(), nullptr); + } + else if(v.refersToSameSourceAs(swatches[themeName]["square_iolets"])) + { + theme.setProperty("square_iolets", v.toString(), nullptr); + } + else if(v.refersToSameSourceAs(swatches[themeName]["square_object_corners"])) + { + theme.setProperty("square_object_corners", v.toString(), nullptr); + } + else if(v.refersToSameSourceAs(swatches[themeName]["thin_connections"])) + { + theme.setProperty("thin_connections", v.toString(), nullptr); + } + } + pd->setTheme(PlugDataLook::currentTheme, true); return; } - - auto themeTree = SettingsFile::getInstance()->getColourThemesTree(); + for (auto theme : themeTree) { auto themeName = theme.getProperty("theme").toString(); From d77c9dd4b506e5df8cb22d568382dbab3d1aa7e9 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 17 Apr 2024 15:21:24 +0930 Subject: [PATCH 0587/1030] give focus to new window for callout area (when semitransparent) --- Source/PluginEditor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 001f26f58c..fffcfab233 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -482,6 +482,7 @@ CallOutBox& PluginEditor::showCalloutBox(std::unique_ptr content, Rec { content->addComponentListener(new CalloutDeletionListener(this)); calloutArea->addToDesktop(ComponentPeer::windowIsTemporary); + calloutArea->toFront(true); auto bounds = calloutArea->getLocalArea(nullptr, screenBounds); return CallOutBox::launchAsynchronously(std::move(content), bounds, calloutArea.get()); } From db1bd192f9a4d1b2d4831e8fafb80b34c6c95ba7 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 18 Apr 2024 15:32:51 +0930 Subject: [PATCH 0588/1030] Initial work on custom plugin latency object --- .../Documentation/plugdata/plugin_latency.md | 20 +++++++++++++++++++ Resources/Patches/plugin_latency-help.pd | 1 + Resources/Patches/plugin_latency.pd | 5 +++++ Resources/Scripts/package_resources.py | 2 ++ Source/Pd/Instance.cpp | 17 +++++++++++++--- Source/Pd/Instance.h | 3 +++ Source/PluginProcessor.cpp | 12 +++++++++++ Source/PluginProcessor.h | 7 ++++++- 8 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 Resources/Documentation/plugdata/plugin_latency.md create mode 100644 Resources/Patches/plugin_latency-help.pd create mode 100644 Resources/Patches/plugin_latency.pd diff --git a/Resources/Documentation/plugdata/plugin_latency.md b/Resources/Documentation/plugdata/plugin_latency.md new file mode 100644 index 0000000000..b4cf1db0cc --- /dev/null +++ b/Resources/Documentation/plugdata/plugin_latency.md @@ -0,0 +1,20 @@ +--- +title: plugin_latency + +description: set plugin latency compensation for DAW + +categories: +- object + +pdcategories: PlugData, UI + +arguments: + +inlets: + 1st: + - type: float + description: set DAW latency compensation value for plugin + +draft: false +--- +[plugin_latency] sends latency compensation value to the DAW. diff --git a/Resources/Patches/plugin_latency-help.pd b/Resources/Patches/plugin_latency-help.pd new file mode 100644 index 0000000000..b837debff3 --- /dev/null +++ b/Resources/Patches/plugin_latency-help.pd @@ -0,0 +1 @@ +#N canvas 536 141 700 308 12; \ No newline at end of file diff --git a/Resources/Patches/plugin_latency.pd b/Resources/Patches/plugin_latency.pd new file mode 100644 index 0000000000..9470269910 --- /dev/null +++ b/Resources/Patches/plugin_latency.pd @@ -0,0 +1,5 @@ +#N canvas 536 141 700 308 12; +#X obj 68 110 inlet; +#X text 48 74 send plugin latency compensation value to DAW; +#X obj 68 139 s latency_compensation; +#X connect 0 0 2 0; diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index f7853c7c62..f684a2cc90 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -83,6 +83,7 @@ def splitFile(file, num_files): copyFile("../Patches/playhead.pd", "./Abstractions") copyFile("../Patches/param.pd", "./Abstractions") copyFile("../Patches/daw_storage.pd", "./Abstractions") +copyFile("../Patches/plugin_latency.pd", "./Abstractions") #copyFile("../Patches/beat.pd", "./Abstractions") globMove("./Abstractions/*-help.pd", "./Documentation/5.reference") @@ -93,6 +94,7 @@ def splitFile(file, num_files): copyFile("../Patches/param-help.pd", "./Documentation/5.reference") copyFile("../Patches/playhead-help.pd", "./Documentation/5.reference") copyFile("../Patches/daw_storage-help.pd", "./Documentation/5.reference") +copyFile("../Patches/plugin_latency-help.pd", "./Documentation/5.reference") globCopy("../../Libraries/cyclone/cyclone_objects/abstractions/*.pd", "./Abstractions/cyclone") copyDir("../../Libraries/cyclone/documentation/help_files", "./Documentation/10.cyclone") diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 650435abeb..6df29ae777 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -142,6 +142,7 @@ Instance::~Instance() pd_free(static_cast(midiReceiver)); pd_free(static_cast(printReceiver)); pd_free(static_cast(parameterReceiver)); + pd_free(static_cast(pluginLatencyReceiver)); pd_free(static_cast(parameterChangeReceiver)); // JYG added this @@ -195,6 +196,9 @@ void Instance::initialisePd(String& pdlua_version) parameterReceiver = pd::Setup::createReceiver(this, "param", reinterpret_cast(internal::instance_multi_bang), reinterpret_cast(internal::instance_multi_float), reinterpret_cast(internal::instance_multi_symbol), reinterpret_cast(internal::instance_multi_list), reinterpret_cast(internal::instance_multi_message)); + pluginLatencyReceiver = pd::Setup::createReceiver(this, "latency_compensation", reinterpret_cast(internal::instance_multi_bang), reinterpret_cast(internal::instance_multi_float), reinterpret_cast(internal::instance_multi_symbol), + reinterpret_cast(internal::instance_multi_list), reinterpret_cast(internal::instance_multi_message)); + // JYG added This dataBufferReceiver = pd::Setup::createReceiver(this, "to_daw_databuffer", reinterpret_cast(internal::instance_multi_bang), reinterpret_cast(internal::instance_multi_float), reinterpret_cast(internal::instance_multi_symbol), reinterpret_cast(internal::instance_multi_list), reinterpret_cast(internal::instance_multi_message)); @@ -468,20 +472,27 @@ void Instance::processMessage(Message mess) if (mess.destination == "pd") { receiveSysMessage(mess.selector, mess.list); } - if (mess.destination == "param" && mess.list.size() >= 2) { + if (mess.destination == "latency_compensation" && mess.list.size() == 1) { + if (!mess.list[0].isFloat()) + return; + performLatencyCompensationChange(mess.list[0].getFloat()); + } + else if (mess.destination == "param" && mess.list.size() >= 2) { if (!mess.list[0].isSymbol() || !mess.list[1].isFloat()) return; auto name = mess.list[0].toString(); float value = mess.list[1].getFloat(); performParameterChange(0, name, value); - } else if (mess.destination == "param_change" && mess.list.size() >= 2) { + } + else if (mess.destination == "param_change" && mess.list.size() >= 2) { if (!mess.list[0].isSymbol() || !mess.list[1].isFloat()) return; auto name = mess.list[0].toString(); int state = mess.list[1].getFloat() != 0; performParameterChange(1, name, state); // JYG added This - } else if (mess.destination == "to_daw_databuffer") { + } + else if (mess.destination == "to_daw_databuffer") { fillDataBuffer(mess.list); } } diff --git a/Source/Pd/Instance.h b/Source/Pd/Instance.h index 47f33817cd..7d810b3858 100644 --- a/Source/Pd/Instance.h +++ b/Source/Pd/Instance.h @@ -259,6 +259,8 @@ class Instance { virtual void performParameterChange(int type, String const& name, float value) { } + virtual void performLatencyCompensationChange(float value) { } + // JYG added this virtual void fillDataBuffer(std::vector const& list) { } virtual void parseDataBuffer(XmlElement const& xml) { } @@ -300,6 +302,7 @@ class Instance { void* atoms = nullptr; void* messageReceiver = nullptr; void* parameterReceiver = nullptr; + void* pluginLatencyReceiver = nullptr; void* parameterChangeReceiver = nullptr; void* midiReceiver = nullptr; void* printReceiver = nullptr; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index c129c4c253..bbc062a9d2 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1626,6 +1626,18 @@ void PluginProcessor::showTextEditor(unsigned long ptr, Rectangle bounds, S })); } +void PluginProcessor::handleAsyncUpdate() +{ + setLatencySamples(customLatencySamples); +} + +// set custom plugin latency +void PluginProcessor::performLatencyCompensationChange(float value) +{ + customLatencySamples = floor(value); + triggerAsyncUpdate(); +} + void PluginProcessor::performParameterChange(int type, String const& name, float value) { // Type == 1 means it sets the change gesture state diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index de674ceefa..a656555335 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -27,7 +27,7 @@ class StatusbarSource; struct PlugDataLook; class PluginEditor; class ConnectionMessageDisplay; -class PluginProcessor : public AudioProcessor +class PluginProcessor : public AudioProcessor, public AsyncUpdater , public pd::Instance, public SettingsFileListener { public: PluginProcessor(); @@ -115,6 +115,8 @@ class PluginProcessor : public AudioProcessor Array getEditors() const; void performParameterChange(int type, String const& name, float value) override; + + void performLatencyCompensationChange(float value) override; // Jyg added this void fillDataBuffer(std::vector const& list) override; @@ -172,6 +174,9 @@ class PluginProcessor : public AudioProcessor Component::SafePointer connectionListener; private: + void handleAsyncUpdate() override; + int customLatencySamples = 0; + SmoothedValue smoothedGain; int audioAdvancement = 0; From c6b7d084d0717c005c873a4f6ec2bd8949391cac Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 18 Apr 2024 16:45:36 +0200 Subject: [PATCH 0589/1030] Updated JUCE and nanovg --- Libraries/JUCE | 2 +- Libraries/nanovg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index 7c364fcb5d..a7e425410c 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit 7c364fcb5d540278df318688f336f8fb78727886 +Subproject commit a7e425410cbf9b4d667dd1a3c289b2e70b9f0e78 diff --git a/Libraries/nanovg b/Libraries/nanovg index 4aa0b787d0..2312f575b0 160000 --- a/Libraries/nanovg +++ b/Libraries/nanovg @@ -1 +1 @@ -Subproject commit 4aa0b787d0125023ef70ba57648abb7f3611efce +Subproject commit 2312f575b0ee7765b898b46882e47dcb991a6f71 From 796bf75e1f27034ca40f8bb27bb005a2110d6fda Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 18 Apr 2024 16:45:45 +0200 Subject: [PATCH 0590/1030] Minor optimisations --- Source/NVGSurface.h | 1 + Source/ObjectGrid.cpp | 22 +++++++++++-- Source/Objects/GraphOnParent.h | 14 ++++++++- Source/Objects/MessageObject.h | 2 ++ Source/Objects/ObjectBase.h | 50 +++++++++++++++++++++++++++--- Source/Objects/OpenFileObject.h | 7 +++++ Source/Standalone/PlugDataWindow.h | 2 +- 7 files changed, 90 insertions(+), 8 deletions(-) diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 8035d2dd9e..930cb4088c 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -77,6 +77,7 @@ public Component, public Timer void addNVGContextListener(NVGContextListener* listener); void removeNVGContextListener(NVGContextListener* listener); + Rectangle getInvalidArea() { return invalidArea; } float getRenderScale() const; diff --git a/Source/ObjectGrid.cpp b/Source/ObjectGrid.cpp index 40c9b1c715..bb6f4c430c 100644 --- a/Source/ObjectGrid.cpp +++ b/Source/ObjectGrid.cpp @@ -11,6 +11,7 @@ #include "ObjectGrid.h" #include "Object.h" #include "Canvas.h" +#include "PluginEditor.h" #include "Connection.h" ObjectGrid::ObjectGrid(Canvas* cnv) : cnv(cnv) @@ -400,6 +401,10 @@ void ObjectGrid::setIndicator(int idx, Line line, float scale) startTimerHz(60); } } + else { + auto lineArea = cnv->editor->nvgSurface.getLocalArea(cnv, Rectangle(lines[idx].getStart(), lines[idx].getEnd()).expanded(2)); + cnv->editor->nvgSurface.invalidateArea(lineArea); + } lines[idx] = line; @@ -410,11 +415,22 @@ void ObjectGrid::setIndicator(int idx, Line line, float scale) startTimerHz(60); } } + else { + auto lineArea = cnv->editor->nvgSurface.getLocalArea(cnv, Rectangle(lines[idx].getStart(), lines[idx].getEnd()).expanded(2)); + cnv->editor->nvgSurface.invalidateArea(lineArea); + } } void ObjectGrid::timerCallback() { - cnv->repaint(); + if(lines[0].getLength() != 0 && lineAlpha[0] != 0.0f) { + auto lineArea = cnv->editor->nvgSurface.getLocalArea(cnv, Rectangle(lines[0].getStart(), lines[0].getEnd()).expanded(2)); + cnv->editor->nvgSurface.invalidateArea(lineArea); + } + if(lines[1].getLength() != 0 && lineAlpha[1] != 0.0f) { + auto lineArea = cnv->editor->nvgSurface.getLocalArea(cnv, Rectangle(lines[1].getStart(), lines[1].getEnd()).expanded(2)); + cnv->editor->nvgSurface.invalidateArea(lineArea); + } bool done = true; // TODO: use multi-timer? for(int i = 0; i < 2; i++) { @@ -427,7 +443,9 @@ void ObjectGrid::timerCallback() } } - if(done) stopTimer(); + if(done) { + stopTimer(); + } } void ObjectGrid::render(NVGcontext* nvg) diff --git a/Source/Objects/GraphOnParent.h b/Source/Objects/GraphOnParent.h index 95a3b52578..c6f001ffff 100644 --- a/Source/Objects/GraphOnParent.h +++ b/Source/Objects/GraphOnParent.h @@ -220,13 +220,25 @@ class GraphOnParent final : public ObjectBase { textRenderer.renderText(nvg, text, Fonts::getDefaultFont().withHeight(13), object->findColour(PlugDataColour::canvasTextColourId), Rectangle(5, 0, getWidth() - 5, 16), getImageScale()); } + Canvas* topLevel = cnv; + while(auto* nextCnv = topLevel->findParentComponentOfClass()) + { + topLevel = nextCnv; + } + auto b = getLocalBounds().toFloat(); if(canvas) { + auto invalidArea = cnv->editor->nvgSurface.getInvalidArea(); + + if(topLevel->isScrolling) invalidArea = canvas->getLocalBounds(); + else if(!invalidArea.isEmpty()) invalidArea = canvas->getLocalArea(&cnv->editor->nvgSurface, invalidArea).expanded(1); + else return; + nvgSave(nvg); nvgIntersectRoundedScissor(nvg, b.getX() + 0.75f, b.getY() + 0.75f, b.getWidth() - 1.5f, b.getHeight() - 1.5f, Corners::objectCornerRadius); nvgTranslate(nvg, canvas->getX(), canvas->getY()); - canvas->performRender(nvg, canvas->getLocalBounds()); // TODO: more precise invalidation? + canvas->performRender(nvg, invalidArea); // TODO: more precise invalidation? nvgRestore(nvg); } diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index 9ef9b848d0..64edca634b 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -91,6 +91,8 @@ class MessageObject final : public ObjectBase void updateTextLayout() { + if(cnv->isGraph) return; // Text layouting is expensive, so skip if it's not necessary + auto objText = editor ? editor->getText() : objectText; if (editor && cnv->suggestor && cnv->suggestor->getText().isNotEmpty()) { objText = cnv->suggestor->getText(); diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index ed7fc3dd67..acf5f3258c 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -12,6 +12,7 @@ #include "ObjectParameters.h" #include "Utility/SynchronousValue.h" #include "Utility/NVGComponent.h" +#include "Utility/CachedTextRender.h" class PluginProcessor; class Canvas; @@ -22,23 +23,64 @@ class Patch; class Object; -class ObjectLabel : public Label, public NVGComponent { +class ObjectLabel : public Label, public NVGComponent, public NVGContextListener { - NVGImageRenderer imageRenderer; + hash32 lastTextHash = 0; + int imageId = 0; + int lastWidth = 0, lastHeight = 0; + float lastScale = 1.0f; + NVGSurface& surface; public: - explicit ObjectLabel(NVGSurface& surface) : NVGComponent(this), imageRenderer(surface) + explicit ObjectLabel(NVGSurface& s) : NVGComponent(this), surface(s) { setJustificationType(Justification::centredLeft); setBorderSize(BorderSize(0, 0, 0, 0)); setMinimumHorizontalScale(0.2f); setEditable(false, false); setInterceptsMouseClicks(false, false); + surface.addNVGContextListener(this); + } + + ~ObjectLabel() + { + surface.removeNVGContextListener(this); + } + + void nvgContextDeleted(NVGcontext* nvg) { + if(imageId) nvgDeleteImage(nvg, imageId); + imageId = 0; } void renderLabel(NVGcontext* nvg, float scale) { - imageRenderer.renderComponentFromImage(nvg, *this, scale); + auto textHash = hash(getText()); + if(!imageId || lastTextHash != textHash || lastScale != scale || lastWidth != getWidth() || lastHeight != getHeight()) + { + updateImage(nvg, scale); + lastTextHash = textHash; + lastScale = scale; + lastWidth = getWidth(); + lastHeight = getHeight(); + } + + nvgBeginPath(nvg); + nvgRect(nvg, 0, 0, getWidth() + 1, getHeight()); + nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, getWidth() + 1, getHeight(), 0, imageId, 1.0f)); + nvgFill(nvg); + } + + void updateImage(NVGcontext* nvg, float scale) + { + auto componentImage = createComponentSnapshot(Rectangle(0, 0, getWidth() + 1, getHeight()), false, scale); + + if(imageId && lastWidth == getWidth() && lastHeight == getHeight()) { + imageId = NVGImageRenderer::convertImage(nvg, componentImage, imageId); + } + else { + if(imageId) nvgDeleteImage(nvg, imageId); + imageId = NVGImageRenderer::convertImage(nvg, componentImage); + } } private: diff --git a/Source/Objects/OpenFileObject.h b/Source/Objects/OpenFileObject.h index 794bfad56d..a86af76659 100644 --- a/Source/Objects/OpenFileObject.h +++ b/Source/Objects/OpenFileObject.h @@ -87,6 +87,8 @@ class OpenFileObject final : public TextBase { void updateTextLayout() override { + if(cnv->isGraph) return; // Text layouting is expensive, so skip if it's not necessary + auto objText = getLinkText(); if (editor && cnv->suggestor && cnv->suggestor->getText().isNotEmpty()) { objText = cnv->suggestor->getText(); @@ -203,6 +205,11 @@ class OpenFileObject final : public TextBase { updateTextLayout(); repaint(); } + + bool hideInGraph() override + { + return false; + } void mouseDown(MouseEvent const& e) override { diff --git a/Source/Standalone/PlugDataWindow.h b/Source/Standalone/PlugDataWindow.h index 44fd113f76..8834981a7b 100644 --- a/Source/Standalone/PlugDataWindow.h +++ b/Source/Standalone/PlugDataWindow.h @@ -478,7 +478,7 @@ class PlugDataWindow : public DocumentWindow auto* pdEditor = dynamic_cast(editor); setUsingNativeTitleBar(nativeWindow); - + pdEditor->nvgSurface.detachContext(); clearAllBuffers(); From c34c6f80462098381f7573bec4777465c73d5857 Mon Sep 17 00:00:00 2001 From: Timothy Schoen <44585538+timothyschoen@users.noreply.github.com> Date: Fri, 19 Apr 2024 02:04:59 +0200 Subject: [PATCH 0591/1030] Update NVGSurface.cpp --- Source/NVGSurface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 99ee6fc942..1a772df1e4 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -298,7 +298,6 @@ void NVGSurface::render() if(!invalidArea.isEmpty()) { auto invalidated = invalidArea.expanded(1); - invalidArea = Rectangle(0, 0, 0, 0); // First, draw only the invalidated region to a separate framebuffer // I've found that nvgScissor doesn't always clip everything, meaning that there will be graphical glitches if we don't do this @@ -334,6 +333,7 @@ void NVGSurface::render() nvgBindFramebuffer(nullptr); needsBufferSwap = true; + invalidArea = Rectangle(0, 0, 0, 0); } #if ENABLE_FPS_COUNT From 104947f08872867a7e822862f1ab26ec438e251f Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 19 Apr 2024 15:08:22 +0930 Subject: [PATCH 0592/1030] use switch for processing system messages --- Source/Pd/Instance.cpp | 65 ++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 6df29ae777..78902c0637 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -467,33 +467,44 @@ void Instance::sendMessage(char const* receiver, char const* msg, std::vectors_thing, msg, list); } -void Instance::processMessage(Message mess) -{ - if (mess.destination == "pd") { - receiveSysMessage(mess.selector, mess.list); - } - if (mess.destination == "latency_compensation" && mess.list.size() == 1) { - if (!mess.list[0].isFloat()) - return; - performLatencyCompensationChange(mess.list[0].getFloat()); - } - else if (mess.destination == "param" && mess.list.size() >= 2) { - if (!mess.list[0].isSymbol() || !mess.list[1].isFloat()) - return; - auto name = mess.list[0].toString(); - float value = mess.list[1].getFloat(); - performParameterChange(0, name, value); - } - else if (mess.destination == "param_change" && mess.list.size() >= 2) { - if (!mess.list[0].isSymbol() || !mess.list[1].isFloat()) - return; - auto name = mess.list[0].toString(); - int state = mess.list[1].getFloat() != 0; - performParameterChange(1, name, state); - // JYG added This - } - else if (mess.destination == "to_daw_databuffer") { - fillDataBuffer(mess.list); +void Instance::processMessage(Message mess) { + const auto dest = hash(mess.destination); + + switch (dest) { + case hash("pd"): + receiveSysMessage(mess.selector, mess.list); + break; + case hash("latency_compensation"): + if (mess.list.size() == 1) { + if (!mess.list[0].isFloat()) + return; + performLatencyCompensationChange(mess.list[0].getFloat()); + } + break; + case hash("param"): + if (mess.list.size() >= 2) { + if (!mess.list[0].isSymbol() || !mess.list[1].isFloat()) + return; + auto name = mess.list[0].toString(); + float value = mess.list[1].getFloat(); + performParameterChange(0, name, value); + } + break; + case hash("param_change"): + if (mess.list.size() >= 2) { + if (!mess.list[0].isSymbol() || !mess.list[1].isFloat()) + return; + auto name = mess.list[0].toString(); + int state = mess.list[1].getFloat() != 0; + performParameterChange(1, name, state); + } + break; + // JYG added this + case hash("to_daw_databuffer"): + fillDataBuffer(mess.list); + break; + default: + break; } } From c578aad26940896dc144469c721f9ef8e612a95a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Apr 2024 15:17:07 +0200 Subject: [PATCH 0593/1030] Performance improvements --- Source/NVGSurface.cpp | 6 ++++-- Source/Object.cpp | 1 + Source/Objects/SliderObject.h | 1 + Source/Utility/Config.h | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 99ee6fc942..36014457e3 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -256,6 +256,7 @@ void NVGSurface::removeNVGContextListener(NVGContextListener* listener) void NVGSurface::render() { + auto startTime = Time::getMillisecondCounter(); bool hasCanvas = editor->pluginMode != nullptr; for(auto* split : editor->splitView.splits) { @@ -438,13 +439,14 @@ void NVGSurface::render() needsBufferSwap = false; } + auto elapsed = Time::getMillisecondCounter() - startTime; // We update frambuffers after we call swapBuffers to make sure the frame is on time - if(isAttached()) { + if(isAttached() && elapsed < 14) { for(auto* split : editor->splitView.splits) { if(auto* cnv = split->getTabComponent()->getCurrentCanvas()) { - cnv->updateFramebuffers(nvg, cnv->getLocalBounds(), 8); + cnv->updateFramebuffers(nvg, cnv->getLocalBounds(), 14 - elapsed); } } } diff --git a/Source/Object.cpp b/Source/Object.cpp index b73fa841ee..0bf2a1e6ce 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -157,6 +157,7 @@ void Object::nvgContextDeleted(NVGcontext* nvg) fb = nullptr; if(activityOverlayImage) nvgDeleteImage(nvg, activityOverlayImage); activityOverlayImage = 0; + fbDirty = true; } void Object::timerCallback() diff --git a/Source/Objects/SliderObject.h b/Source/Objects/SliderObject.h index 3eb9a4aaa0..8d140335fa 100644 --- a/Source/Objects/SliderObject.h +++ b/Source/Objects/SliderObject.h @@ -17,6 +17,7 @@ class ReversibleSlider : public Slider, public NVGComponent { setScrollWheelEnabled(false); getProperties().set("Style", "SliderObject"); setVelocityModeParameters(1.0f, 1, 0.0f, false); + setRepaintsOnMouseActivity(false); } ~ReversibleSlider() override { } diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index 7e7297f4b8..589934d4a3 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -9,7 +9,7 @@ using namespace juce; #ifndef ENABLE_OBJECT_FB_DEBUGGING -#define ENABLE_OBJECT_FB_DEBUGGING 0 +#define ENABLE_OBJECT_FB_DEBUGGING 1 #endif #ifndef ENABLE_FB_DEBUGGING From d1e144e3a8a4588ac91551844dc62064211fa868 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Apr 2024 15:21:40 +0200 Subject: [PATCH 0594/1030] Disable debugging --- Source/Utility/Config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index 589934d4a3..7e7297f4b8 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -9,7 +9,7 @@ using namespace juce; #ifndef ENABLE_OBJECT_FB_DEBUGGING -#define ENABLE_OBJECT_FB_DEBUGGING 1 +#define ENABLE_OBJECT_FB_DEBUGGING 0 #endif #ifndef ENABLE_FB_DEBUGGING From 4882c5460512ef76c47673c24159b6360254ddeb Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Apr 2024 22:52:12 +0200 Subject: [PATCH 0595/1030] GPU acceleration for pdlua graphics --- Libraries/pd-lua | 2 +- Source/Objects/LuaObject.h | 368 +++++++++++++++++++++---------------- 2 files changed, 215 insertions(+), 155 deletions(-) diff --git a/Libraries/pd-lua b/Libraries/pd-lua index c4d26fa8fd..7638b2b82b 160000 --- a/Libraries/pd-lua +++ b/Libraries/pd-lua @@ -1 +1 @@ -Subproject commit c4d26fa8fdbb9b993fdf6bcb40ede43b0f8107e9 +Subproject commit 7638b2b82bc72fb823b4056cf2060c72fa873ba0 diff --git a/Source/Objects/LuaObject.h b/Source/Objects/LuaObject.h index 5c69248172..371c9a65a8 100644 --- a/Source/Objects/LuaObject.h +++ b/Source/Objects/LuaObject.h @@ -19,23 +19,57 @@ void pdlua_gfx_mouse_move(t_pdlua* o, int x, int y); void pdlua_gfx_mouse_drag(t_pdlua* o, int x, int y); void pdlua_gfx_repaint(t_pdlua* o, int firsttime); } - -class LuaObject : public ObjectBase, public AsyncUpdater { - std::unique_ptr graphics; +class LuaObject : public ObjectBase, public Timer, public NVGContextListener { + Colour currentColour; CriticalSection bufferSwapLock; - Image firstBuffer; - Image secondBuffer; - Image* drawBuffer = &firstBuffer; - Image* displayBuffer = &secondBuffer; bool isSelected = false; Value zoomScale; std::unique_ptr textEditor; std::unique_ptr saveDialog; + + NVGframebuffer* framebuffer = nullptr; + int framebufferWidth = 0, framebufferHeight = 0; + + struct LuaGuiMessage { + t_symbol* symbol; + std::vector data; + int size; + + LuaGuiMessage() {}; + + LuaGuiMessage(t_symbol* sym, int argc, t_atom* argv) + : symbol(sym) + { + data = std::vector(argv, argv + argc); + size = argc; + } + + LuaGuiMessage(LuaGuiMessage const& other) noexcept + { + symbol = other.symbol; + size = other.size; + data = other.data; + } + + LuaGuiMessage& operator=(LuaGuiMessage const& other) noexcept + { + // Check for self-assignment + if (this != &other) { + symbol = other.symbol; + size = other.size; + data = other.data; + } + + return *this; + } + }; + + std::vector guiCommandBuffer; + moodycamel::ReaderWriterQueue guiMessageQueue; - static inline std::map currentPaths = std::map(); static inline std::map> allDrawTargets = std::map>(); public: @@ -48,9 +82,25 @@ class LuaObject : public ObjectBase, public AsyncUpdater { allDrawTargets[pdlua.get()].push_back(this); } + cnv->editor->nvgSurface.addNVGContextListener(this); + parentHierarchyChanged(); + startTimerHz(60); + } + + ~LuaObject() + { + if(auto pdlua = ptr.get()) + { + auto& listeners = allDrawTargets[pdlua.get()]; + listeners.erase(std::remove(listeners.begin(), listeners.end(), this), listeners.end()); + } + + cnv->editor->nvgSurface.removeNVGContextListener(this); + zoomScale.removeListener(this); } + // We can only attach the zoomscale to canvas after the canvas has been added to its own parent // When initialising, this isn't always the case void parentHierarchyChanged() override @@ -69,15 +119,10 @@ class LuaObject : public ObjectBase, public AsyncUpdater { } } - ~LuaObject() - { - if(auto pdlua = ptr.get()) - { - auto& listeners = allDrawTargets[pdlua.get()]; - listeners.erase(std::remove(listeners.begin(), listeners.end(), this), listeners.end()); - } - zoomScale.removeListener(this); - } + void nvgContextDeleted(NVGcontext* nvg) override { + if(framebuffer) nvgDeleteFramebuffer(framebuffer); + framebuffer = nullptr; + }; Rectangle getPdBounds() override { @@ -159,16 +204,14 @@ class LuaObject : public ObjectBase, public AsyncUpdater { sendRepaintMessage(); } - void paint(Graphics& g) override + void render(NVGcontext* nvg) override { - if(isSelected != object->isSelected()) - { - isSelected = object->isSelected(); - sendRepaintMessage(); + if(framebuffer) { + nvgBeginPath(nvg); + nvgRect(nvg, 0, 0, getWidth() + 1, getHeight()); + nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, getWidth() + 1, getHeight(), 0, framebuffer->image, 1.0f)); + nvgFill(nvg); } - - ScopedLock swapLock(bufferSwapLock); - g.drawImage(*displayBuffer, getLocalBounds().toFloat()); } void valueChanged(Value& v) override @@ -176,72 +219,11 @@ class LuaObject : public ObjectBase, public AsyncUpdater { sendRepaintMessage(); } - void handleAsyncUpdate() override - { - repaint(); - } - - static void receiveLuaPathMessage(t_gpointer* path, t_symbol* sym, int argc, t_atom* argv) - { - switch(hash(sym->s_name)) { - case hash("lua_start_path"): { - if (argc >= 2) { - auto& currentPath = currentPaths[path]; - float x = atom_getfloat(argv); - float y = atom_getfloat(argv + 1); - currentPath.startNewSubPath(x, y); - } - break; - } - case hash("lua_line_to"): { - if (argc >= 2) { - auto& currentPath = currentPaths[path]; - float x = atom_getfloat(argv); - float y = atom_getfloat(argv + 1); - currentPath.lineTo(x, y); - } - break; - } - case hash("lua_quad_to"): { - if (argc >= 4) { // Assuming quad_to takes 3 arguments - auto& currentPath = currentPaths[path]; - float x1 = atom_getfloat(argv); - float y1 = atom_getfloat(argv + 1); - float x2 = atom_getfloat(argv + 2); - float y2 = atom_getfloat(argv + 3); - currentPath.quadraticTo(x1, y1, x2, y2); - } - break; - } - case hash("lua_cubic_to"): { - if (argc >= 5) { // Assuming cubic_to takes 4 arguments - auto& currentPath = currentPaths[path]; - float x1 = atom_getfloat(argv); - float y1 = atom_getfloat(argv + 1); - float x2 = atom_getfloat(argv + 2); - float y2 = atom_getfloat(argv + 3); - float x3 = atom_getfloat(argv + 4); - float y3 = atom_getfloat(argv + 5); - currentPath.cubicTo(x1, y1, x2, y2, x3, y3); - } - break; - } - case hash("lua_close_path"): { - auto& currentPath = currentPaths[path]; - currentPath.closeSubPath(); - break; - } - case hash("lua_free_path"): { - currentPaths.erase(path); - break; - } - default: - break; - } - } - - void receiveLuaPaintMessage(t_symbol* sym, int argc, t_atom* argv) + void handleGuiMessage(t_symbol* sym, int argc, t_atom* argv) { + NVGcontext* nvg = cnv->editor->nvgSurface.getRawContext(); + if(!nvg) return; + auto hashsym = hash(sym->s_name); // First check functions that don't need an active graphics context, of modify the active graphics context switch(hashsym) @@ -253,34 +235,31 @@ class LuaObject : public ObjectBase, public AsyncUpdater { int imageHeight = std::ceil(getHeight() * scale); if(!imageWidth || !imageHeight) return; - if(drawBuffer->getWidth() != imageWidth || drawBuffer->getHeight() != imageHeight) + if(!framebuffer || framebufferWidth != imageWidth || framebufferHeight != imageHeight) { - *drawBuffer = Image(Image::PixelFormat::ARGB, imageWidth, imageHeight, true); + nvgBindFramebuffer(nullptr); + if(framebuffer) nvgDeleteFramebuffer(framebuffer); + framebuffer = nvgCreateFramebuffer(nvg, imageWidth, imageHeight, NVG_IMAGE_PREMULTIPLIED); + nvgBindFramebuffer(framebuffer); + framebufferWidth = imageWidth; + framebufferHeight = imageHeight; } - else{ - drawBuffer->clear(Rectangle(imageWidth, imageHeight)); + else { + nvgBindFramebuffer(framebuffer); } - graphics = std::make_unique(*drawBuffer); - graphics->saveState(); - graphics->addTransform(AffineTransform::scale(scale)); // for hi-dpi displays and canvas zooming - graphics->saveState(); + nvgViewport(0, 0, getWidth(), getHeight()); + nvgClear(nvg); + nvgBeginFrame(nvg, getWidth(), getHeight(), scale); + nvgSave(nvg); return; } case hash("lua_end_paint"): { - if(!graphics) break; - graphics->restoreState(); - graphics.reset(nullptr); - - // swap buffers - { - ScopedLock swapLock(bufferSwapLock); - auto* oldDrawBuffer = drawBuffer; - drawBuffer = displayBuffer; - displayBuffer = oldDrawBuffer; - } + if(!framebuffer) return; - triggerAsyncUpdate(); + nvgEndFrame(nvg); + nvgBindFramebuffer(nullptr); + repaint(); return; } case hash("lua_resized"): { @@ -297,9 +276,8 @@ class LuaObject : public ObjectBase, public AsyncUpdater { } } - if(!graphics) return; // If there is no active graphics context at this point, return + if(!framebuffer) return; // If there is no active framebuffer at this point, return - // Functions that do need an active graphics context switch (hashsym) { case hash("lua_set_color"): { if (argc == 1) { @@ -307,7 +285,8 @@ class LuaObject : public ObjectBase, public AsyncUpdater { auto& lnf = LookAndFeel::getDefaultLookAndFeel(); currentColour = Array{lnf.findColour(PlugDataColour::guiObjectBackgroundColourId), lnf.findColour(PlugDataColour::canvasTextColourId), lnf.findColour(PlugDataColour::guiObjectInternalOutlineColour)}[colourID]; - graphics->setColour(currentColour); + nvgFillColor(nvg, convertColour(currentColour)); + nvgStrokeColor(nvg, convertColour(currentColour)); } if (argc >= 3) { Colour color(static_cast(atom_getfloat(argv)), @@ -315,7 +294,8 @@ class LuaObject : public ObjectBase, public AsyncUpdater { static_cast(atom_getfloat(argv + 2))); currentColour = color.withAlpha(argc >= 4 ? atom_getfloat(argv + 3) : 1.0f); - graphics->setColour(currentColour); + nvgFillColor(nvg, convertColour(currentColour)); + nvgStrokeColor(nvg, convertColour(currentColour)); } break; } @@ -327,7 +307,11 @@ class LuaObject : public ObjectBase, public AsyncUpdater { float y2 = atom_getfloat(argv + 3); float lineThickness = atom_getfloat(argv + 4); - graphics->drawLine(x1, y1, x2, y2, lineThickness); + nvgStrokeWidth(nvg, lineThickness); + nvgBeginPath(nvg); + nvgMoveTo(nvg, x1, y1); + nvgLineTo(nvg, x2, y2); + nvgStroke(nvg); } break; } @@ -337,7 +321,10 @@ class LuaObject : public ObjectBase, public AsyncUpdater { float y = atom_getfloat(argv + 1); float w = atom_getfloat(argv + 2); float h = atom_getfloat(argv + 3); - graphics->fillEllipse(Rectangle(x, y, w, h)); + + nvgBeginPath(nvg); + nvgEllipse(nvg, x + (w / 2), y + (h / 2), w / 2, h / 2); + nvgFill(nvg); } break; } @@ -348,7 +335,11 @@ class LuaObject : public ObjectBase, public AsyncUpdater { float w = atom_getfloat(argv + 2); float h = atom_getfloat(argv + 3); float lineThickness = atom_getfloat(argv + 4); - graphics->drawEllipse(Rectangle(x, y, w, h), lineThickness); + + nvgStrokeWidth(nvg, lineThickness); + nvgBeginPath(nvg); + nvgEllipse(nvg, x + (w / 2), y + (h / 2), w / 2, h / 2); + nvgStroke(nvg); } break; } @@ -358,7 +349,10 @@ class LuaObject : public ObjectBase, public AsyncUpdater { float y = atom_getfloat(argv + 1); float w = atom_getfloat(argv + 2); float h = atom_getfloat(argv + 3); - graphics->fillRect(Rectangle(x, y, w, h)); + + nvgBeginPath(nvg); + nvgRect(nvg, x, y, w, h); + nvgFill(nvg); } break; } @@ -369,7 +363,11 @@ class LuaObject : public ObjectBase, public AsyncUpdater { float w = atom_getfloat(argv + 2); float h = atom_getfloat(argv + 3); float lineThickness = atom_getfloat(argv + 4); - graphics->drawRect(Rectangle(x, y, w, h), lineThickness); + + nvgStrokeWidth(nvg, lineThickness); + nvgBeginPath(nvg); + nvgRect(nvg, x, y, w, h); + nvgStroke(nvg); } break; } @@ -380,7 +378,10 @@ class LuaObject : public ObjectBase, public AsyncUpdater { float w = atom_getfloat(argv + 2); float h = atom_getfloat(argv + 3); float cornerRadius = atom_getfloat(argv + 4); - graphics->fillRoundedRectangle(Rectangle(x, y, w, h), cornerRadius); + + nvgBeginPath(nvg); + nvgRoundedRect(nvg, x, y, w, h, cornerRadius); + nvgFill(nvg); } break; } @@ -393,7 +394,11 @@ class LuaObject : public ObjectBase, public AsyncUpdater { float h = atom_getfloat(argv + 3); float cornerRadius = atom_getfloat(argv + 4); float lineThickness = atom_getfloat(argv + 5); - graphics->drawRoundedRectangle(Rectangle(x, y, w, h), cornerRadius, lineThickness); + + nvgStrokeWidth(nvg, lineThickness); + nvgBeginPath(nvg); + nvgRoundedRect(nvg, x, y, w, h, cornerRadius); + nvgStroke(nvg); } break; } @@ -404,54 +409,74 @@ class LuaObject : public ObjectBase, public AsyncUpdater { float x2 = atom_getfloat(argv + 2); float y2 = atom_getfloat(argv + 3); float lineThickness = atom_getfloat(argv + 4); - graphics->drawLine(x1, y1, x2, y2, lineThickness); + + nvgStrokeWidth(nvg, lineThickness); + nvgBeginPath(nvg); + nvgMoveTo(nvg, x1, y1); + nvgLineTo(nvg, x2, y2); + nvgStroke(nvg); } break; } case hash("lua_draw_text"): { if (argc >= 4) { - auto text = String::fromUTF8(atom_getsymbol(argv)->s_name); - auto string = AttributedString(text); float x = atom_getfloat(argv + 1); float y = atom_getfloat(argv + 2); float w = atom_getfloat(argv + 3); - float fontHeight = atom_getfloat(argv + 4) * 1.3; // Increase fontsize to match pd-vanilla - - string.setFont(Font(fontHeight)); - string.setColour(currentColour); - string.setJustification(Justification::topLeft); + float fontHeight = atom_getfloat(argv + 4); - TextLayout layout; - layout.createLayout(string, w); - layout.draw(*graphics, {x, y, w, layout.getHeight()}); + nvgBeginPath(nvg); + nvgFontSize(nvg, fontHeight); + nvgTextAlign(nvg, NVG_ALIGN_TOP | NVG_ALIGN_LEFT); + nvgTextBox(nvg, x, y, w, atom_getsymbol(argv)->s_name, nullptr); } break; } case hash("lua_fill_path"): { - if (argc >= 1) { - auto& currentPath = currentPaths[argv[0].a_w.w_gpointer]; - graphics->fillPath(currentPath); + nvgBeginPath(nvg); + nvgMoveTo(nvg, atom_getfloat(argv), atom_getfloat(argv + 1)); + for(int i = 1; i < argc / 2; i++) + { + float x = atom_getfloat(argv + (i * 2)); + float y = atom_getfloat(argv + (i * 2) + 1); + nvgLineTo(nvg, x, y); } + + nvgClosePath(nvg); + nvgFill(nvg); break; } case hash("lua_stroke_path"): { - if (argc >= 2) { - auto& currentPath = currentPaths[argv[0].a_w.w_gpointer]; - graphics->strokePath(currentPath, PathStrokeType(atom_getfloat(argv + 1))); + nvgBeginPath(nvg); + auto strokeWidth = atom_getfloat(argv); + + int numPoints = (argc - 1) / 2; + nvgMoveTo(nvg, atom_getfloat(argv + 1), atom_getfloat(argv + 2)); + for(int i = 1; i < numPoints - 1; i++) + { + float x = atom_getfloat(argv + (i * 2) + 1); + float y = atom_getfloat(argv + (i * 2) + 2); + nvgLineTo(nvg, x, y); } + + nvgStrokeWidth(nvg, strokeWidth); + nvgStroke(nvg); break; } case hash("lua_fill_all"): { - auto colour = currentColour; - - graphics->fillRoundedRectangle(getLocalBounds().toFloat(), Corners::objectCornerRadius); - + auto bounds = getLocalBounds().toFloat().reduced(0.5f); auto outlineColour = object->findColour(isSelected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); - graphics->setColour(outlineColour); - graphics->drawRoundedRectangle(getLocalBounds().toFloat().reduced(0.5f).withTrimmedRight(0.5f).withTrimmedBottom(0.5f), Corners::objectCornerRadius, 1.0f); - graphics->setColour(colour); + nvgBeginPath(nvg); + nvgRoundedRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), Corners::objectCornerRadius); + nvgFill(nvg); + nvgStrokeWidth(nvg, 1.0f); + nvgStrokeColor(nvg, convertColour(outlineColour)); + nvgStroke(nvg); + + nvgStrokeColor(nvg, convertColour(currentColour)); + break; } @@ -459,7 +484,7 @@ class LuaObject : public ObjectBase, public AsyncUpdater { if (argc >= 2) { float tx = atom_getfloat(argv); float ty = atom_getfloat(argv + 1); - graphics->addTransform(AffineTransform::translation(tx, ty)); + nvgTranslate(nvg, tx, ty); } break; } @@ -467,13 +492,13 @@ class LuaObject : public ObjectBase, public AsyncUpdater { if (argc >= 2) { float sx = atom_getfloat(argv); float sy = atom_getfloat(argv + 1); - graphics->addTransform(AffineTransform::scale(sx, sy)); + nvgScale(nvg, sx, sy); } break; } case hash("lua_reset_transform"): { - graphics->restoreState(); - graphics->saveState(); + nvgRestore(nvg); + nvgSave(nvg); break; } default: @@ -481,17 +506,52 @@ class LuaObject : public ObjectBase, public AsyncUpdater { } } - static void drawCallback(void* target, t_symbol* sym, int argc, t_atom* argv) + void timerCallback() override { - if(target == nullptr && argc != 0) + LuaGuiMessage guiMessage; + while(guiMessageQueue.try_dequeue(guiMessage)) { - receiveLuaPathMessage(argv[0].a_w.w_gpointer, sym, argc-1, argv+1); - return; + guiCommandBuffer.push_back(guiMessage); } + auto* startMesage = pd->generateSymbol("lua_start_paint"); + auto* endMessage = pd->generateSymbol("lua_end_paint"); + + int startIdx = -1, endIdx = -1; + bool updateScene = false; + for(int i = guiCommandBuffer.size() - 1; i >= 0; i--) + { + if(guiCommandBuffer[i].symbol == startMesage) startIdx = i; + if(guiCommandBuffer[i].symbol == endMessage) endIdx = i + 1; + + if(startIdx != -1 && endIdx != -1) { + updateScene = true; + break; + } + } + + if(updateScene) { + if(endIdx > startIdx) { + for(int i = startIdx; i < endIdx; i++) + { + handleGuiMessage(guiCommandBuffer[i].symbol, guiCommandBuffer[i].size, guiCommandBuffer[i].data.data()); + } + } + guiCommandBuffer.erase(guiCommandBuffer.begin(), guiCommandBuffer.begin() + endIdx); + } + + if(isSelected != object->isSelected()) + { + isSelected = object->isSelected(); + sendRepaintMessage(); + } + } + + static void drawCallback(void* target, t_symbol* sym, int argc, t_atom* argv) + { for(auto* object : allDrawTargets[static_cast(target)]) { - object->receiveLuaPaintMessage(sym, argc, argv); + object->guiMessageQueue.enqueue({sym, argc, argv}); } } From cef1ac823aa0a9d9d084a5c68f6d34a93a5da30f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Apr 2024 22:52:29 +0200 Subject: [PATCH 0596/1030] GPU acceleration for number boxes --- Source/Components/DraggableNumber.h | 13 ++++++++++++- Source/NVGSurface.h | 2 ++ Source/Object.cpp | 2 +- Source/Objects/FloatAtomObject.h | 3 ++- Source/Objects/NumberObject.h | 2 +- Source/Objects/NumboxTildeObject.h | 2 +- Source/Utility/HashUtils.h | 2 +- Source/Utility/StringUtils.h | 7 ++++--- 8 files changed, 24 insertions(+), 9 deletions(-) diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index a81e970e67..91df993df8 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -34,6 +34,8 @@ class DraggableNumber : public Label double valueToResetTo = 0.0; double valueToRevertTo = 0.0; bool showEllipses = true; + + std::unique_ptr nvgCtx; public: std::function onValueChange = [](double) {}; @@ -282,6 +284,16 @@ class DraggableNumber : public Label return draggedDecimal; } + + void render(NVGcontext* nvg, float scale) + { + if(!nvgCtx || nvgCtx->getContext() != nvg) nvgCtx = std::make_unique(nvg); + nvgCtx->setPhysicalPixelScaleFactor(scale); + Graphics g(*nvgCtx); + { + paintEntireComponent(g, true); + } + } void paint(Graphics& g) override { @@ -292,7 +304,6 @@ class DraggableNumber : public Label } auto font = getFont(); - if (!isBeingEdited()) { auto textArea = getBorderSize().subtractedFrom(getLocalBounds()).toFloat(); auto numberText = formatNumber(getText().getDoubleValue(), decimalDrag); diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 930cb4088c..df7a563c84 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -121,6 +121,8 @@ public Component, public Timer void invalidateArea(Rectangle area); void invalidateAll(); + + NVGcontext* getRawContext() { return nvg; } private: diff --git a/Source/Object.cpp b/Source/Object.cpp index 0bf2a1e6ce..0e0ed585af 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -1197,7 +1197,7 @@ void Object::updateFramebuffer(NVGcontext* nvg) nvgFill(nvg); #endif nvgEndFrame(nvg); - nvgBindFramebuffer(NULL); + nvgBindFramebuffer(nullptr); fbDirty = false; } diff --git a/Source/Objects/FloatAtomObject.h b/Source/Objects/FloatAtomObject.h index e13633c71e..3f53561ca0 100644 --- a/Source/Objects/FloatAtomObject.h +++ b/Source/Objects/FloatAtomObject.h @@ -199,7 +199,8 @@ class FloatAtomObject final : public ObjectBase { nvgStroke(nvg); } - imageRenderer.renderComponentFromImage(nvg, input, getImageScale()); + input.render(nvg, getImageScale()); + //imageRenderer.renderComponentFromImage(nvg, input, getImageScale()); } void updateLabel() override diff --git a/Source/Objects/NumberObject.h b/Source/Objects/NumberObject.h index b91e54d43f..32530d78e6 100644 --- a/Source/Objects/NumberObject.h +++ b/Source/Objects/NumberObject.h @@ -296,7 +296,7 @@ class NumberObject final : public ObjectBase { nvgFillColor(nvg, convertColour(highlighed ? highlightColour : normalColour)); nvgFill(nvg); - imageRenderer.renderComponentFromImage(nvg, input, getImageScale()); + input.render(nvg, getImageScale()); } float getValue() diff --git a/Source/Objects/NumboxTildeObject.h b/Source/Objects/NumboxTildeObject.h index 7f4ff9af3b..10e77d3dca 100644 --- a/Source/Objects/NumboxTildeObject.h +++ b/Source/Objects/NumboxTildeObject.h @@ -255,7 +255,7 @@ class NumboxTildeObject final : public ObjectBase nvgSave(nvg); nvgTranslate(nvg, input.getX(), input.getY()); - imageRenderer.renderComponentFromImage(nvg, input, getImageScale()); + input.render(nvg, getImageScale()); nvgRestore(nvg); auto icon = mode ? Icons::ThinDown : Icons::Sine; diff --git a/Source/Utility/HashUtils.h b/Source/Utility/HashUtils.h index bcf6e83844..5940584924 100644 --- a/Source/Utility/HashUtils.h +++ b/Source/Utility/HashUtils.h @@ -34,5 +34,5 @@ constexpr hash32 hash(char const* str) */ inline hash32 hash(juce::String const& str) { - return hash(str.toUTF8().getAddress()); + return hash(str.toRawUTF8()); } diff --git a/Source/Utility/StringUtils.h b/Source/Utility/StringUtils.h index 2d3a78202e..c6a7d69deb 100644 --- a/Source/Utility/StringUtils.h +++ b/Source/Utility/StringUtils.h @@ -92,12 +92,13 @@ struct CachedFontStringWidth : public DeletedAtShutdown { auto stringHash = hash(singleLine); - for(auto [cachedFont, cache] : stringWidthCache) + for(auto& [cachedFont, cache] : stringWidthCache) { if(cachedFont == font) { auto cacheHit = cache.find(stringHash); - if(cacheHit != cache.end()) return cacheHit->second; + if(cacheHit != cache.end()) + return cacheHit->second; auto stringWidth = font.getStringWidthFloat(singleLine); cache[stringHash] = stringWidth; @@ -131,5 +132,5 @@ struct CachedFontStringWidth : public DeletedAtShutdown return instance; } - static inline CachedFontStringWidth* instance; + static inline CachedFontStringWidth* instance = nullptr; }; From af77efeaef95fdc6977883ffd5f056af5600b09f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Apr 2024 23:19:38 +0200 Subject: [PATCH 0597/1030] Lua graphics rendering fix --- Source/Objects/LuaObject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Objects/LuaObject.h b/Source/Objects/LuaObject.h index 371c9a65a8..9ce220edb1 100644 --- a/Source/Objects/LuaObject.h +++ b/Source/Objects/LuaObject.h @@ -452,7 +452,7 @@ class LuaObject : public ObjectBase, public Timer, public NVGContextListener { int numPoints = (argc - 1) / 2; nvgMoveTo(nvg, atom_getfloat(argv + 1), atom_getfloat(argv + 2)); - for(int i = 1; i < numPoints - 1; i++) + for(int i = 1; i < numPoints; i++) { float x = atom_getfloat(argv + (i * 2) + 1); float y = atom_getfloat(argv + (i * 2) + 2); From 3eeb93504183f08672cae44f2ad1916e0915850f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Apr 2024 23:19:59 +0200 Subject: [PATCH 0598/1030] Use opaque colour for dialog backgrounds on non-compositing wms --- Source/Dialogs/Dialogs.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/Dialogs/Dialogs.h b/Source/Dialogs/Dialogs.h index b4a523e18d..93ffaedb10 100644 --- a/Source/Dialogs/Dialogs.h +++ b/Source/Dialogs/Dialogs.h @@ -51,8 +51,14 @@ class Dialog : public Component, public ComponentListener { void paint(Graphics& g) override { - g.setColour(Colours::black.withAlpha(0.5f)); - + if(!ProjectInfo::canUseSemiTransparentWindows()) + { + g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); + } + else { + g.setColour(Colours::black.withAlpha(0.5f)); + } + auto bounds = getLocalBounds().toFloat().reduced(backgroundMargin); if (wantsRoundedCorners()) { From def7df13cf4722a1056b757c3cde4fd16888c1ce Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Apr 2024 23:26:20 +0200 Subject: [PATCH 0599/1030] Pdlua rounding fixes --- Libraries/pd-lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/pd-lua b/Libraries/pd-lua index 7638b2b82b..10089fc8ae 160000 --- a/Libraries/pd-lua +++ b/Libraries/pd-lua @@ -1 +1 @@ -Subproject commit 7638b2b82bc72fb823b4056cf2060c72fa873ba0 +Subproject commit 10089fc8aebe1d734a4710cab8772afff0377778 From 98cf8a200dcc59b37a279a779e315512cd77ae57 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 20 Apr 2024 23:37:42 +0200 Subject: [PATCH 0600/1030] Fixed problems due to renaming oscope~ to scope~ --- Resources/Documentation/ELSE/oscope~.md | 68 ---------------- Resources/Documentation/cyclone/scope~.md | 94 ----------------------- Source/Dialogs/AddObjectMenu.h | 2 +- Source/Utility/ObjectThemeManager.h | 1 - 4 files changed, 1 insertion(+), 164 deletions(-) delete mode 100644 Resources/Documentation/ELSE/oscope~.md delete mode 100644 Resources/Documentation/cyclone/scope~.md diff --git a/Resources/Documentation/ELSE/oscope~.md b/Resources/Documentation/ELSE/oscope~.md deleted file mode 100644 index d8a11997c2..0000000000 --- a/Resources/Documentation/ELSE/oscope~.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: oscope~ - -description: oscilloscope display - -categories: - - object - -pdcategory: ELSE, UI, Analysis - -arguments: - -inlets: - 1st: - - type: signal - description: signal to be displayed in the X axis - - type: list - description: one float sets 'nsamples', optional second sets 'nlines' - 2nd: - - type: signal - description: signal to be displayed on the Y axis - -outlets: - -flags: - - name: -fgcolor - - name: -bgcolor - - name: -gridcolor - - name: -range - - name: -trigger - - name: -triglevel - - name: -nsamples - - name: -nlines - - name: -delay - - name: -drawstyle - - name: -dim - - name: -receive - -methods: - - type: nsamples - description: sets number of samples per line (2-8192, default 256) - - type: nlines - description: sets number of lines in buffer (8-256, default 128) - - type: dim - description: sets width/height (default 200 100) - - type: range - description: sets vertical range (default -1 1) - - type: fgcolor - description: sets front/signal RGB color (values 0-255) - - type: bgcolor - description: sets background RGB color (values 0-255) - - type: gridcolor - description: sets grid RGB color (values 0-255) - - type: drawstyle - description: <1> sets alternate drawing style (default 0) - - type: trigger - description: sets trigger mode: 0 (none, default), 1 (up) or 2 (down) - - type: triglevel - description: sets threshold level for the trigger mode (default 0) - - type: delay - description: onset time delay between displays (default 0) - - type: receive - description: receive symbol (default empty) - -draft: false ---- - -[oscope~] displays a signal in the style of an oscilloscope. diff --git a/Resources/Documentation/cyclone/scope~.md b/Resources/Documentation/cyclone/scope~.md deleted file mode 100644 index 603ab26b4c..0000000000 --- a/Resources/Documentation/cyclone/scope~.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: scope~ - -description: oscilloscope display - -categories: - - object - -pdcategory: cyclone, UI - -arguments: (none) - -inlets: - 1st: - - type: signal - description: signal to be displayed in the X axis - - type: float - description: sets samples per line - 2nd: - - type: signal - description: signal to be displayed in the Y axis - - type: float - description: sets the number of lines in buffer - -outlets: - -flags: - - name: @fgcolor - description: sets front/signal RGB color - - name: @bgcolor - description: sets background RGB color - - name: @gridcolor - description: sets grid RGB color - - name: @range - description: sets vertical range - - name: @trigger - description: 0 (none), 1 (up) or 2 (down) - - name: @triglevel - description: sets threshold level for the trigger mode - - name: @bufsize - description: sets the number of lines in buffer (8-256) - - name: @calccount - description: sets samples per line - - name: @delay - description: onset time delay between displays - - name: @drawstyle - description: <1> sets alternate drawing style - - name: @dim - description: sets width/height - - name: @receive - description: receive symbol - -methods: - - type: dim - description: sets width/height - default: 130 130 - - type: calccount - description: sets samples per line - default: - - type: bufsize - description: sets the number of lines in buffer (8-256) - default: 128 - - type: range - description: sets vertical range - default: -1 1 - - type: fgcolor - description: sets front/signal RGB color - default: - - type: bgcolor - description: sets background RGB color - default: - - type: gridcolor - description: sets grid RGB color - default: - - type: drawstyle - description: <1> sets alternate drawing style - default: 0 - - type: trigger - description: 0 (none), 1 (up) or 2 (down) - default: 0 - - type: triglevel - description: sets threshold level for the trigger mode - default: 0 - - type: delay - description: onset time delay between displays - default: 0 - - type: receive - description: receive symbol - default: empty - -draft: true ---- - -[scope~] displays a signal in the style of an oscilloscope. diff --git a/Source/Dialogs/AddObjectMenu.h b/Source/Dialogs/AddObjectMenu.h index 58edb90fe3..66df5cbd3f 100644 --- a/Source/Dialogs/AddObjectMenu.h +++ b/Source/Dialogs/AddObjectMenu.h @@ -220,7 +220,7 @@ class ObjectList : public Component { { Icons::GlyphNumber, "nbx", "(@keypress) Number box", "Number", NewNumbox }, { Icons::GlyphCanvas, "cnv", "(@keypress) Canvas", "Canvas", NewCanvas }, { Icons::GlyphFunction, "function", "Function", "Function", OtherObject }, - { Icons::GlyphOscilloscope, "oscope~", "Oscilloscope", "Scope", OtherObject }, + { Icons::GlyphOscilloscope, "scope~", "Oscilloscope", "Scope", OtherObject }, { Icons::GlyphKeyboard, "#X obj 0 0 keyboard 16 80 4 2 0 0 empty empty", "Piano keyboard", "Keyboard", OtherObject }, { Icons::GlyphMessbox, "messbox", "ELSE Message box", "Messbox", OtherObject }, { Icons::GlyphBicoeff, "#X obj 0 0 bicoeff 450 150 peaking", "Bicoeff generator", "Bicoeff", OtherObject }, diff --git a/Source/Utility/ObjectThemeManager.h b/Source/Utility/ObjectThemeManager.h index 6ec7ad3146..817c0fe858 100644 --- a/Source/Utility/ObjectThemeManager.h +++ b/Source/Utility/ObjectThemeManager.h @@ -80,7 +80,6 @@ class ObjectThemeManager : public Component { { "nbx", "4 18 -1e+37 1e+37 0 0 empty empty empty 0 -8 0 10 @bgColour @lblColour @lblColour 0 256" }, { "cnv", "15 100 60 empty empty empty 20 12 0 14 @lnColour @lblColour" }, { "function", "200 100 empty empty 0 1 @bgColour_rgb @lblColour_rgb 0 0 0 0 0 1000 0" }, - { "oscope~", "130 130 256 3 128 -1 1 0 0 0 0 @fgColour_rgb @bgColour_rgb @lnColour_rgb 0 empty" }, { "scope~", "130 130 256 3 128 -1 1 0 0 0 0 @fgColour_rgb @bgColour_rgb @lnColour_rgb 0 empty" }, { "messbox", "180 60 @bgColour_rgb @lblColour_rgb 0 12" }, { "vu", "20 120 empty empty -1 -8 0 10 @bgColour @lblColour 1 0" }, From c607db88ed7b15b7d6835eaa5e4f0837282ed1de Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Apr 2024 01:56:18 +0200 Subject: [PATCH 0601/1030] Fixed scale factor bugs --- Source/Components/DraggableNumber.h | 3 +-- Source/NVGSurface.cpp | 10 +++++++++- Source/NVGSurface.h | 5 ++++- Source/Objects/FloatAtomObject.h | 3 +-- Source/Objects/KeyboardObject.h | 1 - Source/Objects/LuaObject.h | 2 +- Source/Objects/NumberObject.h | 2 +- Source/Objects/NumboxTildeObject.h | 2 +- Source/Objects/PictureObject.h | 1 - 9 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index 91df993df8..0e234c75c5 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -285,10 +285,9 @@ class DraggableNumber : public Label return draggedDecimal; } - void render(NVGcontext* nvg, float scale) + void render(NVGcontext* nvg) { if(!nvgCtx || nvgCtx->getContext() != nvg) nvgCtx = std::make_unique(nvg); - nvgCtx->setPhysicalPixelScaleFactor(scale); Graphics g(*nvgCtx); { paintEntireComponent(g, true); diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 4dc4e3b16b..ebfae9321d 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -150,6 +150,14 @@ void NVGSurface::detachContext() #endif } + +void NVGSurface::propertyChanged(String const& name, var const& value) { + if(name == "global_scale") + { + sendContextDeleteMessage(); + } +} + float NVGSurface::getRenderScale() const { auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); @@ -158,7 +166,7 @@ float NVGSurface::getRenderScale() const return OSUtils::MTLGetPixelScale(getView()) * desktopScale; #else if(!isAttached()) return desktopScale; - return glContext->getRenderingScale() * desktopScale; + return glContext->getRenderingScale();// * desktopScale; #endif } diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index df7a563c84..33409a9b59 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -12,6 +12,7 @@ using namespace juce::gl; #include "Utility/Config.h" +#include "Utility/SettingsFile.h" #include #ifdef NANOVG_GL_IMPLEMENTATION @@ -56,7 +57,7 @@ public NSViewComponent #elif NANOVG_METAL_IMPLEMENTATION && JUCE_IOS public UIViewComponent #else -public Component, public Timer +public Component, public Timer, public SettingsFileListener #endif { public: @@ -73,6 +74,8 @@ public Component, public Timer void timerCallback() override; #endif + void propertyChanged(String const& name, var const& value) override; + void sendContextDeleteMessage(); void addNVGContextListener(NVGContextListener* listener); void removeNVGContextListener(NVGContextListener* listener); diff --git a/Source/Objects/FloatAtomObject.h b/Source/Objects/FloatAtomObject.h index 3f53561ca0..11b4c307e5 100644 --- a/Source/Objects/FloatAtomObject.h +++ b/Source/Objects/FloatAtomObject.h @@ -199,8 +199,7 @@ class FloatAtomObject final : public ObjectBase { nvgStroke(nvg); } - input.render(nvg, getImageScale()); - //imageRenderer.renderComponentFromImage(nvg, input, getImageScale()); + input.render(nvg); } void updateLabel() override diff --git a/Source/Objects/KeyboardObject.h b/Source/Objects/KeyboardObject.h index af0fff4f22..63f0c51903 100644 --- a/Source/Objects/KeyboardObject.h +++ b/Source/Objects/KeyboardObject.h @@ -290,7 +290,6 @@ class KeyboardObject final : public ObjectBase void render(NVGcontext* nvg) override { if(!nvgCtx || nvgCtx->getContext() != nvg) nvgCtx = std::make_unique(nvg); - nvgCtx->setPhysicalPixelScaleFactor(getImageScale()); Graphics g(*nvgCtx); { paintEntireComponent(g, true); diff --git a/Source/Objects/LuaObject.h b/Source/Objects/LuaObject.h index 9ce220edb1..abbe6a742b 100644 --- a/Source/Objects/LuaObject.h +++ b/Source/Objects/LuaObject.h @@ -248,7 +248,7 @@ class LuaObject : public ObjectBase, public Timer, public NVGContextListener { nvgBindFramebuffer(framebuffer); } - nvgViewport(0, 0, getWidth(), getHeight()); + nvgViewport(0, 0, getWidth() * scale, getHeight() * scale); nvgClear(nvg); nvgBeginFrame(nvg, getWidth(), getHeight(), scale); nvgSave(nvg); diff --git a/Source/Objects/NumberObject.h b/Source/Objects/NumberObject.h index 32530d78e6..44d5107448 100644 --- a/Source/Objects/NumberObject.h +++ b/Source/Objects/NumberObject.h @@ -296,7 +296,7 @@ class NumberObject final : public ObjectBase { nvgFillColor(nvg, convertColour(highlighed ? highlightColour : normalColour)); nvgFill(nvg); - input.render(nvg, getImageScale()); + input.render(nvg); } float getValue() diff --git a/Source/Objects/NumboxTildeObject.h b/Source/Objects/NumboxTildeObject.h index 10e77d3dca..a4950e9928 100644 --- a/Source/Objects/NumboxTildeObject.h +++ b/Source/Objects/NumboxTildeObject.h @@ -255,7 +255,7 @@ class NumboxTildeObject final : public ObjectBase nvgSave(nvg); nvgTranslate(nvg, input.getX(), input.getY()); - input.render(nvg, getImageScale()); + input.render(nvg); nvgRestore(nvg); auto icon = mode ? Icons::ThinDown : Icons::Sine; diff --git a/Source/Objects/PictureObject.h b/Source/Objects/PictureObject.h index 183818463c..42fb356a90 100644 --- a/Source/Objects/PictureObject.h +++ b/Source/Objects/PictureObject.h @@ -125,7 +125,6 @@ class PictureObject final : public ObjectBase { void render(NVGcontext* nvg) override { if(!nvgCtx || nvgCtx->getContext() != nvg) nvgCtx = std::make_unique(nvg); - nvgCtx->setPhysicalPixelScaleFactor(getImageScale()); Graphics g(*nvgCtx); { paintEntireComponent(g, true); From 77032ba4717a179f7b1e43fb59987184bb2b382a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Apr 2024 03:30:12 +0200 Subject: [PATCH 0602/1030] Compilation fix --- Source/NVGSurface.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 33409a9b59..12413d1e4e 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -51,14 +51,15 @@ class NVGContextListener class FrameTimer; class PluginEditor; -class NVGSurface : +class NVGSurface : #if NANOVG_METAL_IMPLEMENTATION && JUCE_MAC public NSViewComponent #elif NANOVG_METAL_IMPLEMENTATION && JUCE_IOS public UIViewComponent #else -public Component, public Timer, public SettingsFileListener +public Component, public Timer #endif +, public SettingsFileListener { public: NVGSurface(PluginEditor* editor); From 4af3b7eeaad7b45f270b0a7e16f2fc20bf2df2ca Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Apr 2024 13:36:03 +0200 Subject: [PATCH 0603/1030] Fixed message listener culling messages if too many come in --- .gitmodules | 3 ++ Libraries/plf_stack | 1 + Source/Objects/SliderObject.h | 13 +++---- Source/Pd/MessageListener.h | 5 ++- Source/Utility/ThreadSafeStack.h | 60 ++++++++++---------------------- 5 files changed, 34 insertions(+), 48 deletions(-) create mode 160000 Libraries/plf_stack diff --git a/.gitmodules b/.gitmodules index b7480a1129..e5949ed3e5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -40,3 +40,6 @@ [submodule "Libraries/nanovg"] path = Libraries/nanovg url = https://github.com/timothyschoen/nanovg.git +[submodule "Libraries/plf_stack"] + path = Libraries/plf_stack + url = https://github.com/mattreecebentley/plf_stack.git diff --git a/Libraries/plf_stack b/Libraries/plf_stack new file mode 160000 index 0000000000..04271f1f81 --- /dev/null +++ b/Libraries/plf_stack @@ -0,0 +1 @@ +Subproject commit 04271f1f8129b26599d15e68fb80cd98735be6b9 diff --git a/Source/Objects/SliderObject.h b/Source/Objects/SliderObject.h index 8d140335fa..3158fd1b8d 100644 --- a/Source/Objects/SliderObject.h +++ b/Source/Objects/SliderObject.h @@ -304,16 +304,17 @@ class SliderObject : public ObjectBase { } break; } - default: { + case hash("color"): { iemHelper.receiveObjectMessage(symbol, atoms, numAtoms); + getLookAndFeel().setColour(Slider::backgroundColourId, Colour::fromString(iemHelper.secondaryColour.toString())); + getLookAndFeel().setColour(Slider::trackColourId, Colour::fromString(iemHelper.primaryColour.toString())); + object->repaint(); break; } + default: { + iemHelper.receiveObjectMessage(symbol, atoms, numAtoms); + break; } - - // Update the colours of the actual slider - if (symbol == hash("color")) { - getLookAndFeel().setColour(Slider::backgroundColourId, Colour::fromString(iemHelper.secondaryColour.toString())); - getLookAndFeel().setColour(Slider::trackColourId, Colour::fromString(iemHelper.primaryColour.toString())); } } diff --git a/Source/Pd/MessageListener.h b/Source/Pd/MessageListener.h index c398271a4e..db2f3527c7 100644 --- a/Source/Pd/MessageListener.h +++ b/Source/Pd/MessageListener.h @@ -146,13 +146,16 @@ class MessageDispatcher { } private: - static constexpr int stackSize = 32768; + static constexpr int stackSize = 65536; using MessageStack = ThreadSafeStack; std::vector>::iterator>> nullListeners; std::unordered_set usedHashes; MessageStack messageStack; + // Queue to use in case our fast stack queue is full + moodycamel::ConcurrentQueue backupQueue; + std::map>> messageListeners; CriticalSection messageListenerLock; }; diff --git a/Source/Utility/ThreadSafeStack.h b/Source/Utility/ThreadSafeStack.h index 1a0a1d9f5b..24b0dc5152 100644 --- a/Source/Utility/ThreadSafeStack.h +++ b/Source/Utility/ThreadSafeStack.h @@ -11,71 +11,49 @@ #include #include #include +#include template class ThreadSafeStack { -private: - struct Buffer { - T data[stackSize]; - int index = 0; - }; - - Buffer buffers[2]; - Buffer* front_; - Buffer* back_; - std::atomic empty = true; + using StackBuffer = plf::stack; + StackBuffer buffers[2]; + StackBuffer* frontBuffer; + StackBuffer* backBuffer; std::mutex swapLock; public: ThreadSafeStack() { - front_ = &buffers[0]; - back_ = &buffers[1]; + frontBuffer = &buffers[0]; + backBuffer = &buffers[1]; + frontBuffer->reserve(stackSize); + backBuffer->reserve(stackSize); } bool isEmpty() { - return empty.load(std::memory_order_relaxed); + std::lock_guard lock(swapLock); + return backBuffer->empty(); } // Swap front and back buffers void swapBuffers() { - swapLock.lock(); - back_ = std::exchange(front_, back_); - swapLock.unlock(); - empty = true; + std::lock_guard lock(swapLock); + backBuffer = std::exchange(frontBuffer, backBuffer); + backBuffer->clear(); } void push(const T& value) { - swapLock.lock(); - Buffer* current_back = back_; - int current_index = current_back->index; - - // Check if the current back buffer is full - while(current_index >= stackSize) { - current_index -= stackSize; - } - - // Perform the push - current_back->data[current_index] = value; - current_back->index = current_index + 1; - swapLock.unlock(); - empty.store(false, std::memory_order_relaxed); + std::lock_guard lock(swapLock); + backBuffer->push(value); } bool pop(T& result) { - if(front_->index == 0) { - return false; - } - - // Perform the pop - int current_index = (front_->index - 1) % stackSize; - T* element = &front_->data[current_index]; - // Decrement the index - front_->index = current_index; + if(frontBuffer->empty()) return false; - result = *element; + result = frontBuffer->top(); + frontBuffer->pop(); return true; } }; From adcbe61bce3dd88eb9ab8bde86626b2edccb4fbc Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Apr 2024 17:48:53 +0200 Subject: [PATCH 0604/1030] Added else/scope~ md documentation --- Resources/Documentation/ELSE/scope~.md | 68 ++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 Resources/Documentation/ELSE/scope~.md diff --git a/Resources/Documentation/ELSE/scope~.md b/Resources/Documentation/ELSE/scope~.md new file mode 100644 index 0000000000..d8a11997c2 --- /dev/null +++ b/Resources/Documentation/ELSE/scope~.md @@ -0,0 +1,68 @@ +--- +title: oscope~ + +description: oscilloscope display + +categories: + - object + +pdcategory: ELSE, UI, Analysis + +arguments: + +inlets: + 1st: + - type: signal + description: signal to be displayed in the X axis + - type: list + description: one float sets 'nsamples', optional second sets 'nlines' + 2nd: + - type: signal + description: signal to be displayed on the Y axis + +outlets: + +flags: + - name: -fgcolor + - name: -bgcolor + - name: -gridcolor + - name: -range + - name: -trigger + - name: -triglevel + - name: -nsamples + - name: -nlines + - name: -delay + - name: -drawstyle + - name: -dim + - name: -receive + +methods: + - type: nsamples + description: sets number of samples per line (2-8192, default 256) + - type: nlines + description: sets number of lines in buffer (8-256, default 128) + - type: dim + description: sets width/height (default 200 100) + - type: range + description: sets vertical range (default -1 1) + - type: fgcolor + description: sets front/signal RGB color (values 0-255) + - type: bgcolor + description: sets background RGB color (values 0-255) + - type: gridcolor + description: sets grid RGB color (values 0-255) + - type: drawstyle + description: <1> sets alternate drawing style (default 0) + - type: trigger + description: sets trigger mode: 0 (none, default), 1 (up) or 2 (down) + - type: triglevel + description: sets threshold level for the trigger mode (default 0) + - type: delay + description: onset time delay between displays (default 0) + - type: receive + description: receive symbol (default empty) + +draft: false +--- + +[oscope~] displays a signal in the style of an oscilloscope. From 01e3bfa4a600ad72a8d8daa1145673012802444d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Apr 2024 17:49:06 +0200 Subject: [PATCH 0605/1030] Improved text object rendering system --- Source/Objects/CommentObject.h | 13 ++++++----- Source/Objects/GraphOnParent.h | 2 +- Source/Objects/MessageObject.h | 16 +++++++------- Source/Objects/OpenFileObject.h | 9 -------- Source/Objects/ScalarObject.h | 2 +- Source/Objects/TextObject.h | 10 ++++++--- Source/Utility/CachedTextRender.h | 36 +++++++++++++++---------------- 7 files changed, 43 insertions(+), 45 deletions(-) diff --git a/Source/Objects/CommentObject.h b/Source/Objects/CommentObject.h index 8ca549b2c7..2496836a3c 100644 --- a/Source/Objects/CommentObject.h +++ b/Source/Objects/CommentObject.h @@ -49,7 +49,7 @@ class CommentObject final : public ObjectBase { if (!editor) { auto textArea = border.subtractedFrom(getLocalBounds()); - textRenderer.renderText(nvg, getText(), Fonts::getDefaultFont().withHeight(15), object->findColour(PlugDataColour::commentTextColourId), textArea, getImageScale()); + textRenderer.renderText(nvg, getText(), Fonts::getDefaultFont().withHeight(15), object->findColour(PlugDataColour::commentTextColourId), textArea, getImageScale(), getValue(sizeProperty)); } else { imageRenderer.renderComponentFromImage(nvg, *editor, getImageScale()); @@ -164,8 +164,9 @@ class CommentObject final : public ObjectBase } auto textSize = textRenderer.getTextBounds(); + // Calculating string width is expensive, so we cache all the strings that we already calculated the width for - int idealWidth = textSize.getWidth() + 8; + int idealWidth = CachedStringWidth<15>::calculateStringWidth(objText) + 8; // We want to adjust the width so ideal text with aligns with fontWidth int offset = idealWidth % fontWidth; @@ -190,8 +191,11 @@ class CommentObject final : public ObjectBase } auto colour = object->findColour(PlugDataColour::canvasTextColourId); - int textWidth = getTextSize().getWidth() - 6; - textRenderer.prepareLayout(objText, Fonts::getDefaultFont().withHeight(15), colour, textWidth); + int textWidth = getTextSize().getWidth() - 8; + if(textRenderer.prepareLayout(objText, Fonts::getDefaultFont().withHeight(15), colour, textWidth, getValue(sizeProperty))) + { + repaint(); + } } std::unique_ptr createConstrainer() override @@ -308,7 +312,6 @@ class CommentObject final : public ObjectBase // For resize-while-typing behaviour void textEditorTextChanged(TextEditor&) override { - updateTextLayout(); object->updateBounds(); } }; diff --git a/Source/Objects/GraphOnParent.h b/Source/Objects/GraphOnParent.h index c6f001ffff..561308c044 100644 --- a/Source/Objects/GraphOnParent.h +++ b/Source/Objects/GraphOnParent.h @@ -217,7 +217,7 @@ class GraphOnParent final : public ObjectBase { // Strangly, the title goes below the graph content in pd if (!getValue(hideNameAndArgs) && getText() != "graph") { auto text = getText(); - textRenderer.renderText(nvg, text, Fonts::getDefaultFont().withHeight(13), object->findColour(PlugDataColour::canvasTextColourId), Rectangle(5, 0, getWidth() - 5, 16), getImageScale()); + textRenderer.renderText(nvg, text, Fonts::getDefaultFont().withHeight(13), object->findColour(PlugDataColour::canvasTextColourId), Rectangle(5, 0, getWidth() - 5, 16), getImageScale(), getWidth()); } Canvas* topLevel = cnv; diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index 64edca634b..771fc41426 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -69,10 +69,9 @@ class MessageObject final : public ObjectBase } auto textSize = textRenderer.getTextBounds(); - // Calculating string width is expensive, so we cache all the strings that we already calculated the width for - int idealWidth = textSize.getWidth() + 14; - if(editor) idealWidth += 1; + // Calculating string width is expensive, so we cache all the strings that we already calculated the width for + int idealWidth = CachedStringWidth<15>::calculateStringWidth(objText) + 14; // We want to adjust the width so ideal text with aligns with fontWidth int offset = idealWidth % fontWidth; @@ -99,8 +98,11 @@ class MessageObject final : public ObjectBase } auto colour = object->findColour(PlugDataColour::canvasTextColourId); - int textWidth = getTextSize().getWidth() - 12; - textRenderer.prepareLayout(objText, Fonts::getDefaultFont().withHeight(15), colour, textWidth); + int textWidth = getTextSize().getWidth() - 14; + if(textRenderer.prepareLayout(objText, Fonts::getDefaultFont().withHeight(15), colour, textWidth, getValue(sizeProperty))) + { + repaint(); + } } void setPdBounds(Rectangle b) override @@ -182,7 +184,6 @@ class MessageObject final : public ObjectBase nvgStrokeWidth(nvg, 1.0f); nvgStroke(nvg); } - if(editor) { @@ -190,7 +191,7 @@ class MessageObject final : public ObjectBase } else { auto text = getText(); - textRenderer.renderText(nvg, text, Fonts::getDefaultFont().withHeight(15), object->findColour(PlugDataColour::canvasTextColourId), border.subtractedFrom(getLocalBounds()), getImageScale()); + textRenderer.renderText(nvg, text, Fonts::getDefaultFont().withHeight(15), object->findColour(PlugDataColour::canvasTextColourId), border.subtractedFrom(getLocalBounds()), getImageScale(), getValue(sizeProperty)); } } @@ -317,7 +318,6 @@ class MessageObject final : public ObjectBase // For resize-while-typing behaviour void textEditorTextChanged(TextEditor& ed) override { - updateTextLayout(); object->updateBounds(); } diff --git a/Source/Objects/OpenFileObject.h b/Source/Objects/OpenFileObject.h index a86af76659..54e3143921 100644 --- a/Source/Objects/OpenFileObject.h +++ b/Source/Objects/OpenFileObject.h @@ -220,13 +220,4 @@ class OpenFileObject final : public TextBase { pd->sendDirectMessage(openfile.get(), "bang", std::vector {}); } } - - void render(NVGcontext* nvg) override - { - imageRenderer.renderComponentFromImage(nvg, *this, getImageScale()); - } - - void paintOverChildren(Graphics& g) override - { - } }; diff --git a/Source/Objects/ScalarObject.h b/Source/Objects/ScalarObject.h index 37a2786e6d..2e760be17a 100644 --- a/Source/Objects/ScalarObject.h +++ b/Source/Objects/ScalarObject.h @@ -383,7 +383,7 @@ class DrawableSymbol final : public DrawableTemplate auto bounds = getBoundingBox().getBoundingBox().toNearestInt(); nvgSave(nvg); nvgTranslate(nvg, bounds.getX(), bounds.getY()); - textRenderer.renderText(nvg, getText(), getFont(), getColour(), bounds.withZeroOrigin(), scale); + textRenderer.renderText(nvg, getText(), getFont(), getColour(), bounds.withZeroOrigin(), scale, getWidth()); nvgRestore(nvg); } diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index 6e5716e11e..db043485b3 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -223,7 +223,7 @@ class TextBase : public ObjectBase // we could render at the actual scale, but that makes the transition to scolling/zooming pretty rough // Instead, rendering at 2x scale gives us pretty good sharpness overall - cachedTextRender.renderText(nvg, text, Fonts::getDefaultFont().withHeight(15), object->findColour(PlugDataColour::canvasTextColourId), textArea, getImageScale()); + cachedTextRender.renderText(nvg, text, Fonts::getDefaultFont().withHeight(15), object->findColour(PlugDataColour::canvasTextColourId), textArea, getImageScale(), getValue(sizeProperty)); } } @@ -277,8 +277,9 @@ class TextBase : public ObjectBase } auto textSize = cachedTextRender.getTextBounds(); + // Calculating string width is expensive, so we cache all the strings that we already calculated the width for - int idealWidth = textSize.getWidth() + 12; + int idealWidth = CachedStringWidth<15>::calculateStringWidth(objText) + 11; // We want to adjust the width so ideal text with aligns with fontWidth int offset = idealWidth % fontWidth; @@ -307,7 +308,10 @@ class TextBase : public ObjectBase auto colour = object->findColour(PlugDataColour::canvasTextColourId); int textWidth = getTextSize().getWidth() - 11; - cachedTextRender.prepareLayout(objText, Fonts::getDefaultFont().withHeight(15), colour, textWidth); + if(cachedTextRender.prepareLayout(objText, Fonts::getDefaultFont().withHeight(15), colour, textWidth, getValue(sizeProperty))) + { + repaint(); + } } void setPdBounds(Rectangle b) override diff --git a/Source/Utility/CachedTextRender.h b/Source/Utility/CachedTextRender.h index 031c8449fe..282376b947 100644 --- a/Source/Utility/CachedTextRender.h +++ b/Source/Utility/CachedTextRender.h @@ -2,8 +2,6 @@ class CachedTextRender : public NVGContextListener { - NVGSurface& surface; - public: CachedTextRender(NVGSurface& nvgSurface) : surface(nvgSurface) @@ -22,12 +20,13 @@ class CachedTextRender : public NVGContextListener imageId = 0; } - void renderText(NVGcontext* nvg, String const& text, Font const& font, Colour const& colour, Rectangle const& bounds, float scale) + void renderText(NVGcontext* nvg, String const& text, Font const& font, Colour const& colour, Rectangle const& bounds, float scale, int width) { - if(imageId <= 0 || lastTextHash != hash(text) || scale != lastScale || colour != lastColour || lastWidth != bounds.getWidth()) + if(updateImage || imageId <= 0 || lastTextHash != hash(text) || scale != lastScale || colour != lastColour || lastWidth != width) { layoutReady = false; - renderTextToImage(nvg, text, font, colour, Rectangle(bounds.getX(), bounds.getY(), bounds.getWidth() + 3, bounds.getHeight()), scale); + renderTextToImage(nvg, text, font, colour, Rectangle(bounds.getX(), bounds.getY(), bounds.getWidth() + 3, bounds.getHeight()), scale, width); + updateImage = false; } nvgBeginPath(nvg); @@ -40,38 +39,36 @@ class CachedTextRender : public NVGContextListener } // If you want to use this for text measuring as well, you might want the measuring to be ready before - void prepareLayout(String const& text, Font const& font, Colour const& colour, int const width) + bool prepareLayout(String const& text, Font const& font, Colour const& colour, int const width, int const cachedWidth) { - if(lastTextHash != hash(text) || colour != lastColour || width != lastWidth) { + auto textHash = hash(text); + bool needsUpdate = lastTextHash != textHash || colour != lastColour || cachedWidth != lastWidth; + if(needsUpdate) { auto attributedText = AttributedString(text); attributedText.setColour(colour); attributedText.setJustification(Justification::centredLeft); attributedText.setFont(Font(15)); layout = TextLayout(); - layout.createLayout(attributedText, width+1); + layout.createLayout(attributedText, width); idealHeight = layout.getHeight(); - idealWidth = CachedFontStringWidth::get()->calculateStringWidth(font, text); - lastWidth = width; + lastWidth = cachedWidth; - lastTextHash = hash(text); + lastTextHash = textHash; lastColour = colour; + updateImage = true; } layoutReady = true; + return needsUpdate; } - void renderTextToImage(NVGcontext* nvg, String const& text, Font const& font, const Colour& colour, Rectangle const& bounds, float scale) + void renderTextToImage(NVGcontext* nvg, String const& text, Font const& font, const Colour& colour, Rectangle const& bounds, float scale, int cachedWidth) { int width = std::floor(bounds.getWidth() * scale); int height = std::floor(bounds.getHeight() * scale); - - if(!layoutReady) - { - prepareLayout(text, font, colour, bounds.getWidth() - 3); - } - + Image textImage = Image(Image::ARGB, width, height, true); { Graphics g(textImage); @@ -119,6 +116,8 @@ class CachedTextRender : public NVGContextListener } private: + NVGSurface& surface; + int imageId = 0; int imageWidth = 0, imageHeight = 0; hash32 lastTextHash; @@ -129,4 +128,5 @@ class CachedTextRender : public NVGContextListener TextLayout layout; bool layoutReady = false; + bool updateImage = false; }; From 9724ac9bc5b43851585acc3bf03ba32f4f2d3aac Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Apr 2024 17:52:57 +0200 Subject: [PATCH 0606/1030] Fixed object grid snapping to furthest object --- Source/ObjectGrid.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ObjectGrid.cpp b/Source/ObjectGrid.cpp index bb6f4c430c..059757540c 100644 --- a/Source/ObjectGrid.cpp +++ b/Source/ObjectGrid.cpp @@ -43,7 +43,7 @@ Array ObjectGrid::getSnappableObjects(Object* draggedObject) auto distA = a->getBounds().getCentre().getDistanceFrom(centre); auto distB = b->getBounds().getCentre().getDistanceFrom(centre); - return distA < distB; + return distA > distB; }); return snappable; From c26bc9883cf4dc7d4358ace5ac15377a9c45ca93 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Apr 2024 17:56:28 +0200 Subject: [PATCH 0607/1030] Fixed broken right-click menu for graphs --- Source/Object.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index 0e0ed585af..d7906b816b 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -744,7 +744,7 @@ void Object::mouseDown(MouseEvent const& e) { // Only show right-click menu in locked mode if the object can be opened // We don't allow alt+click for popupmenus here, as that will conflict with some object behaviour, like for [range.hsl] - if (e.mods.isRightButtonDown() && !cnv->editor->pluginMode) { + if (e.mods.isRightButtonDown() && !cnv->editor->pluginMode && !cnv->isGraph) { PopupMenu::dismissAllActiveMenus(); if (!getValue(locked)) { if(!e.mods.isAnyModifierKeyDown()) cnv->deselectAll(); From a924b878d3305c2d496460ea0afdc2d8d6eb646e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Apr 2024 18:05:05 +0200 Subject: [PATCH 0608/1030] Make object DnD usable for non-compositing wms --- Source/Components/ObjectDragAndDrop.h | 21 +++++---------------- Source/Utility/OfflineObjectRenderer.cpp | 7 ++++++- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/Source/Components/ObjectDragAndDrop.h b/Source/Components/ObjectDragAndDrop.h index d7c5eaa819..5e5ced4f93 100644 --- a/Source/Components/ObjectDragAndDrop.h +++ b/Source/Components/ObjectDragAndDrop.h @@ -85,7 +85,6 @@ class ObjectClickAndDrop : public Component ImageComponent imageComponent; static inline std::unique_ptr instance = nullptr; - bool isOnEditor = false; bool dropState = false; @@ -98,12 +97,7 @@ class ObjectClickAndDrop : public Component objectString = target->getObjectString(); objectName = target->getPatchStringName(); - if (ProjectInfo::canUseSemiTransparentWindows()) { - addToDesktop(ComponentPeer::windowIsTemporary); - } else { - isOnEditor = true; - editor->addChildComponent(this); - } + addToDesktop(ComponentPeer::windowIsTemporary); setAlwaysOnTop(true); @@ -124,7 +118,7 @@ class ObjectClickAndDrop : public Component imageComponent.setAlpha(0.0f); auto screenPos = Desktop::getMousePosition(); - setCentrePosition(isOnEditor ? getLocalPoint(nullptr, screenPos) : screenPos); + setCentrePosition(screenPos); startTimerHz(60); setVisible(true); @@ -146,14 +140,9 @@ class ObjectClickAndDrop : public Component auto screenPos = Desktop::getMousePosition(); auto mousePosition = Point(); Component* underMouse; - - if (isOnEditor) { - mousePosition = editor->getLocalPoint(nullptr, screenPos); - underMouse = editor->getComponentAt(mousePosition); - } else { - mousePosition = screenPos; - underMouse = editor->getComponentAt(editor->getLocalPoint(nullptr, screenPos)); - } + + mousePosition = screenPos; + underMouse = editor->getComponentAt(editor->getLocalPoint(nullptr, screenPos)); Canvas* foundCanvas = nullptr; diff --git a/Source/Utility/OfflineObjectRenderer.cpp b/Source/Utility/OfflineObjectRenderer.cpp index 74deedcea1..c8106375c4 100644 --- a/Source/Utility/OfflineObjectRenderer.cpp +++ b/Source/Utility/OfflineObjectRenderer.cpp @@ -120,7 +120,12 @@ ImageWithOffset OfflineObjectRenderer::patchToTempImage(String const& patch, flo g.addTransform(AffineTransform::scale(scale)); g.setColour(Colours::white); for (auto& rect : objectRects) { - g.fillRoundedRectangle(rect.toFloat(), 5.0f); + if(ProjectInfo::canUseSemiTransparentWindows()) { + g.fillRoundedRectangle(rect.toFloat(), 5.0f); + } + else { + g.fillRect(rect); + } } auto output = ImageWithOffset(image, size); From fc734a7d3a74b499103a708a7aad209a576442d6 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Apr 2024 18:15:43 +0200 Subject: [PATCH 0609/1030] More DnD fixes for non-compositing wms --- .../Components/ZoomableDragAndDropContainer.cpp | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/Source/Components/ZoomableDragAndDropContainer.cpp b/Source/Components/ZoomableDragAndDropContainer.cpp index d23384cf34..73847c661d 100644 --- a/Source/Components/ZoomableDragAndDropContainer.cpp +++ b/Source/Components/ZoomableDragAndDropContainer.cpp @@ -598,20 +598,11 @@ void ZoomableDragAndDropContainer::startDragging(var const& sourceDescription, auto* dragImageComponent = dragImageComponents.add(new DragImageComponent(imageToUse(dragImage).image, imageToUse(invalidImage).image, sourceDescription, sourceComponent, draggingSource, *this, imageToUse(dragImage).offset.roundToInt(), canZoom)); - if (allowDraggingToExternalWindows) { - if (!Desktop::canUseSemiTransparentWindows()) - dragImageComponent->setOpaque(true); - - dragImageComponent->addToDesktop(ComponentPeer::windowIgnoresMouseClicks - | ComponentPeer::windowIsTemporary); - } else { - if (auto* thisComp = dynamic_cast(this)) { - thisComp->addChildComponent(dragImageComponent); - } else { - jassertfalse; // Your ZoomableDragAndDropContainer needs to be a Component! - return; - } + if (allowDraggingToExternalWindows && !Desktop::canUseSemiTransparentWindows()) { + dragImageComponent->setOpaque(true); } + + dragImageComponent->addToDesktop(ComponentPeer::windowIgnoresMouseClicks | ComponentPeer::windowIsTemporary); dragImageComponent->sourceDetails.localPosition = sourceComponent->getLocalPoint(nullptr, lastMouseDown).toInt(); dragImageComponent->updateLocation(false, lastMouseDown.toInt()); From 2501fa00ec7a109fd27a153f3d579f00bbb0a5d7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Apr 2024 18:18:34 +0200 Subject: [PATCH 0610/1030] Simplify DnD logic --- Source/Components/ZoomableDragAndDropContainer.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Source/Components/ZoomableDragAndDropContainer.cpp b/Source/Components/ZoomableDragAndDropContainer.cpp index 73847c661d..a4ae03e639 100644 --- a/Source/Components/ZoomableDragAndDropContainer.cpp +++ b/Source/Components/ZoomableDragAndDropContainer.cpp @@ -540,12 +540,6 @@ void ZoomableDragAndDropContainer::startDragging(var const& sourceDescription, if (isAlreadyDragging(sourceComponent)) return; - if (!ProjectInfo::canUseSemiTransparentWindows()) { - // If window transparency isn't supported, we should add it to the source component instead of to desktop - // This can be accomplished by disabling cross-window dragging - allowDraggingToExternalWindows = false; - } - auto* draggingSource = getMouseInputSourceForDrag(sourceComponent, inputSourceCausingDrag); if (draggingSource == nullptr || !draggingSource->isDragging()) { @@ -598,7 +592,7 @@ void ZoomableDragAndDropContainer::startDragging(var const& sourceDescription, auto* dragImageComponent = dragImageComponents.add(new DragImageComponent(imageToUse(dragImage).image, imageToUse(invalidImage).image, sourceDescription, sourceComponent, draggingSource, *this, imageToUse(dragImage).offset.roundToInt(), canZoom)); - if (allowDraggingToExternalWindows && !Desktop::canUseSemiTransparentWindows()) { + if (!Desktop::canUseSemiTransparentWindows()) { dragImageComponent->setOpaque(true); } From 716c1e6ef103d583ede89a568971113e53692c31 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 21 Apr 2024 18:30:09 +0200 Subject: [PATCH 0611/1030] Allow multi-window creation without compositing --- Source/PluginEditor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index fffcfab233..15618bd3fe 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -750,7 +750,7 @@ void PluginEditor::fileDragExit(StringArray const&) void PluginEditor::createNewWindow(TabBarButtonComponent* tabButton) { - if (!ProjectInfo::isStandalone || !ProjectInfo::canUseSemiTransparentWindows()) + if (!ProjectInfo::isStandalone) return; auto* newEditor = new PluginEditor(*pd); From 686853a75541f7897266e84b22bc27775607c217 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 22 Apr 2024 01:17:54 +0200 Subject: [PATCH 0612/1030] Fixed multi-window implementation --- Source/Canvas.cpp | 2 +- Source/PluginEditor.cpp | 14 +++++--- Source/Tabbar/ResizableTabbedComponent.cpp | 41 +++++++++++++++------- Source/Tabbar/TabBarButtonComponent.cpp | 1 + Source/Tabbar/Tabbar.cpp | 28 +++++++++------ 5 files changed, 58 insertions(+), 28 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 581e8b234e..e4bb776d0c 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -862,9 +862,9 @@ void Canvas::altKeyChanged(bool isHeld) void Canvas::moveToWindow(PluginEditor* newEditor) { if (newEditor != editor) { + newEditor->nvgSurface.sendContextDeleteMessage(); editor->canvases.removeAndReturn(editor->canvases.indexOf(this)); newEditor->canvases.add(this); - newEditor->nvgSurface.sendContextDeleteMessage(); editor = newEditor; } } diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 15618bd3fe..60f2ade249 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -762,17 +762,21 @@ void PluginEditor::createNewWindow(TabBarButtonComponent* tabButton) newWindow->addToDesktop(window->getDesktopWindowStyleFlags()); newWindow->setVisible(true); - - auto* targetSplit = newEditor->getSplitView()->splits[0]; + auto* originalTabComponent = tabButton->getTabComponent(); auto* originalCanvas = originalTabComponent->getCanvas(tabButton->getIndex()); - auto originalSplitIndex = splitView.getTabComponentSplitIndex(originalTabComponent); - splitView.splits[originalSplitIndex]->moveToSplit(targetSplit, originalCanvas); - originalCanvas->moveToWindow(newEditor); + newEditor->pd->patches.add(originalCanvas->patch); + auto newPatch = newEditor->pd->patches.getLast(); + auto* newCanvas = newEditor->canvases.add(new Canvas(newEditor, *newPatch, nullptr)); + newEditor->addTab(newCanvas); + newCanvas->jumpToOrigin(); newWindow->setTopLeftPosition(Desktop::getInstance().getMousePosition() - Point(500, 60)); newWindow->toFront(true); + + closeTab(originalCanvas); + nvgSurface.sendContextDeleteMessage(); } bool PluginEditor::isActiveWindow() diff --git a/Source/Tabbar/ResizableTabbedComponent.cpp b/Source/Tabbar/ResizableTabbedComponent.cpp index 081d02a7af..c67babba26 100644 --- a/Source/Tabbar/ResizableTabbedComponent.cpp +++ b/Source/Tabbar/ResizableTabbedComponent.cpp @@ -162,10 +162,14 @@ void ResizableTabbedComponent::createNewSplit(DropZones activeZone, Canvas* canv editor->splitView.addSplit(newSplit); editor->splitView.addResizer(resizer); + editor->pd->patches.add(canvas->patch); + auto newPatch = editor->pd->patches.getLast(); + auto* newCanvas = editor->canvases.add(new Canvas(editor, *newPatch, nullptr)); + auto tabTitle = canvas->patch.getTitle(); - newSplit->getTabComponent()->addTab(tabTitle, canvas->viewport.get(), 0); + newSplit->getTabComponent()->addTab(tabTitle, newCanvas->viewport.get(), 0); canvas->viewport->setVisible(true); - canvas->moveToWindow(editor); + newCanvas->jumpToOrigin(); newSplit->resized(); newSplit->getTabComponent()->resized(); @@ -176,20 +180,31 @@ void ResizableTabbedComponent::moveTabToNewSplit(SourceDetails const& dragSource // get the dragging tab auto* sourceTabButton = dynamic_cast(dragSourceDetails.sourceComponent.get()); int sourceTabIndex = sourceTabButton->getIndex(); - auto sourceTabContent = sourceTabButton->getTabComponent(); + auto* sourceTabContent = sourceTabButton->getTabComponent(); int sourceNumTabs = sourceTabContent->getNumTabs(); bool shouldDelete = (sourceNumTabs - 1) == 0; bool dropZoneCentre = (activeZone == DropZones::Centre) ? true : false; - + auto* tabCanvas = sourceTabContent->getCanvas(sourceTabIndex); + if (dropZoneCentre) { - auto* tabCanvas = sourceTabContent->getCanvas(sourceTabIndex); auto tabTitle = tabCanvas->patch.getTitle(); auto newTabIdx = tabComponent->getNumTabs(); - tabComponent->addTab(tabTitle, sourceTabContent->getCanvas(sourceTabIndex)->viewport.get(), newTabIdx); - tabComponent->setCurrentTabIndex(newTabIdx); + + if(tabCanvas->editor != editor) + { + editor->pd->patches.add(tabCanvas->patch); + auto newPatch = editor->pd->patches.getLast(); + auto* newCanvas = editor->canvases.add(new Canvas(editor, *newPatch, nullptr)); + editor->addTab(newCanvas); + newCanvas->jumpToOrigin(); + } + else { + tabComponent->addTab(tabTitle, sourceTabContent->getCanvas(sourceTabIndex)->viewport.get(), newTabIdx); + tabComponent->setCurrentTabIndex(newTabIdx); + } editor->splitView.setFocus(this); - tabCanvas->moveToWindow(editor); + sourceTabContent->removeTab(sourceTabIndex); sourceTabContent->setCurrentTabIndex(sourceTabIndex > (sourceTabContent->getNumTabs() - 1) ? sourceTabIndex - 1 : sourceTabIndex); for (auto* split : editor->splitView.splits) { @@ -198,15 +213,17 @@ void ResizableTabbedComponent::moveTabToNewSplit(SourceDetails const& dragSource } else if (activeZone == DropZones::Left || activeZone == DropZones::Right) { createNewSplit(static_cast(activeZone), sourceTabContent->getCanvas(sourceTabIndex)); } - + if (shouldDelete) { - editor->splitView.setFocus(this); - editor->splitView.removeSplit(sourceTabContent); + tabCanvas->editor->splitView.setFocus(this); + tabCanvas->editor->splitView.removeSplit(sourceTabContent); for (auto* split : editor->splitView.splits) { split->setBoundsWithFactors(getParentComponent()->getLocalBounds()); } + tabCanvas->editor->closeTab(tabCanvas); + tabCanvas->editor->splitView.resized(); } - + // set all current canvas viewports to visible, (if they already are this shouldn't do anything) for (auto* split : editor->splitView.splits) { if (auto tabComponent = split->getTabComponent()) { diff --git a/Source/Tabbar/TabBarButtonComponent.cpp b/Source/Tabbar/TabBarButtonComponent.cpp index efe885f4ec..691d242640 100644 --- a/Source/Tabbar/TabBarButtonComponent.cpp +++ b/Source/Tabbar/TabBarButtonComponent.cpp @@ -257,6 +257,7 @@ void TabBarButtonComponent::mouseDown(MouseEvent const& e) auto* patch = new pd::Patch(pd::WeakReference(parent, pd), pd, false); auto* newCanvas = editor->canvases.add(new Canvas(editor, patch)); editor->addTab(newCanvas); + newCanvas->jumpToOrigin(); newCanvas->getTabbar()->setCurrentTabIndex(newCanvas->getTabIndex()); }); } diff --git a/Source/Tabbar/Tabbar.cpp b/Source/Tabbar/Tabbar.cpp index 59efa54929..3efcdf42f0 100644 --- a/Source/Tabbar/Tabbar.cpp +++ b/Source/Tabbar/Tabbar.cpp @@ -263,19 +263,26 @@ void ButtonBar::itemDropped(SourceDetails const& dragSourceDetails) } else { auto sourceTabButton = static_cast(dragSourceDetails.sourceComponent.get()); int sourceTabIndex = sourceTabButton->getIndex(); - auto sourceTabContent = sourceTabButton->getTabComponent(); + auto sourceTabContent = SafePointer(sourceTabButton->getTabComponent()); int sourceNumTabs = sourceTabContent->getNumVisibleTabs(); bool otherWindow = sourceTabContent->getTopLevelComponent() != getTopLevelComponent(); auto ghostTabBounds = ghostTab->getBounds(); + auto* tabCanvas = sourceTabContent->getCanvas(sourceTabIndex); + + auto* newEditor = owner.getEditor(); + newEditor->pd->patches.add(tabCanvas->patch); + auto newPatch = newEditor->pd->patches.getLast(); + auto* newCanvas = newEditor->canvases.add(new Canvas(newEditor, *newPatch, nullptr)); + inOtherSplit = false; // we remove the ghost tab, which is NOT a proper tab, (it only a tab, and doesn't have a viewport) owner.removeTab(ghostTabIdx); - auto* tabCanvas = sourceTabContent->getCanvas(sourceTabIndex); + auto tabTitle = tabCanvas->patch.getTitle(); // we then re-add the ghost tab, but this time we add it from the owner (tabComponent) // which allows us to inject the viewport - owner.addTab(tabTitle, sourceTabContent->getCanvas(sourceTabIndex)->viewport.get(), ghostTabIdx); + owner.addTab(tabTitle, newCanvas->viewport.get(), ghostTabIdx); owner.setCurrentTabIndex(ghostTabIdx); // we need to give the ghost tab the new tab button, as the old one will be deleted before @@ -284,19 +291,20 @@ void ButtonBar::itemDropped(SourceDetails const& dragSourceDetails) auto newTab = owner.tabs->getTabButton(ghostTabIdx); newTab->setBounds(ghostTabBounds); ghostTab->setTabButtonToGhost(newTab); - - tabCanvas->moveToWindow(owner.getEditor()); // In case it got dragged into a new window - - sourceTabContent->removeTab(sourceTabIndex); - auto sourceCurrentIndex = sourceTabIndex > (sourceTabContent->getNumVisibleTabs() - 1) ? sourceTabIndex - 1 : sourceTabIndex; - sourceTabContent->setCurrentTabIndex(sourceCurrentIndex); + + if(sourceTabContent) { + sourceTabContent->removeTab(sourceTabIndex); + auto sourceCurrentIndex = sourceTabIndex > (sourceTabContent->getNumVisibleTabs() - 1) ? sourceTabIndex - 1 : sourceTabIndex; + sourceTabContent->setCurrentTabIndex(sourceCurrentIndex); + } + tabCanvas->editor->splitView.closeEmptySplits(); if (sourceNumTabs < 2 && !otherWindow) { // we don't animate the ghostTab moving into position, as the geometry of the splits is changing ghostTab->setVisible(false); ghostTabAnimator.cancelAllAnimations(true); - owner.editor->splitView.removeSplit(sourceTabContent); + if(sourceTabContent) owner.editor->splitView.removeSplit(sourceTabContent); for (auto* split : owner.editor->splitView.splits) { split->setBoundsWithFactors(owner.editor->splitView.getLocalBounds()); } From 9919e1b563640843ca243ca23ecf3ce375a61575 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 22 Apr 2024 02:09:27 +0200 Subject: [PATCH 0613/1030] More multi-window fixes --- Source/Canvas.cpp | 10 ---------- Source/Canvas.h | 2 -- Source/Tabbar/ResizableTabbedComponent.cpp | 5 +++-- Source/Tabbar/Tabbar.cpp | 6 +++--- 4 files changed, 6 insertions(+), 17 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index e4bb776d0c..642db9200e 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -859,16 +859,6 @@ void Canvas::altKeyChanged(bool isHeld) SettingsFile::getInstance()->getValueTree().getChildWithName("Overlays").setProperty("alt_mode", isHeld, nullptr); } -void Canvas::moveToWindow(PluginEditor* newEditor) -{ - if (newEditor != editor) { - newEditor->nvgSurface.sendContextDeleteMessage(); - editor->canvases.removeAndReturn(editor->canvases.indexOf(this)); - newEditor->canvases.add(this); - editor = newEditor; - } -} - void Canvas::mouseDown(MouseEvent const& e) { PopupMenu::dismissAllActiveMenus(); diff --git a/Source/Canvas.h b/Source/Canvas.h index 090a4bb49f..f981a8eac9 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -101,8 +101,6 @@ class Canvas : public Component void performSynchronise(); void handleAsyncUpdate() override; - void moveToWindow(PluginEditor* newWindow); - void lookAndFeelChanged() override; void updateDrawables(); diff --git a/Source/Tabbar/ResizableTabbedComponent.cpp b/Source/Tabbar/ResizableTabbedComponent.cpp index c67babba26..df5a75c795 100644 --- a/Source/Tabbar/ResizableTabbedComponent.cpp +++ b/Source/Tabbar/ResizableTabbedComponent.cpp @@ -217,12 +217,13 @@ void ResizableTabbedComponent::moveTabToNewSplit(SourceDetails const& dragSource if (shouldDelete) { tabCanvas->editor->splitView.setFocus(this); tabCanvas->editor->splitView.removeSplit(sourceTabContent); + tabCanvas->editor->splitView.resized(); for (auto* split : editor->splitView.splits) { split->setBoundsWithFactors(getParentComponent()->getLocalBounds()); } - tabCanvas->editor->closeTab(tabCanvas); - tabCanvas->editor->splitView.resized(); } + + tabCanvas->editor->canvases.removeObject(tabCanvas); // set all current canvas viewports to visible, (if they already are this shouldn't do anything) for (auto* split : editor->splitView.splits) { diff --git a/Source/Tabbar/Tabbar.cpp b/Source/Tabbar/Tabbar.cpp index 3efcdf42f0..90b8bccbbd 100644 --- a/Source/Tabbar/Tabbar.cpp +++ b/Source/Tabbar/Tabbar.cpp @@ -291,13 +291,13 @@ void ButtonBar::itemDropped(SourceDetails const& dragSourceDetails) auto newTab = owner.tabs->getTabButton(ghostTabIdx); newTab->setBounds(ghostTabBounds); ghostTab->setTabButtonToGhost(newTab); - + + tabCanvas->editor->closeTab(tabCanvas); + if(sourceTabContent) { - sourceTabContent->removeTab(sourceTabIndex); auto sourceCurrentIndex = sourceTabIndex > (sourceTabContent->getNumVisibleTabs() - 1) ? sourceTabIndex - 1 : sourceTabIndex; sourceTabContent->setCurrentTabIndex(sourceCurrentIndex); } - tabCanvas->editor->splitView.closeEmptySplits(); if (sourceNumTabs < 2 && !otherWindow) { // we don't animate the ghostTab moving into position, as the geometry of the splits is changing From fd3df2690a59b134e4ca266eb3ad4ad91608269d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 22 Apr 2024 02:19:12 +0200 Subject: [PATCH 0614/1030] Fixed slider bug --- Libraries/pure-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/pure-data b/Libraries/pure-data index ba40049d30..6369fc98ca 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit ba40049d307b5a2b29c6e48498606e55ac9f640b +Subproject commit 6369fc98ca18add9c85d57c3598132c1ad00974c From 14d80263f8863d835e090c96b9e57e97a1ede6f4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 22 Apr 2024 13:11:00 +0200 Subject: [PATCH 0615/1030] Fixed multi-window bugs --- Source/PluginEditor.cpp | 2 +- Source/Sidebar/Console.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 60f2ade249..d4762c3419 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -959,7 +959,7 @@ void PluginEditor::closeTab(Canvas* cnv) tabbar->setCurrentTabIndex(newTabIdx); } - pd->patches.removeAllInstancesOf(patch); + pd->patches.removeFirstMatchingValue(patch); for (auto split : splitView.splits) { auto tabbar = split->getTabComponent(); diff --git a/Source/Sidebar/Console.h b/Source/Sidebar/Console.h index a770e75a72..2e8a0e5fdb 100644 --- a/Source/Sidebar/Console.h +++ b/Source/Sidebar/Console.h @@ -281,6 +281,7 @@ class Console : public Component , viewport(v) , pd(instance) { + setAccessible(false); setWantsKeyboardFocus(true); repaint(); } From a17246140d51b5191febe923efd00c51934c6036 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 22 Apr 2024 17:00:07 +0200 Subject: [PATCH 0616/1030] Improved font rendering for numberboxes --- Resources/Fonts/InterTabular.ttf | Bin 818128 -> 817868 bytes Source/Components/DraggableNumber.h | 1 + Source/NVGSurface.cpp | 4 +--- Source/Utility/NanoVGGraphicsContext.cpp | 6 +++--- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Resources/Fonts/InterTabular.ttf b/Resources/Fonts/InterTabular.ttf index 4c4abf8d8cf7b6f5db92bfc3d19847eca2d0f800..c7b7f1fe0dd250e99f62d5d3eb2fda7ddc27fc7e 100644 GIT binary patch delta 27880 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 T{1egetContext() != nvg) nvgCtx = std::make_unique(nvg); + nvgCtx->setPhysicalPixelScaleFactor(2.0f); Graphics g(*nvgCtx); { paintEntireComponent(g, true); diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index ebfae9321d..7903f5d282 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -383,9 +383,7 @@ void NVGSurface::render() if (!nvg) std::cerr << "could not initialise nvg" << std::endl; nvgCreateFontMem(nvg, "Inter", (unsigned char*)BinaryData::InterRegular_ttf, BinaryData::InterRegular_ttfSize, 0); nvgCreateFontMem(nvg, "Inter-Regular", (unsigned char*)BinaryData::InterRegular_ttf, BinaryData::InterRegular_ttfSize, 0); - nvgCreateFontMem(nvg, "Inter-Bold", (unsigned char*)BinaryData::InterBold_ttf, BinaryData::InterBold_ttfSize, 0); - nvgCreateFontMem(nvg, "Inter-Semibold", (unsigned char*)BinaryData::InterSemiBold_ttf, BinaryData::InterSemiBold_ttfSize, 0); - nvgCreateFontMem(nvg, "Inter-Thin", (unsigned char*)BinaryData::InterThin_ttf, BinaryData::InterThin_ttfSize, 0); + nvgCreateFontMem(nvg, "Inter-Tabular", (unsigned char*)BinaryData::InterTabular_ttf, BinaryData::InterTabular_ttfSize, 0); nvgCreateFontMem(nvg, "Icon", (unsigned char*)BinaryData::IconFont_ttf, BinaryData::IconFont_ttfSize, 0); } else if(!hasCanvas && isAttached()) diff --git a/Source/Utility/NanoVGGraphicsContext.cpp b/Source/Utility/NanoVGGraphicsContext.cpp index 3fac5ffd88..870fb127a3 100644 --- a/Source/Utility/NanoVGGraphicsContext.cpp +++ b/Source/Utility/NanoVGGraphicsContext.cpp @@ -407,10 +407,10 @@ void NanoVGGraphicsContext::setFont (const juce::Font& f) } currentGlyphToCharMap = &loadedFonts[name]; - + nvgFontFace (nvg, name.toUTF8()); - nvgFontSize (nvg, font.getHeight() * 0.85f); - nvgTextLetterSpacing(nvg, -0.275f); + nvgFontSize (nvg, font.getHeight() * 0.86f); + //nvgTextLetterSpacing(nvg, -0.275f); } const juce::Font& NanoVGGraphicsContext::getFont() From 2385057b28fe11228dfaafb3c2ec7052cc531f25 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Apr 2024 00:41:31 +0200 Subject: [PATCH 0617/1030] Small fixes --- Resources/Documentation/vanilla/scope~.md | 13 ------------- Source/Objects/LuaObject.h | 8 ++++++++ Source/Utility/NanoVGGraphicsContext.cpp | 2 +- 3 files changed, 9 insertions(+), 14 deletions(-) delete mode 100644 Resources/Documentation/vanilla/scope~.md diff --git a/Resources/Documentation/vanilla/scope~.md b/Resources/Documentation/vanilla/scope~.md deleted file mode 100644 index 26794a47a2..0000000000 --- a/Resources/Documentation/vanilla/scope~.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: scope~ -description: use tabwrite~ now -categories: -- object -pdcategory: vanilla -inlets: - 1st: -outlets: - 1st: -draft: true ---- - diff --git a/Source/Objects/LuaObject.h b/Source/Objects/LuaObject.h index abbe6a742b..fc49e669dc 100644 --- a/Source/Objects/LuaObject.h +++ b/Source/Objects/LuaObject.h @@ -31,6 +31,7 @@ class LuaObject : public ObjectBase, public Timer, public NVGContextListener { std::unique_ptr saveDialog; NVGframebuffer* framebuffer = nullptr; + bool imageNeedsRefresh = false; int framebufferWidth = 0, framebufferHeight = 0; struct LuaGuiMessage { @@ -122,6 +123,7 @@ class LuaObject : public ObjectBase, public Timer, public NVGContextListener { void nvgContextDeleted(NVGcontext* nvg) override { if(framebuffer) nvgDeleteFramebuffer(framebuffer); framebuffer = nullptr; + imageNeedsRefresh = true; }; Rectangle getPdBounds() override @@ -508,6 +510,12 @@ class LuaObject : public ObjectBase, public Timer, public NVGContextListener { void timerCallback() override { + if(imageNeedsRefresh && cnv->editor->nvgSurface.getRawContext()) + { + sendRepaintMessage(); + imageNeedsRefresh = false; + } + LuaGuiMessage guiMessage; while(guiMessageQueue.try_dequeue(guiMessage)) { diff --git a/Source/Utility/NanoVGGraphicsContext.cpp b/Source/Utility/NanoVGGraphicsContext.cpp index 870fb127a3..65f89f3e9b 100644 --- a/Source/Utility/NanoVGGraphicsContext.cpp +++ b/Source/Utility/NanoVGGraphicsContext.cpp @@ -410,7 +410,7 @@ void NanoVGGraphicsContext::setFont (const juce::Font& f) nvgFontFace (nvg, name.toUTF8()); nvgFontSize (nvg, font.getHeight() * 0.86f); - //nvgTextLetterSpacing(nvg, -0.275f); + nvgTextLetterSpacing(nvg, -0.275f); } const juce::Font& NanoVGGraphicsContext::getFont() From 00d7c042871a807eba4c818545027d59811c922b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Apr 2024 01:29:19 +0200 Subject: [PATCH 0618/1030] Pull pure-data main branch updates --- Libraries/pure-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/pure-data b/Libraries/pure-data index 6369fc98ca..436a3ef656 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit 6369fc98ca18add9c85d57c3598132c1ad00974c +Subproject commit 436a3ef65653e47c011d100da679ff6a83cbf5e2 From 1326d4558b69440c0db3001db4d28d3bbc3608b2 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Apr 2024 04:00:25 +0200 Subject: [PATCH 0619/1030] Fix zooming jitter --- Libraries/JUCE | 2 +- Source/Canvas.cpp | 40 +++------------------------------------- Source/CanvasViewport.h | 31 +++++++++++++++++++++++++------ Source/PluginEditor.cpp | 1 - 4 files changed, 29 insertions(+), 45 deletions(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index a7e425410c..744f4105bf 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit a7e425410cbf9b4d667dd1a3c289b2e70b9f0e78 +Subproject commit 744f4105bf5c1337365bd76ae6136d701d2b4904 diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 642db9200e..4b5b3c34cb 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -316,19 +316,17 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) if(hasViewport) { nvgTranslate(nvg, -viewport->getViewPositionX(), -viewport->getViewPositionY()); - invalidRegion = invalidRegion.translated(viewport->getViewPositionX(), viewport->getViewPositionY()); nvgScale(nvg, zoom, zoom); + + invalidRegion = invalidRegion.translated(viewport->getViewPositionX(), viewport->getViewPositionY()); invalidRegion /= zoom; - } - - if(hasViewport) { + nvgBeginPath(nvg); nvgRect(nvg, 0, 0, infiniteCanvasSize, infiniteCanvasSize); nvgFillColor(nvg, backgroundColour); nvgFill(nvg); } - if(hasViewport && !getValue(locked)) { auto feather = getRenderScale() > 1.0f ? 0.25f : 0.75f; if(getValue(zoomScale) >= 1.0f) { @@ -346,8 +344,6 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) auto scaledDotSize = jmap(zoom, 1.0f, 0.25f, 1.0f, 4.0f); if (zoom < 0.3f && getRenderScale() <= 1.0f) scaledDotSize = jmap(zoom, 0.3f, 0.25f, 4.0f, 8.0f); - //if (getRenderScale() <= 1.0f && zoom < 0.5f) - // scaledDotSize *= 1.5f; for(int i = 0; i < 4; i++) { @@ -1895,37 +1891,7 @@ void Canvas::valueChanged(Value& v) { // Update zoom if (v.refersToSameSourceAs(zoomScale)) { - - auto newScaleFactor = getValue(v); - - if (approximatelyEqual(newScaleFactor, 0.0f)) { - newScaleFactor = 1.0f; - zoomScale = 1.0f; - } - hideSuggestions(); - - if (!viewport) - return; - - // Get floating point mouse position relative to screen - auto mousePosition = Desktop::getInstance().getMainMouseSource().getScreenPosition(); - // Get mouse position relative to canvas - auto oldPosition = getLocalPoint(nullptr, mousePosition); - // Apply transform and make sure viewport bounds get updated - setTransform(AffineTransform().scaled(newScaleFactor)); - // After zooming, get mouse position relative to canvas again - auto newPosition = getLocalPoint(nullptr, mousePosition); - // Calculate offset to keep our mouse position the same as before this zoom action - auto offset = newPosition - oldPosition; - setTopLeftPosition(getPosition() + offset.roundToInt()); - // This is needed to make sure the viewport the current canvas bounds to the lastVisibleArea variable - // Without this, future calls to getViewPosition() will give wrong results - viewport->resized(); - - // set and trigger the zoom labsetValueExcludingListenerel popup in the bottom left corner - // TODO: move this to viewport, and have one per viewport? - editor->setZoomLabelLevel(newScaleFactor); } else if (v.refersToSameSourceAs(patchWidth)) { // limit canvas width to smallest object (11px) patchWidth = jmax(11, getValue(patchWidth)); diff --git a/Source/CanvasViewport.h b/Source/CanvasViewport.h index 84954d1469..d75d6116ad 100644 --- a/Source/CanvasViewport.h +++ b/Source/CanvasViewport.h @@ -291,8 +291,6 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent , editor(parent) , cnv(cnv) { - lastCanvasZoom = getValue(cnv->zoomScale); - setScrollBarsShown(false, false); setPositioner(new ViewportPositioner(*this)); @@ -360,12 +358,34 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent auto& scale = cnv->zoomScale; - auto value = getValue(scale); + auto newScaleFactor = getValue(scale); // Apply and limit zoom - value = std::clamp(value * scrollFactor, 0.25f, 3.0f); + newScaleFactor = std::clamp(newScaleFactor * scrollFactor, 0.25f, 3.0f); + + if (approximatelyEqual(newScaleFactor, 0.0f)) { + newScaleFactor = 1.0f; + } + + // Get floating point mouse position relative to screen + auto mousePosition = Desktop::getInstance().getMainMouseSource().getScreenPosition(); + // Get mouse position relative to canvas + auto oldPosition = cnv->getLocalPoint(nullptr, mousePosition); + // Apply transform and make sure viewport bounds get updated + cnv->setTransform(AffineTransform().scaled(newScaleFactor)); + // After zooming, get mouse position relative to canvas again + auto newPosition = cnv->getLocalPoint(nullptr, mousePosition); + // Calculate offset to keep our mouse position the same as before this zoom action + auto offset = newPosition - oldPosition; + cnv->setTopLeftPosition(cnv->getPosition() + offset.roundToInt()); + + // This is needed to make sure the viewport the current canvas bounds to the lastVisibleArea variable + // Without this, future calls to getViewPosition() will give wrong results + resized(); + + editor->setZoomLabelLevel(newScaleFactor); - scale = value; + scale = newScaleFactor; } void adjustScrollbarBounds() @@ -472,5 +492,4 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent ViewportScrollBar hbar = ViewportScrollBar(false, this); bool forceRepaintWhileScrolling = false; - float lastCanvasZoom = 1.0f; }; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index d4762c3419..bbbd0ffaf8 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1974,7 +1974,6 @@ bool PluginEditor::highlightSearchTarget(void* target, bool openNewTabIfNeeded) viewport->setViewPosition(pos); cnv->getTabbar()->setCurrentTabIndex(cnv->getTabIndex()); - return true; } From 7ed85e7ee94b3188880c875fd7874dbb3e5aaa2b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Apr 2024 04:01:59 +0200 Subject: [PATCH 0620/1030] Compilation fix --- Libraries/pure-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/pure-data b/Libraries/pure-data index 436a3ef656..a5aa03b922 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit 436a3ef65653e47c011d100da679ff6a83cbf5e2 +Subproject commit a5aa03b9229352c2156c07dac6ad92394e646a26 From 4d11f76d22b0678a2cba5572d4b4ec80df2aef25 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Apr 2024 04:06:40 +0200 Subject: [PATCH 0621/1030] Fixed zooming bug --- Source/CanvasViewport.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/CanvasViewport.h b/Source/CanvasViewport.h index d75d6116ad..48fd5c7988 100644 --- a/Source/CanvasViewport.h +++ b/Source/CanvasViewport.h @@ -353,7 +353,9 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent void mouseMagnify(MouseEvent const& e, float scrollFactor) override { - if (!cnv) + // Check event time to filter out duplicate events + // This is a workaround for a bug in JUCE that can cause mouse events to be duplicated when an object has a MouseListener on its parent + if (e.eventTime == lastZoomTime || !cnv) return; auto& scale = cnv->zoomScale; @@ -386,6 +388,7 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent editor->setZoomLabelLevel(newScaleFactor); scale = newScaleFactor; + lastZoomTime = e.eventTime; } void adjustScrollbarBounds() @@ -484,6 +487,7 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent private: Time lastScrollTime; + Time lastZoomTime; PluginEditor* editor; Canvas* cnv; Rectangle previousBounds; From 84eb5abcf29f35668936f3974d4ba5291bd2b4d4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Apr 2024 04:21:28 +0200 Subject: [PATCH 0622/1030] Fixed comment performance issue --- Source/Objects/CommentObject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Objects/CommentObject.h b/Source/Objects/CommentObject.h index 2496836a3c..a76f14cb45 100644 --- a/Source/Objects/CommentObject.h +++ b/Source/Objects/CommentObject.h @@ -190,7 +190,7 @@ class CommentObject final : public ObjectBase objText = cnv->suggestor->getText(); } - auto colour = object->findColour(PlugDataColour::canvasTextColourId); + auto colour = object->findColour(PlugDataColour::commentTextColourId); int textWidth = getTextSize().getWidth() - 8; if(textRenderer.prepareLayout(objText, Fonts::getDefaultFont().withHeight(15), colour, textWidth, getValue(sizeProperty))) { From 55fc54fc136399ff597799686d086a7c30f75757 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 23 Apr 2024 04:53:16 +0200 Subject: [PATCH 0623/1030] Fixed zooming with keyboard --- Source/CanvasViewport.h | 15 ++++++++------- Source/PluginEditor.cpp | 20 ++++++++------------ 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/Source/CanvasViewport.h b/Source/CanvasViewport.h index 48fd5c7988..43ef48692a 100644 --- a/Source/CanvasViewport.h +++ b/Source/CanvasViewport.h @@ -358,13 +358,15 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent if (e.eventTime == lastZoomTime || !cnv) return; - auto& scale = cnv->zoomScale; - - auto newScaleFactor = getValue(scale); - // Apply and limit zoom - newScaleFactor = std::clamp(newScaleFactor * scrollFactor, 0.25f, 3.0f); + magnify(std::clamp(getValue(cnv->zoomScale) * scrollFactor, 0.25f, 3.0f)); + lastZoomTime = e.eventTime; + } + + + void magnify(float newScaleFactor) + { if (approximatelyEqual(newScaleFactor, 0.0f)) { newScaleFactor = 1.0f; } @@ -387,8 +389,7 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent editor->setZoomLabelLevel(newScaleFactor); - scale = newScaleFactor; - lastZoomTime = e.eventTime; + cnv->zoomScale = newScaleFactor; } void adjustScrollbarBounds() diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index bbbd0ffaf8..603b619fe3 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1667,21 +1667,20 @@ bool PluginEditor::perform(InvocationInfo const& info) } cnv->patch.endUndoSequence("ConnectionPathFind"); - return true; } case CommandIDs::ZoomIn: { - auto& scale = getCurrentCanvas()->zoomScale; - float newScale = getValue(scale) + 0.1f; - scale = static_cast(static_cast(round(std::clamp(newScale, 0.25f, 3.0f) * 10.))) / 10.; - + auto* viewport = dynamic_cast(cnv->viewport.get()); + float newScale = getValue(getCurrentCanvas()->zoomScale) + 0.1f; + newScale = static_cast(static_cast(round(std::clamp(newScale, 0.25f, 3.0f) * 10.))) / 10.; + viewport->magnify(newScale); return true; } case CommandIDs::ZoomOut: { - auto& scale = getCurrentCanvas()->zoomScale; - float newScale = getValue(scale) - 0.1f; - scale = static_cast(static_cast(round(std::clamp(newScale, 0.25f, 3.0f) * 10.))) / 10.; - + auto* viewport = dynamic_cast(cnv->viewport.get()); + float newScale = getValue(getCurrentCanvas()->zoomScale) - 0.1f; + newScale = static_cast(static_cast(round(std::clamp(newScale, 0.25f, 3.0f) * 10.))) / 10.; + viewport->magnify(newScale); return true; } case CommandIDs::ZoomNormal: { @@ -1708,7 +1707,6 @@ bool PluginEditor::perform(InvocationInfo const& info) return true; } case CommandIDs::NextTab: { - auto* tabbar = cnv->getTabbar(); int currentIdx = cnv->getTabIndex() + 1; @@ -1767,7 +1765,6 @@ bool PluginEditor::perform(InvocationInfo const& info) return true; } case CommandIDs::ToggleDSP: { - if(pd_getdspstate()) { pd->releaseDSP(); @@ -1779,7 +1776,6 @@ bool PluginEditor::perform(InvocationInfo const& info) return true; } default: { - cnv = getCurrentCanvas(); // This should close any opened editors before creating a new object From 4631bf6e2accc25834384cc6121ce47e2e58451a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 24 Apr 2024 00:52:23 +0200 Subject: [PATCH 0624/1030] Merge plaits~ improvements from ELSE --- Libraries/pd-else | 2 +- Libraries/pd-lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/pd-else b/Libraries/pd-else index fadb5734e1..1d253e9879 160000 --- a/Libraries/pd-else +++ b/Libraries/pd-else @@ -1 +1 @@ -Subproject commit fadb5734e1312eb86f5bd7467a072a464ed20740 +Subproject commit 1d253e98794faa690a43c5dfcff3e55f699ede0b diff --git a/Libraries/pd-lua b/Libraries/pd-lua index 10089fc8ae..34cd81550e 160000 --- a/Libraries/pd-lua +++ b/Libraries/pd-lua @@ -1 +1 @@ -Subproject commit 10089fc8aebe1d734a4710cab8772afff0377778 +Subproject commit 34cd81550e796ee58a25dd82c11ea8841938352a From 2ff7daffc8dd690f198e58ff11fa8053cc400d64 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 24 Apr 2024 00:53:07 +0200 Subject: [PATCH 0625/1030] Increment version suffix --- Source/Utility/Config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index 7e7297f4b8..308de6aadd 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -51,7 +51,7 @@ struct ProjectInfo { #else static inline File const appDataDir = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory).getChildFile("plugdata"); #endif - static inline String const versionSuffix = "-test2"; + static inline String const versionSuffix = "-test3"; static inline File const versionDataDir = appDataDir.getChildFile("Versions").getChildFile(ProjectInfo::versionString + versionSuffix); }; From 3f6d3b1d31d162afdaddf82a7f3e7fb304842f57 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 24 Apr 2024 01:38:52 +0200 Subject: [PATCH 0626/1030] More lasso optimisation --- Libraries/JUCE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index 744f4105bf..70f3136bc4 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit 744f4105bf5c1337365bd76ae6136d701d2b4904 +Subproject commit 70f3136bc40d442b81641d7748917b51e1c9e962 From b0dce48513888f60ffc7369aa8ba10f786a80c9e Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 24 Apr 2024 18:17:07 +0930 Subject: [PATCH 0627/1030] display plugin latency in editor if changed via a PD patch --- Source/PluginProcessor.cpp | 3 + Source/Statusbar.cpp | 136 +++++++++++++++++++++++++++++++++++++ Source/Statusbar.h | 7 ++ 3 files changed, 146 insertions(+) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index bbc062a9d2..2e6871d4ec 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1635,6 +1635,9 @@ void PluginProcessor::handleAsyncUpdate() void PluginProcessor::performLatencyCompensationChange(float value) { customLatencySamples = floor(value); + for (auto& editor : getEditors()) { + editor->statusbar->setLatencyDisplay(customLatencySamples); + } triggerAsyncUpdate(); } diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index d5b7cb73f4..3ddc6fd5b1 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -23,6 +23,125 @@ #include "Components/ArrowPopupMenu.h" +class LatencyDisplayButton : public Component, public MultiTimer, public SettableTooltipClient { + Label latencyValue; + Label icon; + bool isHover = false; + Colour bgColour; + + enum TimerRoutine { Timeout, Animate }; + float alpha = 1.0f; + bool fading = false; +public: + std::function onClick = []() {}; + LatencyDisplayButton() + { + icon.setFont(Fonts::getIconFont()); + icon.setText(Icons::GlyphDelay, dontSendNotification); + + icon.setJustificationType(Justification::centredLeft); + latencyValue.setJustificationType(Justification::centredRight); + + setInterceptsMouseClicks(true, false); + addMouseListener(this, true); + + setTooltip("Plugin latency, click to reset"); + + addAndMakeVisible(latencyValue); + addAndMakeVisible(icon); + + buttonStateChanged(); + }; + + void lookAndFeelChanged() override + { + buttonStateChanged(); + } + + void timerCallback(int ID) override + { + switch (ID) { + case Timeout: + startTimer(Animate, 1000 / 30.0f); + break; + case Animate: + alpha = pow(alpha, 1.0f / 2.2f); + alpha -= 0.02f; + alpha = pow(alpha, 2.2f); + fading = true; + if (alpha <= 0.01f) { + stopTimer(Animate); + setVisible(false); + } + buttonStateChanged(); + break; + default: + break; + } + } + + void paint(Graphics& g) override + { + auto b = getLocalBounds().reduced(1, 6).toFloat(); + g.setColour(bgColour); + g.fillRoundedRectangle(b, Corners::defaultCornerRadius); + } + + void setLatencyValue(const int value) + { + latencyValue.setText(String(value) + " smpl", dontSendNotification); + if (value == 64) { + startTimer(Timeout, 1000 / 3.0f); + } else { + stopTimer(Timeout); + stopTimer(Animate); + fading = false; + setVisible(true); + alpha = 1.0f; + buttonStateChanged(); + } + } + + void mouseDown(const MouseEvent& e) override + { + onClick(); + } + + void buttonStateChanged() + { + bgColour = getLookAndFeel().findColour(isHover ? PlugDataColour::toolbarHoverColourId : PlugDataColour::toolbarActiveColourId).withAlpha(alpha); + auto textColour = bgColour.contrasting().withAlpha(alpha); + icon.setColour(Label::textColourId, textColour); + latencyValue.setColour(Label::textColourId, textColour); + + repaint(); + } + + void mouseEnter(const MouseEvent& e) override + { + if (fading) + return; + + isHover = true; + buttonStateChanged(); + } + + void mouseExit(const MouseEvent& e) override + { + if (fading) + return; + + isHover = false; + buttonStateChanged(); + } + + void resized() override + { + icon.setBounds(0, 0, getHeight(), getHeight()); + latencyValue.setBounds(getHeight(), 0, getWidth() - getHeight(), getHeight()); + } +}; + class OversampleSelector : public TextButton { class OversampleSettingsPopup : public Component { @@ -637,6 +756,14 @@ Statusbar::Statusbar(PluginProcessor* processor) oversampleSelector->setButtonText(String(1 << pd->oversampling) + "x"); addAndMakeVisible(*oversampleSelector); + latencyDisplayButton = std::make_unique(); + addChildComponent(latencyDisplayButton.get()); + latencyDisplayButton->onClick = [this](){ + currentLatency = 64; + pd->setLatencySamples(64); + latencyDisplayButton->setVisible(false); + }; + powerButton.setButtonText(Icons::Power); protectButton.setButtonText(Icons::Protection); centreButton.setButtonText(Icons::Centre); @@ -851,6 +978,15 @@ void Statusbar::resized() midiBlinker->setBounds(position(40, true) - 8, 0, 55, getHeight()); fourthSeparatorPosition = position(10, true); cpuMeter->setBounds(position(48, true), 0, 50, getHeight()); + latencyDisplayButton->setBounds(position(100, true), 0, 100, getHeight()); +} + +void Statusbar::setLatencyDisplay(int value) +{ + if (currentLatency != value) { + currentLatency = value; + latencyDisplayButton->setLatencyValue(value); + } } void Statusbar::audioProcessedChanged(bool audioProcessed) diff --git a/Source/Statusbar.h b/Source/Statusbar.h index 6162c7058f..fb53802899 100644 --- a/Source/Statusbar.h +++ b/Source/Statusbar.h @@ -20,6 +20,7 @@ class CPUMeter; class PluginProcessor; class VolumeSlider; class OversampleSelector; +class LatencyDisplayButton; class StatusbarSource : public Timer { @@ -87,6 +88,8 @@ class Statusbar : public Component void audioProcessedChanged(bool audioProcessed) override; + void setLatencyDisplay(int value); + bool wasLocked = false; // Make sure it doesn't re-lock after unlocking (because cmd is still down) std::unique_ptr levelMeter; @@ -104,8 +107,12 @@ class Statusbar : public Component std::unique_ptr oversampleSelector; + std::unique_ptr latencyDisplayButton; + Label zoomLabel; + int currentLatency = 64; + Value showDirection; static constexpr int statusbarHeight = 30; From 727d40cc531be6455b94e9e6e0969af37e585b16 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 24 Apr 2024 12:29:41 +0200 Subject: [PATCH 0628/1030] Fixed issue with warning icon --- Resources/Fonts/IconFont.ttf | Bin 61264 -> 61160 bytes Source/Dialogs/Dialogs.cpp | 10 ++++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Resources/Fonts/IconFont.ttf b/Resources/Fonts/IconFont.ttf index 5263f10d14d3c8ea97d288839efeb979a02cb9e8..b85af7ebc0476ddc52a7d9959be9650c0e42e602 100644 GIT binary patch delta 810 zcmW+!ZAepL6n@`(@3yO(?t9L)O}%&C>HNCx<8Ih8XHu5B)|$1{{LYp+6#AJ6Oxy7diJ0tf(z z0S^EQT&GWaN?vbw3303b*1@oFc9_ZmCmA zDBG1Q@w)it_;FR>7*I3nF7=$osA<+rYBB9w?Vp6MgixY0@p+OnsW0gVp&+`6UD8QT z>m<5Ba!PVX@+y@`Ra2vSOrNNKmU1Z-q`FfhhI&IV&7QWAK9^COF_RUd)wGMgPcLWp zXa6#OF}*RZn3d)sbLXDL&ZwC|Yo;}1^Vyb<58GAt@QIOJTkdS0ByZH=b__To`8oM_ z^AAo1&WO$hi=0J~;>zMrB}~bNbH|l);lYJbx5(Xg(dNl7Rh1rGig=^FOIMc4Y-O`$ zhvlB~$K|04yrT6gUdgR|Qzfbzs8&|@)yQflYoFFdtLvuf>#rf#1vi=+v73cW?S6rO zuz9%UZp&e-d(4LSAe|`i{8jLRoKJdm;ER@cIL0EuU3Xc%8=r|u#ru3M|H=6O>;llfuvkW{GhCd?Fl}PyVlH6b!TgCOjAa@t1FIJ6KGqj(8f-Ca zZ`jXqSa4Ku?BZnLbl_~_Ji*1q)x-6UJC6Gh&lFw>-d((Z_@ek`@pJHp@y`(u6R;6T z5|}4&N{~S?O>l|e6QM;y4}^JyEreTyZ;7ahEEBmP>Lj{Gj7_Xf?4P)o_z4NVdWkTJ zHIhP-5t54}Ur4QxHj&Aa*(J*+nqXc_0s|!~Cz>VND7qthPxPM{o0uiBHgQ&Q*Wz8`ZzcF7oJ#aae3x`2*-j?; zQ_74~vvh~d1z8r^H92c?9dhsFJ<4BKU{uhy*@9ytQ#~KUrhm`b{a9NWxEc5vgc;-+ zlo`|+j2X-stQkBQd>8^4Vi^*F=|j;-R8WLnQOQ)$L|jl&P*o8~fW_1mmDB~z#6^uw zpaI-nRcNS%VA5l|4S8Kw{@2sE)CC<@ii*vazF-u}0ceLYLwuicD>|IRZS z+S~s-Z_mhBw~6@|RDjU|C{n-a6Ds$YGMajpHx9o=?HL_dUOUwPp3IVa^WQnfLdSm> woERM$4ICRcY;b}I=psaZhX8FX1R2lhz-Z`Dzj-sza#VrMJ=_tGSwLwV0Pl|>X#fBK diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index 50b63cd6bf..b38e254a4a 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -316,10 +316,12 @@ void Dialogs::showOkayCancelDialog(std::unique_ptr* target, Component* p } void paint(Graphics& g) override - { - g.setColour(findColour(PlugDataColour::panelTextColourId)); - g.setFont(Fonts::getIconFont().withHeight(48)); - g.drawFittedText(Icons::Warning, getLocalBounds().removeFromTop(90), Justification::centred, 1); + { + AttributedString warningIcon(Icons::Warning); + warningIcon.setFont(Fonts::getIconFont().withHeight(48)); + warningIcon.setColour(findColour(PlugDataColour::panelTextColourId)); + warningIcon.setJustification(Justification::centred); + warningIcon.draw(g, getLocalBounds().toFloat().removeFromTop(90)); auto contentBounds = getLocalBounds().withTrimmedTop(63).reduced(16); layout.draw(g, contentBounds.removeFromTop(48).toFloat()); From e493f32642f3ece8d1d8f03702dcff0e6992284f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 24 Apr 2024 13:20:52 +0200 Subject: [PATCH 0629/1030] Optimise colour lookups --- Libraries/JUCE | 2 +- Source/Objects/ArrayObject.h | 18 ++++++++---------- Source/Objects/AtomHelper.h | 10 +++++----- Source/Objects/BangObject.h | 6 +++--- Source/Objects/BicoeffObject.h | 10 +++++----- Source/Objects/ButtonObject.h | 6 +++--- Source/Objects/CanvasObject.h | 2 +- Source/Objects/CommentObject.h | 8 ++++---- Source/Objects/FloatAtomObject.h | 20 ++++++++++---------- Source/Objects/FunctionObject.h | 4 ++-- Source/Objects/GraphOnParent.h | 10 +++++----- Source/Objects/IEMHelper.h | 3 +-- Source/Objects/KeyboardObject.h | 8 ++++---- Source/Objects/KnobObject.h | 4 ++-- Source/Objects/ListObject.h | 16 ++++++++-------- Source/Objects/LuaObject.h | 2 +- Source/Objects/MessageObject.h | 14 +++++++------- Source/Objects/MessboxObject.h | 6 +++--- Source/Objects/MousePadObject.h | 2 +- Source/Objects/NoteObject.h | 10 +++++----- Source/Objects/NumberObject.h | 8 ++++---- Source/Objects/NumboxTildeObject.h | 4 ++-- Source/Objects/ObjectBase.cpp | 4 ++-- Source/Objects/OpenFileObject.h | 8 ++++---- Source/Objects/PictureObject.h | 4 ++-- Source/Objects/RadioObject.h | 6 +++--- Source/Objects/ScopeObject.h | 2 +- Source/Objects/SliderObject.h | 6 +++--- Source/Objects/SymbolAtomObject.h | 16 ++++++++-------- Source/Objects/TextObject.h | 12 ++++++------ Source/Objects/ToggleObject.h | 4 ++-- Source/Objects/VUMeterObject.h | 6 +++--- 32 files changed, 119 insertions(+), 122 deletions(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index 70f3136bc4..336a8e8397 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit 70f3136bc40d442b81641d7748917b51e1c9e962 +Subproject commit 336a8e83973eb72b091c71ff6f0eb87a843500ad diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index 6c875bf4d6..bd3eba8390 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -379,7 +379,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess nvgFontSize(nvg, 11); nvgFontFace(nvg, "Inter-Regular"); nvgTextAlign(nvg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); - nvgFillColor(nvg, convertColour(object->findColour(PlugDataColour::canvasTextColourId))); + nvgFillColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId))); nvgText(nvg, position.x, position.y, errorText.toRawUTF8(), nullptr); error = false; } else if(visible) { @@ -391,7 +391,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess { if (error) { // TODO: error colour - Fonts::drawText(g, "array " + getUnexpandedName() + " is invalid", 0, 0, getWidth(), getHeight(), object->findColour(PlugDataColour::canvasTextColourId), 15, Justification::centred); + Fonts::drawText(g, "array " + getUnexpandedName() + " is invalid", 0, 0, getWidth(), getHeight(), LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId), 15, Justification::centred); error = false; } else if(visible) { paintGraph(g); @@ -611,7 +611,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess int colour = template_getfloat(templ, gensym("color"), scalar->sc_vec, 1); if (colour <= 0) { - return object->findColour(PlugDataColour::guiObjectInternalOutlineColour); + return LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour); } auto rangecolor = [](int n) /* 0 to 9 in 5 steps */ @@ -630,7 +630,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess return Colour(red, green, blue); } - return object->findColour(PlugDataColour::guiObjectInternalOutlineColour); + return LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour); } void valueChanged(Value& value) override @@ -1113,8 +1113,6 @@ class ArrayEditorDialog : public Component { { g.setColour(findColour(PlugDataColour::guiObjectBackgroundColourId)); g.drawRoundedRectangle(getLocalBounds().toFloat(), Corners::windowCornerRadius, 1.0f); - - } void paint(Graphics& g) override @@ -1221,9 +1219,9 @@ class ArrayObject final : public ObjectBase { void render(NVGcontext* nvg) override { auto b = getLocalBounds().toFloat().reduced(0.5f); - auto backgroundColour = convertColour(object->findColour(PlugDataColour::guiObjectBackgroundColourId)); - auto selectedOutlineColour = convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(object->findColour(PlugDataColour::objectOutlineColourId)); + auto backgroundColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectBackgroundColourId)); + auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), backgroundColour, object->isSelected() ? selectedOutlineColour : outlineColour, Corners::objectCornerRadius); @@ -1273,7 +1271,7 @@ class ArrayObject final : public ObjectBase { label->setBounds(bounds); label->setText(title, dontSendNotification); - label->setColour(Label::textColourId, object->findColour(PlugDataColour::canvasTextColourId)); + label->setColour(Label::textColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); object->cnv->addAndMakeVisible(label.get()); } diff --git a/Source/Objects/AtomHelper.h b/Source/Objects/AtomHelper.h index c53afe738f..867188fae2 100644 --- a/Source/Objects/AtomHelper.h +++ b/Source/Objects/AtomHelper.h @@ -73,8 +73,8 @@ class AtomHelper { sendSymbol = getSendSymbol(); receiveSymbol = getReceiveSymbol(); - gui->getLookAndFeel().setColour(Label::textWhenEditingColourId, object->findColour(Label::textWhenEditingColourId)); - gui->getLookAndFeel().setColour(Label::textColourId, object->findColour(Label::textColourId)); + gui->getLookAndFeel().setColour(Label::textWhenEditingColourId, LookAndFeel::getDefaultLookAndFeel().findColour(Label::textWhenEditingColourId)); + gui->getLookAndFeel().setColour(Label::textColourId, LookAndFeel::getDefaultLookAndFeel().findColour(Label::textColourId)); } int getWidthInChars() @@ -291,9 +291,9 @@ class AtomHelper { label->setFont(Font(fontHeight)); label->setText(text, dontSendNotification); - auto textColour = object->findColour(PlugDataColour::canvasTextColourId); - if (std::abs(textColour.getBrightness() - object->findColour(PlugDataColour::canvasBackgroundColourId).getBrightness()) < 0.3f) { - textColour = object->findColour(PlugDataColour::canvasBackgroundColourId).contrasting(); + auto textColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId); + if (std::abs(textColour.getBrightness() - LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasBackgroundColourId).getBrightness()) < 0.3f) { + textColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasBackgroundColourId).contrasting(); } label->setColour(Label::textColourId, textColour); diff --git a/Source/Objects/BangObject.h b/Source/Objects/BangObject.h index 40389cb753..dae7a4e2e1 100644 --- a/Source/Objects/BangObject.h +++ b/Source/Objects/BangObject.h @@ -109,9 +109,9 @@ class BangObject final : public ObjectBase { auto foregroundColour = convertColour(iemHelper.getForegroundColour()); // TODO: this is some bad threading practice! auto backgroundColour = convertColour(iemHelper.getBackgroundColour()); - auto selectedOutlineColour = convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(object->findColour(PlugDataColour::objectOutlineColourId)); - auto internalLineColour = convertColour(object->findColour(PlugDataColour::guiObjectInternalOutlineColour)); + auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); + auto internalLineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour)); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), backgroundColour, object->isSelected() ? selectedOutlineColour : outlineColour, Corners::objectCornerRadius); diff --git a/Source/Objects/BicoeffObject.h b/Source/Objects/BicoeffObject.h index cbcafc3332..c8f1d5ab86 100644 --- a/Source/Objects/BicoeffObject.h +++ b/Source/Objects/BicoeffObject.h @@ -191,13 +191,13 @@ class BicoeffGraph : public Component, public NVGComponent { void render(NVGcontext* nvg) override { auto b = getLocalBounds().reduced(0.5f); - auto backgroundColour = convertColour(object->findColour(PlugDataColour::guiObjectBackgroundColourId)); - auto selectedOutlineColour = convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(object->findColour(PlugDataColour::objectOutlineColourId)); + auto backgroundColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectBackgroundColourId)); + auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), backgroundColour, object->isSelected() ? selectedOutlineColour : outlineColour, Corners::objectCornerRadius); - nvgStrokeColor(nvg, convertColour(object->findColour(PlugDataColour::guiObjectInternalOutlineColour))); + nvgStrokeColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour))); nvgBeginPath(nvg); nvgMoveTo(nvg, filterX1 * getWidth(), 0.0f); nvgLineTo(nvg, filterX1 * getWidth(), getHeight()); @@ -216,7 +216,7 @@ class BicoeffGraph : public Component, public NVGComponent { nvgStrokeWidth(nvg, 1.0f); nvgLineStyle(nvg, NVG_BUTT); setJUCEPath(nvg, magnitudePath); - nvgStrokeColor(nvg, convertColour(object->findColour(PlugDataColour::canvasTextColourId))); + nvgStrokeColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId))); nvgStroke(nvg); } diff --git a/Source/Objects/ButtonObject.h b/Source/Objects/ButtonObject.h index cd932f38f4..3c98a82492 100644 --- a/Source/Objects/ButtonObject.h +++ b/Source/Objects/ButtonObject.h @@ -133,9 +133,9 @@ class ButtonObject : public ObjectBase { auto foregroundColour = convertColour(Colour::fromString(primaryColour.toString())); auto backgroundColour = convertColour(Colour::fromString(secondaryColour.toString())); - auto selectedOutlineColour = convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(object->findColour(PlugDataColour::objectOutlineColourId)); - auto internalLineColour = convertColour(object->findColour(PlugDataColour::guiObjectInternalOutlineColour)); + auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); + auto internalLineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour)); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), backgroundColour, object->isSelected() ? selectedOutlineColour : outlineColour, Corners::objectCornerRadius); diff --git a/Source/Objects/CanvasObject.h b/Source/Objects/CanvasObject.h index f5e1341fe6..b0e497ef12 100644 --- a/Source/Objects/CanvasObject.h +++ b/Source/Objects/CanvasObject.h @@ -129,7 +129,7 @@ class CanvasObject final : public ObjectBase { return; } - nvgStrokeColor(nvg, convertColour(object->isSelected() ? object->findColour(PlugDataColour::objectSelectedOutlineColourId) : bgcolour.contrasting(0.75f))); + nvgStrokeColor(nvg, convertColour(object->isSelected() ? LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId) : bgcolour.contrasting(0.75f))); nvgStrokeWidth(nvg, 1.0f); nvgBeginPath(nvg); nvgRoundedRect(nvg, draggableRect.getX() + 1.0f, draggableRect.getY() + 1.0f, draggableRect.getWidth() - 2.0f, draggableRect.getHeight() - 2.0f, Corners::objectCornerRadius); diff --git a/Source/Objects/CommentObject.h b/Source/Objects/CommentObject.h index a76f14cb45..48b417c8e0 100644 --- a/Source/Objects/CommentObject.h +++ b/Source/Objects/CommentObject.h @@ -49,7 +49,7 @@ class CommentObject final : public ObjectBase { if (!editor) { auto textArea = border.subtractedFrom(getLocalBounds()); - textRenderer.renderText(nvg, getText(), Fonts::getDefaultFont().withHeight(15), object->findColour(PlugDataColour::commentTextColourId), textArea, getImageScale(), getValue(sizeProperty)); + textRenderer.renderText(nvg, getText(), Fonts::getDefaultFont().withHeight(15), LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::commentTextColourId), textArea, getImageScale(), getValue(sizeProperty)); } else { imageRenderer.renderComponentFromImage(nvg, *editor, getImageScale()); @@ -60,7 +60,7 @@ class CommentObject final : public ObjectBase { auto selected = object->isSelected(); if (!locked && (object->isMouseOverOrDragging(true) || selected) && !cnv->isGraph) { - g.setColour(object->findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::objectOutlineColourId)); + g.setColour(LookAndFeel::getDefaultLookAndFeel().findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::objectOutlineColourId)); g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(0.5f), Corners::objectCornerRadius, 1.0f); } @@ -121,7 +121,7 @@ class CommentObject final : public ObjectBase addAndMakeVisible(editor.get()); editor->grabKeyboardFocus(); - editor->setColour(TextEditor::textColourId, object->findColour(PlugDataColour::commentTextColourId)); + editor->setColour(TextEditor::textColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::commentTextColourId)); editor->onFocusLost = [this]() { hideEditor(); @@ -190,7 +190,7 @@ class CommentObject final : public ObjectBase objText = cnv->suggestor->getText(); } - auto colour = object->findColour(PlugDataColour::commentTextColourId); + auto colour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::commentTextColourId); int textWidth = getTextSize().getWidth() - 8; if(textRenderer.prepareLayout(objText, Fonts::getDefaultFont().withHeight(15), colour, textWidth, getValue(sizeProperty))) { diff --git a/Source/Objects/FloatAtomObject.h b/Source/Objects/FloatAtomObject.h index 11b4c307e5..c5f4339a6d 100644 --- a/Source/Objects/FloatAtomObject.h +++ b/Source/Objects/FloatAtomObject.h @@ -130,15 +130,15 @@ class FloatAtomObject final : public ObjectBase { void lookAndFeelChanged() override { - input.setColour(Label::textWhenEditingColourId, object->findColour(PlugDataColour::canvasTextColourId)); - input.setColour(Label::textColourId, object->findColour(PlugDataColour::canvasTextColourId)); - input.setColour(TextEditor::textColourId, object->findColour(PlugDataColour::canvasTextColourId)); + input.setColour(Label::textWhenEditingColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); + input.setColour(Label::textColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); + input.setColour(TextEditor::textColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); repaint(); } void paintOverChildren(Graphics& g) override { - g.setColour(object->findColour(PlugDataColour::guiObjectInternalOutlineColour)); + g.setColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour)); Path triangle; triangle.addTriangle(Point(getWidth() - 8, 0), Point(getWidth(), 0), Point(getWidth(), 8)); @@ -153,7 +153,7 @@ class FloatAtomObject final : public ObjectBase { g.restoreState(); bool selected = object->isSelected() && !cnv->isGraph; - auto outlineColour = object->findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); + auto outlineColour = LookAndFeel::getDefaultLookAndFeel().findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); g.setColour(outlineColour); g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(0.5f), Corners::objectCornerRadius, 1.0f); @@ -161,7 +161,7 @@ class FloatAtomObject final : public ObjectBase { bool highlighed = hasKeyboardFocus(true) && ::getValue(object->locked); if (highlighed) { - g.setColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); + g.setColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(1.0f), Corners::objectCornerRadius, 2.0f); } } @@ -169,9 +169,9 @@ class FloatAtomObject final : public ObjectBase { void render(NVGcontext* nvg) override { auto b = getLocalBounds().toFloat().reduced(0.5f); - auto backgroundColour = convertColour(object->findColour(PlugDataColour::guiObjectBackgroundColourId)); - auto selectedOutlineColour = convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(object->findColour(PlugDataColour::objectOutlineColourId)); + auto backgroundColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectBackgroundColourId)); + auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); bool highlighed = hasKeyboardFocus(true) && ::getValue(object->locked); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), backgroundColour, (object->isSelected() || highlighed) ? selectedOutlineColour : outlineColour, Corners::objectCornerRadius); @@ -180,7 +180,7 @@ class FloatAtomObject final : public ObjectBase { nvgIntersectRoundedScissor(nvg, b.getX() + 0.25f, b.getY() + 0.25f, b.getWidth() - 0.5f, b.getHeight() - 0.5f, Corners::objectCornerRadius); - nvgFillColor(nvg, convertColour(object->findColour(PlugDataColour::guiObjectInternalOutlineColour))); + nvgFillColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour))); nvgBeginPath(nvg); nvgMoveTo(nvg, b.getRight() - 8, b.getY()); nvgLineTo(nvg, b.getRight(), b.getY()); diff --git a/Source/Objects/FunctionObject.h b/Source/Objects/FunctionObject.h index b5fa93eeac..5c1cf7d8ea 100644 --- a/Source/Objects/FunctionObject.h +++ b/Source/Objects/FunctionObject.h @@ -110,8 +110,8 @@ class FunctionObject final : public ObjectBase { auto b = getLocalBounds().toFloat().reduced(0.5f); auto backgroundColour = convertColour(Colour::fromString(secondaryColour.toString())); auto foregroundColour = convertColour(Colour::fromString(primaryColour.toString())); - auto selectedOutlineColour = convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(object->findColour(PlugDataColour::objectOutlineColourId)); + auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), backgroundColour, selected ? selectedOutlineColour : outlineColour, Corners::objectCornerRadius); diff --git a/Source/Objects/GraphOnParent.h b/Source/Objects/GraphOnParent.h index 561308c044..4dfdbd3d01 100644 --- a/Source/Objects/GraphOnParent.h +++ b/Source/Objects/GraphOnParent.h @@ -217,7 +217,7 @@ class GraphOnParent final : public ObjectBase { // Strangly, the title goes below the graph content in pd if (!getValue(hideNameAndArgs) && getText() != "graph") { auto text = getText(); - textRenderer.renderText(nvg, text, Fonts::getDefaultFont().withHeight(13), object->findColour(PlugDataColour::canvasTextColourId), Rectangle(5, 0, getWidth() - 5, 16), getImageScale(), getWidth()); + textRenderer.renderText(nvg, text, Fonts::getDefaultFont().withHeight(13), LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId), Rectangle(5, 0, getWidth() - 5, 16), getImageScale(), getWidth()); } Canvas* topLevel = cnv; @@ -242,8 +242,8 @@ class GraphOnParent final : public ObjectBase { nvgRestore(nvg); } - auto selectedOutlineColour = convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(object->findColour(PlugDataColour::objectOutlineColourId)); + auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); nvgBeginPath(nvg); nvgRoundedRect(nvg, b.getX(), b.getY(), b.getWidth() - 0.5f, b.getHeight() - 0.5f, Corners::objectCornerRadius); @@ -252,13 +252,13 @@ class GraphOnParent final : public ObjectBase { nvgStroke(nvg); if (isOpenedInSplitView) { - nvgFillColor(nvg, convertColour(object->findColour(PlugDataColour::guiObjectBackgroundColourId))); + nvgFillColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectBackgroundColourId))); nvgFill(nvg); nvgBeginPath(nvg); nvgFontFace(nvg, "Inter-Regular"); nvgFontSize(nvg, 12.0f); - nvgFillColor(nvg, convertColour(object->findColour(PlugDataColour::commentTextColourId))); // why comment colour? + nvgFillColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::commentTextColourId))); // why comment colour? nvgTextAlign(nvg, NVG_ALIGN_TOP | NVG_ALIGN_CENTER); nvgText(nvg, b.getCentreX(), b.getCentreY(), "Graph opened in split view", nullptr); } diff --git a/Source/Objects/IEMHelper.h b/Source/Objects/IEMHelper.h index de5ce99416..39df6d7588 100644 --- a/Source/Objects/IEMHelper.h +++ b/Source/Objects/IEMHelper.h @@ -31,7 +31,7 @@ class IEMHelper { secondaryColour = Colour(getBackgroundColour()).toString(); labelColour = Colour(getLabelColour()).toString(); - gui->getLookAndFeel().setColour(Label::textWhenEditingColourId, object->findColour(Label::textWhenEditingColourId)); + gui->getLookAndFeel().setColour(Label::textWhenEditingColourId, LookAndFeel::getDefaultLookAndFeel().findColour(Label::textWhenEditingColourId)); gui->getLookAndFeel().setColour(Label::textColourId, Colour::fromString(primaryColour.toString())); gui->getLookAndFeel().setColour(TextButton::buttonOnColourId, Colour::fromString(primaryColour.toString())); @@ -108,7 +108,6 @@ class IEMHelper { auto colour = "#FF" + atom.toString().fromFirstOccurrenceOf("#", false, false); gui->setParameterExcludingListener(targetValue, colour); } else { - int iemcolor = atom.getFloat(); if (iemcolor >= 0) { diff --git a/Source/Objects/KeyboardObject.h b/Source/Objects/KeyboardObject.h index 63f0c51903..205e8e0202 100644 --- a/Source/Objects/KeyboardObject.h +++ b/Source/Objects/KeyboardObject.h @@ -130,7 +130,7 @@ class MIDIKeyboard : public MidiKeyboardState, public MidiKeyboardComponent { if (isOver) c = Colour(235, 235, 235); if (isDown) - c = object->findColour(PlugDataColour::dataColourId); + c = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::dataColourId); area = area.reduced(0.0f, 0.5f); @@ -153,7 +153,7 @@ class MIDIKeyboard : public MidiKeyboardState, public MidiKeyboardComponent { // don't draw the first separator line to fix object look if (midiNoteNumber != getRangeStart()) { - g.setColour(object->findColour(PlugDataColour::outlineColourId)); + g.setColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::outlineColourId)); g.fillRect(area.withWidth(1.0f)); } @@ -194,7 +194,7 @@ class MIDIKeyboard : public MidiKeyboardState, public MidiKeyboardComponent { if (isOver) c = Colour(101, 101, 101); if (isDown) - c = object->findColour(PlugDataColour::dataColourId).darker(0.5f); + c = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::dataColourId).darker(0.5f); g.setColour(c); g.fillRect(area); @@ -508,7 +508,7 @@ class KeyboardObject final : public ObjectBase void paintOverChildren(Graphics& g) override { bool selected = object->isSelected() && !cnv->isGraph; - auto outlineColour = object->findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::objectOutlineColourId); + auto outlineColour = LookAndFeel::getDefaultLookAndFeel().findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::objectOutlineColourId); g.setColour(outlineColour); g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(0.5f), Corners::objectCornerRadius, 1.0f); diff --git a/Source/Objects/KnobObject.h b/Source/Objects/KnobObject.h index 36800a2435..21ded5db80 100644 --- a/Source/Objects/KnobObject.h +++ b/Source/Objects/KnobObject.h @@ -435,7 +435,7 @@ class KnobObject : public ObjectBase { auto b = getLocalBounds().toFloat().reduced(0.5f); if (::getValue(outline)) { bool selected = object->isSelected() && !cnv->isGraph; - auto outlineColour = object->findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); + auto outlineColour = LookAndFeel::getDefaultLookAndFeel().findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), convertColour(Colour::fromString(secondaryColour.toString())), convertColour(outlineColour), Corners::objectCornerRadius); } else { @@ -448,7 +448,7 @@ class KnobObject : public ObjectBase { nvgCircle(nvg, circleBounds.getCentreX(), circleBounds.getCentreY(), circleBounds.getWidth() / 2.0f); nvgFill(nvg); - nvgStrokeColor(nvg, convertColour(object->findColour(objectOutlineColourId))); + nvgStrokeColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(objectOutlineColourId))); nvgStrokeWidth(nvg, 1.0f); nvgStroke(nvg); } diff --git a/Source/Objects/ListObject.h b/Source/Objects/ListObject.h index 1e83502130..00784e81e9 100644 --- a/Source/Objects/ListObject.h +++ b/Source/Objects/ListObject.h @@ -166,9 +166,9 @@ class ListObject final : public ObjectBase { void render(NVGcontext* nvg) override { auto b = getLocalBounds().toFloat().reduced(0.5f); - auto backgroundColour = convertColour(object->findColour(PlugDataColour::guiObjectBackgroundColourId)); - auto selectedOutlineColour = convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(object->findColour(PlugDataColour::objectOutlineColourId)); + auto backgroundColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectBackgroundColourId)); + auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), backgroundColour, object->isSelected() ? selectedOutlineColour : outlineColour, Corners::objectCornerRadius); @@ -178,7 +178,7 @@ class ListObject final : public ObjectBase { nvgIntersectRoundedScissor(nvg, b.getX() + 0.25f, b.getY() + 0.25f, b.getWidth() - 0.5f, b.getHeight() - 0.5f, Corners::objectCornerRadius); - nvgFillColor(nvg, convertColour(object->findColour(PlugDataColour::guiObjectInternalOutlineColour))); + nvgFillColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour))); nvgBeginPath(nvg); nvgMoveTo(nvg, b.getRight() - 8, b.getY()); nvgLineTo(nvg, b.getRight(), b.getY()); @@ -207,7 +207,7 @@ class ListObject final : public ObjectBase { bool highlighed = hasKeyboardFocus(true) && getValue(object->locked); if (highlighed) { - nvgStrokeColor(nvg, convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId))); + nvgStrokeColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId))); nvgStrokeWidth(nvg, 2.0f); nvgBeginPath(nvg); nvgRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), Corners::objectCornerRadius); @@ -218,9 +218,9 @@ class ListObject final : public ObjectBase { void lookAndFeelChanged() override { - listLabel.setColour(Label::textWhenEditingColourId, object->findColour(PlugDataColour::canvasTextColourId)); - listLabel.setColour(Label::textColourId, object->findColour(PlugDataColour::canvasTextColourId)); - listLabel.setColour(TextEditor::textColourId, object->findColour(PlugDataColour::canvasTextColourId)); + listLabel.setColour(Label::textWhenEditingColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); + listLabel.setColour(Label::textColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); + listLabel.setColour(TextEditor::textColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); repaint(); } diff --git a/Source/Objects/LuaObject.h b/Source/Objects/LuaObject.h index fc49e669dc..2cfa4dd0d4 100644 --- a/Source/Objects/LuaObject.h +++ b/Source/Objects/LuaObject.h @@ -467,7 +467,7 @@ class LuaObject : public ObjectBase, public Timer, public NVGContextListener { } case hash("lua_fill_all"): { auto bounds = getLocalBounds().toFloat().reduced(0.5f); - auto outlineColour = object->findColour(isSelected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); + auto outlineColour = LookAndFeel::getDefaultLookAndFeel().findColour(isSelected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); nvgBeginPath(nvg); nvgRoundedRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), Corners::objectCornerRadius); diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index 771fc41426..0e671d6c3a 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -97,7 +97,7 @@ class MessageObject final : public ObjectBase objText = cnv->suggestor->getText(); } - auto colour = object->findColour(PlugDataColour::canvasTextColourId); + auto colour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId); int textWidth = getTextSize().getWidth() - 14; if(textRenderer.prepareLayout(objText, Fonts::getDefaultFont().withHeight(15), colour, textWidth, getValue(sizeProperty))) { @@ -138,10 +138,10 @@ class MessageObject final : public ObjectBase { auto b = getLocalBounds().toFloat().reduced(0.5f); - auto backgroundColour = convertColour(object->findColour(PlugDataColour::guiObjectBackgroundColourId)); - auto selectedOutlineColour = convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(object->findColour(PlugDataColour::objectOutlineColourId)); - auto flagColour = convertColour(object->findColour(PlugDataColour::guiObjectInternalOutlineColour)); + auto backgroundColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectBackgroundColourId)); + auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); + auto flagColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour)); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), backgroundColour, object->isSelected() ? selectedOutlineColour : outlineColour, Corners::objectCornerRadius); @@ -159,7 +159,7 @@ class MessageObject final : public ObjectBase nvgRect(nvg, b.getRight() - d, b.getY(), d, b.getHeight()); nvgRect(nvg, b.getX(), b.getBottom() - d, b.getWidth(), d); nvgRect(nvg, b.getX(), b.getY(), d, b.getHeight()); - nvgFillColor(nvg, convertColour(object->findColour(PlugDataColour::outlineColourId))); + nvgFillColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::outlineColourId))); nvgFill(nvg); } @@ -191,7 +191,7 @@ class MessageObject final : public ObjectBase } else { auto text = getText(); - textRenderer.renderText(nvg, text, Fonts::getDefaultFont().withHeight(15), object->findColour(PlugDataColour::canvasTextColourId), border.subtractedFrom(getLocalBounds()), getImageScale(), getValue(sizeProperty)); + textRenderer.renderText(nvg, text, Fonts::getDefaultFont().withHeight(15), LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId), border.subtractedFrom(getLocalBounds()), getImageScale(), getValue(sizeProperty)); } } diff --git a/Source/Objects/MessboxObject.h b/Source/Objects/MessboxObject.h index edc6ae8499..04de14a8ab 100644 --- a/Source/Objects/MessboxObject.h +++ b/Source/Objects/MessboxObject.h @@ -21,11 +21,11 @@ class MessboxObject final : public ObjectBase MessboxObject(pd::WeakReference obj, Object* parent) : ObjectBase(obj, parent) { - editor.setColour(TextEditor::textColourId, object->findColour(PlugDataColour::canvasTextColourId)); + editor.setColour(TextEditor::textColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); editor.setColour(TextEditor::backgroundColourId, Colours::transparentBlack); editor.setColour(TextEditor::focusedOutlineColourId, Colours::transparentBlack); editor.setColour(TextEditor::outlineColourId, Colours::transparentBlack); - editor.setColour(ScrollBar::thumbColourId, object->findColour(PlugDataColour::scrollbarThumbColourId)); + editor.setColour(ScrollBar::thumbColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::scrollbarThumbColourId)); editor.setAlwaysOnTop(true); editor.setMultiLine(true); @@ -125,7 +125,7 @@ class MessboxObject final : public ObjectBase void paintOverChildren(Graphics& g) override { bool selected = object->isSelected() && !cnv->isGraph; - auto outlineColour = object->findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::objectOutlineColourId); + auto outlineColour = LookAndFeel::getDefaultLookAndFeel().findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::objectOutlineColourId); g.setColour(outlineColour); g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(0.5f), Corners::objectCornerRadius, 1.0f); diff --git a/Source/Objects/MousePadObject.h b/Source/Objects/MousePadObject.h index f8ff3a1485..805553c88b 100644 --- a/Source/Objects/MousePadObject.h +++ b/Source/Objects/MousePadObject.h @@ -90,7 +90,7 @@ class MousePadObject final : public ObjectBase { auto b = getLocalBounds().toFloat().reduced(0.5f); auto* x = ptr.getRaw(); auto fillColour = Colour(x->x_color[0], x->x_color[1], x->x_color[2]); - auto outlineColour = object->findColour(object->isSelected() && !cnv->isGraph ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::outlineColourId); + auto outlineColour = LookAndFeel::getDefaultLookAndFeel().findColour(object->isSelected() && !cnv->isGraph ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::outlineColourId); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), convertColour(fillColour), convertColour(outlineColour), Corners::objectCornerRadius); } diff --git a/Source/Objects/NoteObject.h b/Source/Objects/NoteObject.h index 2fa1b07b28..cd188412ed 100644 --- a/Source/Objects/NoteObject.h +++ b/Source/Objects/NoteObject.h @@ -43,11 +43,11 @@ class NoteObject final : public ObjectBase { addAndMakeVisible(noteEditor); - noteEditor.setColour(TextEditor::textColourId, object->findColour(PlugDataColour::canvasTextColourId)); + noteEditor.setColour(TextEditor::textColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); noteEditor.setColour(TextEditor::backgroundColourId, Colours::transparentBlack); noteEditor.setColour(TextEditor::focusedOutlineColourId, Colours::transparentBlack); noteEditor.setColour(TextEditor::outlineColourId, Colours::transparentBlack); - noteEditor.setColour(ScrollBar::thumbColourId, object->findColour(PlugDataColour::scrollbarThumbColourId)); + noteEditor.setColour(ScrollBar::thumbColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::scrollbarThumbColourId)); noteEditor.setAlwaysOnTop(true); noteEditor.setMultiLine(true); @@ -144,8 +144,8 @@ class NoteObject final : public ObjectBase { repaint(); updateFont(); - getLookAndFeel().setColour(Label::textWhenEditingColourId, object->findColour(Label::textWhenEditingColourId)); - getLookAndFeel().setColour(Label::textColourId, object->findColour(Label::textColourId)); + getLookAndFeel().setColour(Label::textWhenEditingColourId, LookAndFeel::getDefaultLookAndFeel().findColour(Label::textWhenEditingColourId)); + getLookAndFeel().setColour(Label::textColourId, LookAndFeel::getDefaultLookAndFeel().findColour(Label::textColourId)); } void updateSizeProperty() override @@ -206,7 +206,7 @@ class NoteObject final : public ObjectBase { { if (getValue(outline)) { bool selected = object->isSelected() && !cnv->isGraph; - auto outlineColour = object->findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::objectOutlineColourId); + auto outlineColour = LookAndFeel::getDefaultLookAndFeel().findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::objectOutlineColourId); g.setColour(outlineColour); g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(0.5f), Corners::objectCornerRadius, 1.0f); diff --git a/Source/Objects/NumberObject.h b/Source/Objects/NumberObject.h index 44d5107448..e897c5524f 100644 --- a/Source/Objects/NumberObject.h +++ b/Source/Objects/NumberObject.h @@ -273,8 +273,8 @@ class NumberObject final : public ObjectBase { bool selected = object->isSelected() && !cnv->isGraph; auto backgroundColour = convertColour(iemHelper.getBackgroundColour()); - auto selectedOutlineColour = convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(object->findColour(PlugDataColour::objectOutlineColourId)); + auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), backgroundColour, selected ? selectedOutlineColour : outlineColour, Corners::objectCornerRadius); @@ -289,8 +289,8 @@ class NumberObject final : public ObjectBase { nvgLineTo(nvg, leftX, centreY - 5.0f); nvgClosePath(nvg); - auto normalColour = object->findColour(PlugDataColour::guiObjectInternalOutlineColour); - auto highlightColour = object->findColour(PlugDataColour::objectSelectedOutlineColourId); + auto normalColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour); + auto highlightColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId); bool highlighed = hasKeyboardFocus(true) && ::getValue(object->locked); nvgFillColor(nvg, convertColour(highlighed ? highlightColour : normalColour)); diff --git a/Source/Objects/NumboxTildeObject.h b/Source/Objects/NumboxTildeObject.h index a4950e9928..d5bbe030b4 100644 --- a/Source/Objects/NumboxTildeObject.h +++ b/Source/Objects/NumboxTildeObject.h @@ -249,7 +249,7 @@ class NumboxTildeObject final : public ObjectBase auto b = getLocalBounds().toFloat().reduced(0.5f); auto backgroundColour = Colour::fromString(secondaryColour.toString()); bool selected = object->isSelected() && !cnv->isGraph; - auto outlineColour = object->findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::objectOutlineColourId); + auto outlineColour = LookAndFeel::getDefaultLookAndFeel().findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::objectOutlineColourId); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), convertColour(backgroundColour), convertColour(outlineColour), Corners::objectCornerRadius); @@ -262,7 +262,7 @@ class NumboxTildeObject final : public ObjectBase auto iconBounds = Rectangle(7, 3, getHeight(), getHeight()); nvgFontFace(nvg, "Icon"); nvgFontSize(nvg, 12.0f); - nvgFillColor(nvg, convertColour(object->findColour(PlugDataColour::dataColourId))); + nvgFillColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::dataColourId))); nvgTextAlign(nvg, NVG_ALIGN_TOP | NVG_ALIGN_LEFT); nvgText(nvg, iconBounds.getX(), iconBounds.getY(), icon.toRawUTF8(), nullptr); } diff --git a/Source/Objects/ObjectBase.cpp b/Source/Objects/ObjectBase.cpp index 47ee8e5cd7..cb5b179d2f 100644 --- a/Source/Objects/ObjectBase.cpp +++ b/Source/Objects/ObjectBase.cpp @@ -421,11 +421,11 @@ void ObjectBase::render(NVGcontext* nvg) void ObjectBase::paint(Graphics& g) { - g.setColour(object->findColour(PlugDataColour::guiObjectBackgroundColourId)); + g.setColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectBackgroundColourId)); g.fillRoundedRectangle(getLocalBounds().toFloat().reduced(0.5f), Corners::objectCornerRadius); bool selected = object->isSelected() && !cnv->isGraph; - auto outlineColour = object->findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); + auto outlineColour = LookAndFeel::getDefaultLookAndFeel().findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); g.setColour(outlineColour); g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(0.5f), Corners::objectCornerRadius, 1.0f); diff --git a/Source/Objects/OpenFileObject.h b/Source/Objects/OpenFileObject.h index 54e3143921..7c68542933 100644 --- a/Source/Objects/OpenFileObject.h +++ b/Source/Objects/OpenFileObject.h @@ -98,12 +98,12 @@ class OpenFileObject final : public TextBase { int textWidth = getTextObjectWidth() - 14; // Reserve a bit of extra space for the text margin auto currentLayoutHash = hash(objText); - auto colour = object->findColour(PlugDataColour::canvasTextColourId); + auto colour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId); if(layoutTextHash != currentLayoutHash || colour.getARGB() != lastColourARGB || textWidth != lastTextWidth || mouseIsOver != mouseWasOver) { bool locked = getValue(object->locked) || getValue(object->commandLocked); - auto colour = object->findColour((locked && mouseIsOver) ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::canvasTextColourId); + auto colour = LookAndFeel::getDefaultLookAndFeel().findColour((locked && mouseIsOver) ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::canvasTextColourId); auto attributedText = AttributedString(objText); attributedText.setColour(colour); @@ -175,12 +175,12 @@ class OpenFileObject final : public TextBase { { updateTextLayout(); - auto backgroundColour = object->findColour(PlugDataColour::textObjectBackgroundColourId); + auto backgroundColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::textObjectBackgroundColourId); g.setColour(backgroundColour); g.fillRoundedRectangle(getLocalBounds().toFloat().reduced(0.5f), Corners::objectCornerRadius); - auto ioletAreaColour = object->findColour(PlugDataColour::ioletAreaColourId); + auto ioletAreaColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::ioletAreaColourId); if (ioletAreaColour != backgroundColour) { g.setColour(ioletAreaColour); diff --git a/Source/Objects/PictureObject.h b/Source/Objects/PictureObject.h index 42fb356a90..04abb671b8 100644 --- a/Source/Objects/PictureObject.h +++ b/Source/Objects/PictureObject.h @@ -136,11 +136,11 @@ class PictureObject final : public ObjectBase { if (imageFile.existsAsFile()) { g.drawImageAt(img, 0, 0); } else { - Fonts::drawText(g, "?", getLocalBounds(), object->findColour(PlugDataColour::canvasTextColourId), 30, Justification::centred); + Fonts::drawText(g, "?", getLocalBounds(), LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId), 30, Justification::centred); } bool selected = object->isSelected() && !cnv->isGraph; - auto outlineColour = object->findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); + auto outlineColour = LookAndFeel::getDefaultLookAndFeel().findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); if (getValue(outline)) { g.setColour(outlineColour); diff --git a/Source/Objects/RadioObject.h b/Source/Objects/RadioObject.h index 1132b25ebd..bb774dc0c1 100644 --- a/Source/Objects/RadioObject.h +++ b/Source/Objects/RadioObject.h @@ -176,14 +176,14 @@ class RadioObject final : public ObjectBase { { auto b = getLocalBounds().toFloat().reduced(0.5f); bool isSelected = object->isSelected() && !cnv->isGraph; - auto selectedOutlineColour = convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(object->findColour(PlugDataColour::objectOutlineColourId)); + auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), convertColour(iemHelper.getBackgroundColour()), isSelected ? selectedOutlineColour : outlineColour, Corners::objectCornerRadius); float size = (isVertical ? static_cast(getHeight()) / numItems : static_cast(getWidth()) / numItems); - nvgStrokeColor(nvg, convertColour(object->findColour(PlugDataColour::guiObjectInternalOutlineColour))); + nvgStrokeColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour))); nvgStrokeWidth(nvg, 1.0f); for (int i = 1; i < numItems; i++) { diff --git a/Source/Objects/ScopeObject.h b/Source/Objects/ScopeObject.h index 99215621df..cb45b530e1 100644 --- a/Source/Objects/ScopeObject.h +++ b/Source/Objects/ScopeObject.h @@ -117,7 +117,7 @@ class ScopeObject : public ObjectBase { auto b = getLocalBounds().toFloat().reduced(0.5f); - nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), convertColour(Colour::fromString(secondaryColour.toString())), convertColour(object->findColour(PlugDataColour::objectOutlineColourId)), Corners::objectCornerRadius); + nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), convertColour(Colour::fromString(secondaryColour.toString())), convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)), Corners::objectCornerRadius); auto dx = getWidth() * 0.125f; auto dy = getHeight() * 0.25f; diff --git a/Source/Objects/SliderObject.h b/Source/Objects/SliderObject.h index 3158fd1b8d..0d63fe9d2b 100644 --- a/Source/Objects/SliderObject.h +++ b/Source/Objects/SliderObject.h @@ -101,7 +101,7 @@ class ReversibleSlider : public Slider, public NVGComponent { auto sliderPos = jmap(valueToProportionOfLength(getValue()), 0.0f, 1.0f, b.getX(), b.getWidth() - thumbSize); auto bounds = Rectangle(sliderPos, b.getY(), thumbSize, b.getHeight()); - nvgFillColor(nvg, convertColour(findColour(Slider::trackColourId))); + nvgFillColor(nvg, convertColour(getLookAndFeel().findColour(Slider::trackColourId))); nvgBeginPath(nvg); nvgRoundedRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), cornerSize); nvgFill(nvg); @@ -110,7 +110,7 @@ class ReversibleSlider : public Slider, public NVGComponent { auto sliderPos = jmap(valueToProportionOfLength(getValue()), 1.0f, 0.0f, b.getY(), b.getHeight() - thumbSize); auto bounds = Rectangle(b.getWidth(), thumbSize).translated(b.getX(), sliderPos); - nvgFillColor(nvg, convertColour(findColour(Slider::trackColourId))); + nvgFillColor(nvg, convertColour(getLookAndFeel().findColour(Slider::trackColourId))); nvgBeginPath(nvg); nvgRoundedRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), cornerSize); nvgFill(nvg); @@ -322,7 +322,7 @@ class SliderObject : public ObjectBase { { auto b = getLocalBounds().toFloat().reduced(0.5f); bool selected = object->isSelected() && !cnv->isGraph; - auto outlineColour = object->findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); + auto outlineColour = LookAndFeel::getDefaultLookAndFeel().findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), convertColour(iemHelper.getBackgroundColour()), convertColour(outlineColour), Corners::objectCornerRadius); diff --git a/Source/Objects/SymbolAtomObject.h b/Source/Objects/SymbolAtomObject.h index ed08a54891..b6c82e6163 100644 --- a/Source/Objects/SymbolAtomObject.h +++ b/Source/Objects/SymbolAtomObject.h @@ -124,18 +124,18 @@ class SymbolAtomObject final : public ObjectBase void lookAndFeelChanged() override { - input.setColour(Label::textWhenEditingColourId, object->findColour(PlugDataColour::canvasTextColourId)); - input.setColour(Label::textColourId, object->findColour(PlugDataColour::canvasTextColourId)); - input.setColour(TextEditor::textColourId, object->findColour(PlugDataColour::canvasTextColourId)); + input.setColour(Label::textWhenEditingColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); + input.setColour(Label::textColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); + input.setColour(TextEditor::textColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); repaint(); } void render(NVGcontext* nvg) override { auto b = getLocalBounds().toFloat().reduced(0.5f); - auto backgroundColour = convertColour(object->findColour(PlugDataColour::guiObjectBackgroundColourId)); - auto selectedOutlineColour = convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(object->findColour(PlugDataColour::objectOutlineColourId)); + auto backgroundColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectBackgroundColourId)); + auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); bool highlighed = hasKeyboardFocus(true) && ::getValue(object->locked); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), backgroundColour, (object->isSelected() || highlighed) ? selectedOutlineColour : outlineColour, Corners::objectCornerRadius); @@ -144,7 +144,7 @@ class SymbolAtomObject final : public ObjectBase nvgIntersectRoundedScissor(nvg, b.getX() + 0.25f, b.getY() + 0.25f, b.getWidth() - 0.5f, b.getHeight() - 0.5f, Corners::objectCornerRadius); nvgBeginPath(nvg); - nvgFillColor(nvg, convertColour(object->findColour(PlugDataColour::guiObjectInternalOutlineColour))); + nvgFillColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour))); nvgMoveTo(nvg, b.getRight() - 8, b.getY()); nvgLineTo(nvg, b.getRight(), b.getY()); nvgLineTo(nvg, b.getRight(), b.getY() + 8); @@ -165,7 +165,7 @@ class SymbolAtomObject final : public ObjectBase } if (highlighed) { - nvgStrokeColor(nvg, convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId))); + nvgStrokeColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId))); nvgStrokeWidth(nvg, 2.0f); nvgBeginPath(nvg); nvgRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), Corners::objectCornerRadius); diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index db043485b3..7015fa2cee 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -144,7 +144,7 @@ struct TextObjectHelper { editor->applyFontToAllText(Font(fontHeight)); object->copyAllExplicitColoursTo(*editor); - editor->setColour(TextEditor::textColourId, object->findColour(PlugDataColour::canvasTextColourId)); + editor->setColour(TextEditor::textColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); editor->setColour(TextEditor::backgroundColourId, Colours::transparentBlack); editor->setColour(TextEditor::focusedOutlineColourId, Colours::transparentBlack); @@ -202,9 +202,9 @@ class TextBase : public ObjectBase { auto b = getLocalBounds(); - auto backgroundColour = convertColour(object->findColour(PlugDataColour::textObjectBackgroundColourId)); - auto selectedOutlineColour = convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(object->findColour(PlugDataColour::objectOutlineColourId)); + auto backgroundColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::textObjectBackgroundColourId)); + auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); if (!isValid) { outlineColour = convertColour(object->isSelected() ? Colours::red.brighter(1.5) : Colours::red); @@ -223,7 +223,7 @@ class TextBase : public ObjectBase // we could render at the actual scale, but that makes the transition to scolling/zooming pretty rough // Instead, rendering at 2x scale gives us pretty good sharpness overall - cachedTextRender.renderText(nvg, text, Fonts::getDefaultFont().withHeight(15), object->findColour(PlugDataColour::canvasTextColourId), textArea, getImageScale(), getValue(sizeProperty)); + cachedTextRender.renderText(nvg, text, Fonts::getDefaultFont().withHeight(15), LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId), textArea, getImageScale(), getValue(sizeProperty)); } } @@ -306,7 +306,7 @@ class TextBase : public ObjectBase objText = cnv->suggestor->getText(); } - auto colour = object->findColour(PlugDataColour::canvasTextColourId); + auto colour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId); int textWidth = getTextSize().getWidth() - 11; if(cachedTextRender.prepareLayout(objText, Fonts::getDefaultFont().withHeight(15), colour, textWidth, getValue(sizeProperty))) { diff --git a/Source/Objects/ToggleObject.h b/Source/Objects/ToggleObject.h index c7507b9283..40e694fec3 100644 --- a/Source/Objects/ToggleObject.h +++ b/Source/Objects/ToggleObject.h @@ -75,8 +75,8 @@ class ToggleObject final : public ObjectBase { auto backgroundColour = convertColour(iemHelper.getBackgroundColour()); auto toggledColour = convertColour(iemHelper.getForegroundColour()); // TODO: don't access audio thread variables in render loop auto untoggledColour = convertColour(iemHelper.getForegroundColour().interpolatedWith(iemHelper.getBackgroundColour(), 0.8f)); - auto selectedOutlineColour = convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(object->findColour(PlugDataColour::objectOutlineColourId)); + auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), backgroundColour, object->isSelected() ? selectedOutlineColour : outlineColour, Corners::objectCornerRadius); diff --git a/Source/Objects/VUMeterObject.h b/Source/Objects/VUMeterObject.h index c0611f13c5..a416b8caec 100644 --- a/Source/Objects/VUMeterObject.h +++ b/Source/Objects/VUMeterObject.h @@ -93,9 +93,9 @@ class VUMeterObject final : public ObjectBase { void render(NVGcontext* nvg) override { auto values = std::vector { ptr.getRaw()->x_fp, ptr.getRaw()->x_fr }; - auto backgroundColour = convertColour(object->findColour(PlugDataColour::guiObjectBackgroundColourId)); - auto selectedOutlineColour = convertColour(object->findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(object->findColour(PlugDataColour::objectOutlineColourId)); + auto backgroundColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectBackgroundColourId)); + auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); int height = getHeight(); int width = getWidth(); From a213bf7febd808f2217e521bcb58c97d0cb056e8 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 24 Apr 2024 14:19:17 +0200 Subject: [PATCH 0630/1030] Optimised connection area invalidation system --- Source/Connection.cpp | 9 ++++++--- Source/Objects/KeyboardObject.h | 4 ---- Source/Utility/RateReducer.h | 1 + 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/Connection.cpp b/Source/Connection.cpp index 2aec72cbe2..09abc1efb7 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -1004,11 +1004,11 @@ void Connection::updatePath() previousPStart = pstart; clipRegion = RectangleList(); - auto pathIter = PathFlatteningIterator(toDraw); + auto pathIter = PathFlatteningIterator(toDraw, AffineTransform(), 12.0f); while(pathIter.next()) // skip first item, since only the x2/y2 coords of that one are valid (and they will be the x1/y1 of the next item) { auto bounds = Rectangle(Point(pathIter.x1, pathIter.y1), Point(pathIter.x2, pathIter.y2)); - clipRegion.add(bounds.expanded(2)); + clipRegion.add(bounds.expanded(3)); } startReconnectHandle = Rectangle(5, 5).withCentre(toDraw.getPointAlongPath(8.5f)); @@ -1016,12 +1016,15 @@ void Connection::updatePath() clipRegion.add(startReconnectHandle.toNearestIntEdges().expanded(4)); clipRegion.add(endReconnectHandle.toNearestIntEdges().expanded(4)); - + cachedIsValid = false; } bool Connection::intersectsRectangle(Rectangle invalidatedArea) { + if(invalidatedArea.contains(getBounds())) + return true; + return clipRegion.intersectsRectangle(invalidatedArea); } diff --git a/Source/Objects/KeyboardObject.h b/Source/Objects/KeyboardObject.h index 205e8e0202..3ed0116db5 100644 --- a/Source/Objects/KeyboardObject.h +++ b/Source/Objects/KeyboardObject.h @@ -6,9 +6,6 @@ // Inherit to customise drawing class MIDIKeyboard : public MidiKeyboardState, public MidiKeyboardComponent { - - Object* object; - bool toggleMode = false; int lastKey = -1; @@ -20,7 +17,6 @@ class MIDIKeyboard : public MidiKeyboardState, public MidiKeyboardComponent { MIDIKeyboard(Object* parent) : MidiKeyboardComponent(*this, MidiKeyboardComponent::horizontalKeyboard) - , object(parent) { // Make sure nothing is drawn outside of our custom draw functions setColour(MidiKeyboardComponent::whiteNoteColourId, Colours::transparentBlack); diff --git a/Source/Utility/RateReducer.h b/Source/Utility/RateReducer.h index 66fc4008a8..f38225d8df 100644 --- a/Source/Utility/RateReducer.h +++ b/Source/Utility/RateReducer.h @@ -12,6 +12,7 @@ struct RateReducer : public Timer { explicit RateReducer(int rate) : timerHz(rate) { + ignoreUnused(timerHz); } bool tooFast() From 6413cc9bc9082279198ec812e30d7856f65a7557 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 24 Apr 2024 14:58:29 +0200 Subject: [PATCH 0631/1030] More small optimisations --- Libraries/nanovg | 2 +- Source/Canvas.cpp | 22 ++++---- Source/Object.cpp | 96 ++++++++++++++++------------------- Source/Object.h | 2 - Source/Objects/BangObject.h | 4 +- Source/Objects/RadioObject.h | 4 +- Source/Objects/SliderObject.h | 2 +- Source/Objects/ToggleObject.h | 6 +-- Source/Utility/Config.h | 8 ++- 9 files changed, 72 insertions(+), 74 deletions(-) diff --git a/Libraries/nanovg b/Libraries/nanovg index 2312f575b0..7a204ed79c 160000 --- a/Libraries/nanovg +++ b/Libraries/nanovg @@ -1 +1 @@ -Subproject commit 2312f575b0ee7765b898b46882e47dcb991a6f71 +Subproject commit 7a204ed79cab6f7f81c978721d57cf1286895305 diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 4b5b3c34cb..16d42a8ff0 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -284,16 +284,18 @@ bool Canvas::updateFramebuffers(NVGcontext* nvg, Rectangle invalidRegion, i bufferScale = zoom; // Then, check if object framebuffers need to be updated - if(viewport) invalidRegion = (invalidRegion + viewport->getViewPosition()) / zoom; - for(auto* obj : objects) - { - auto b = obj->getBounds(); - if(b.intersects(invalidRegion)) { - obj->updateFramebuffer(nvg); - - auto elapsed = Time::getMillisecondCounter() - start; - if(elapsed > maxUpdateTimeMs) { - return false; + if(isScrolling) { + if(viewport) invalidRegion = (invalidRegion + viewport->getViewPosition()) / zoom; + for(auto* obj : objects) + { + auto b = obj->getBounds(); + if(b.intersects(invalidRegion)) { + obj->updateFramebuffer(nvg); + + auto elapsed = Time::getMillisecondCounter() - start; + if(elapsed > maxUpdateTimeMs) { + return false; + } } } } diff --git a/Source/Object.cpp b/Source/Object.cpp index d7906b816b..bfd588412d 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -1161,55 +1161,47 @@ void Object::mouseDrag(MouseEvent const& e) void Object::updateFramebuffer(NVGcontext* nvg) { - if(shouldRenderToFramebuffer()) { - auto b = getLocalBounds(); - bool boundsChanged = b.getWidth() != fbWidth || b.getHeight() != fbHeight; - if(fbDirty || boundsChanged) + auto b = getLocalBounds(); + bool boundsChanged = b.getWidth() != fbWidth || b.getHeight() != fbHeight; + if(fbDirty || boundsChanged) + { + auto maxScale = 3.0f; + int scaledWidth = b.getWidth() * maxScale * cnv->getRenderScale(); + int scaledHeight = b.getHeight() * maxScale * cnv->getRenderScale(); + + if(!fb || boundsChanged) { - auto maxScale = 3.0f; - int scaledWidth = b.getWidth() * maxScale * cnv->getRenderScale(); - int scaledHeight = b.getHeight() * maxScale * cnv->getRenderScale(); + fbWidth = b.getWidth(); + fbHeight = b.getHeight(); - if(!fb || boundsChanged) - { - fbWidth = b.getWidth(); - fbHeight = b.getHeight(); - - if(fb) nvgDeleteFramebuffer(fb); - fb = nvgCreateFramebuffer(nvg, scaledWidth, scaledHeight, NVG_IMAGE_PREMULTIPLIED); - } - - nvgBindFramebuffer(fb); - nvgViewport(0, 0, scaledWidth, scaledHeight); - nvgClear(nvg); + if(fb) nvgDeleteFramebuffer(fb); + fb = nvgCreateFramebuffer(nvg, scaledWidth, scaledHeight, NVG_IMAGE_PREMULTIPLIED); + } + + nvgBindFramebuffer(fb); + nvgViewport(0, 0, scaledWidth, scaledHeight); + nvgClear(nvg); - nvgBeginFrame(nvg, b.getWidth() * maxScale, b.getHeight() * maxScale, cnv->getRenderScale()); - nvgScale(nvg, maxScale, maxScale); - nvgScissor (nvg, 0, 0, b.getWidth(), b.getHeight()); - - performRender(nvg); - + nvgBeginFrame(nvg, b.getWidth() * maxScale, b.getHeight() * maxScale, cnv->getRenderScale()); + nvgScale(nvg, maxScale, maxScale); + nvgScissor (nvg, 0, 0, b.getWidth(), b.getHeight()); + + performRender(nvg); + #if ENABLE_OBJECT_FB_DEBUGGING - static Random rng; - nvgBeginPath(nvg); - nvgFillColor(nvg, nvgRGBA(rng.nextInt(255), rng.nextInt(255), rng.nextInt(255), 0x50)); - nvgRect(nvg, 0, 0, b.getWidth(), b.getHeight()); - nvgFill(nvg); + static Random rng; + nvgBeginPath(nvg); + nvgFillColor(nvg, nvgRGBA(rng.nextInt(255), rng.nextInt(255), rng.nextInt(255), 0x50)); + nvgRect(nvg, 0, 0, b.getWidth(), b.getHeight()); + nvgFill(nvg); #endif - nvgEndFrame(nvg); - nvgBindFramebuffer(nullptr); - fbDirty = false; - - } + nvgEndFrame(nvg); + nvgBindFramebuffer(nullptr); + fbDirty = false; + } } -bool Object::shouldRenderToFramebuffer() -{ - // We render to framebuffer if we are scrolling/zooming - return cnv->isScrolling; -} - void Object::render(NVGcontext* nvg) { if(!activityOverlayImage || activityOverlayDirty) @@ -1217,11 +1209,11 @@ void Object::render(NVGcontext* nvg) if(activityOverlayImage) nvgDeleteImage(nvg, activityOverlayImage); Path objectShadow; objectShadow.addRoundedRectangle(getLocalBounds().reduced(Object::margin - 1), Corners::objectCornerRadius); - activityOverlayImage = StackShadow::createActivityDropShadowImage(nvg, getLocalBounds(), objectShadow, findColour(PlugDataColour::dataColourId), 5.5f, { 0, 0 }, 0, gui && gui->getCanvas()); + activityOverlayImage = StackShadow::createActivityDropShadowImage(nvg, getLocalBounds(), objectShadow, getLookAndFeel().findColour(PlugDataColour::dataColourId), 5.5f, { 0, 0 }, 0, gui && gui->getCanvas()); activityOverlayDirty = false; } - if(fb && shouldRenderToFramebuffer()) + if(fb && cnv->isScrolling) { if(fbDirty) { // If framebuffer is dirty, draw normally now and draw from buffer again on next render performRender(nvg); @@ -1243,7 +1235,7 @@ void Object::performRender(NVGcontext* nvg) { auto lb = getLocalBounds(); auto b = lb.reduced(margin); - auto selectedOutlineColour = convertColour(findColour(PlugDataColour::objectSelectedOutlineColourId)); + auto selectedOutlineColour = convertColour(getLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); if (selectedFlag) { auto& resizeHandleImage = cnv->resizeHandleImage; @@ -1275,7 +1267,7 @@ void Object::performRender(NVGcontext* nvg) if (gui && gui->isTransparent() && !getValue(locked) && !cnv->isGraph) { nvgBeginPath(nvg); - nvgFillColor(nvg, convertColour(findColour(PlugDataColour::canvasBackgroundColourId).contrasting(0.35f).withAlpha(0.1f))); + nvgFillColor(nvg, convertColour(getLookAndFeel().findColour(PlugDataColour::canvasBackgroundColourId).contrasting(0.35f).withAlpha(0.1f))); nvgRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), Corners::objectCornerRadius); nvgFill(nvg); } @@ -1289,8 +1281,8 @@ void Object::performRender(NVGcontext* nvg) if(newObjectEditor) { - auto backgroundColour = convertColour(findColour(PlugDataColour::textObjectBackgroundColourId)); - auto outlineColour = convertColour(findColour(PlugDataColour::objectOutlineColourId)); + auto backgroundColour = convertColour(getLookAndFeel().findColour(PlugDataColour::textObjectBackgroundColourId)); + auto outlineColour = convertColour(getLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); nvgBeginPath(nvg); nvgRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), Corners::objectCornerRadius); @@ -1309,11 +1301,11 @@ void Object::performRender(NVGcontext* nvg) auto outlet = cnv->lastSelectedObject->iolets[cnv->lastSelectedObject->numInputs]; float fakeInletBounds[4] = {16.0f, 4.0f, 8.0f, 8.0f}; nvgBeginPath(nvg); - nvgFillColor(nvg, convertColour(findColour(outlet->isSignal ? PlugDataColour::signalColourId : PlugDataColour::dataColourId).brighter())); + nvgFillColor(nvg, convertColour(getLookAndFeel().findColour(outlet->isSignal ? PlugDataColour::signalColourId : PlugDataColour::dataColourId).brighter())); nvgEllipse(nvg, fakeInletBounds[0] + fakeInletBounds[2] * 0.5f, fakeInletBounds[1] + fakeInletBounds[3] * 0.5f, fakeInletBounds[2] * 0.5f, fakeInletBounds[3] * 0.5f); nvgFill(nvg); - nvgStrokeColor(nvg, convertColour(findColour(PlugDataColour::objectOutlineColourId))); + nvgStrokeColor(nvg, convertColour(getLookAndFeel().findColour(PlugDataColour::objectOutlineColourId))); nvgStrokeWidth(nvg, 1.0f); nvgStroke(nvg); } @@ -1338,14 +1330,14 @@ void Object::performRender(NVGcontext* nvg) auto indexBounds = Rectangle(left, (b.getHeight() / 2) - halfHeight, b.getWidth() - left, halfHeight * 2); nvgBeginPath(nvg); - nvgFillColor(nvg, convertColour(findColour(PlugDataColour::objectSelectedOutlineColourId))); // Adjust fill color as needed + nvgFillColor(nvg, convertColour(getLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId))); // Adjust fill color as needed nvgRoundedRect(nvg, indexBounds.getX(), indexBounds.getY(), indexBounds.getWidth(), indexBounds.getHeight(), 2.0f); nvgFill(nvg); nvgFontSize(nvg, 8.0f); nvgFontFace(nvg, "Inter"); nvgTextAlign(nvg, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER); - nvgFillColor(nvg, convertColour(findColour(PlugDataColour::objectSelectedOutlineColourId).contrasting())); + nvgFillColor(nvg, convertColour(getLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId).contrasting())); nvgText(nvg, indexBounds.getCentreX(), indexBounds.getCentreY(), text.c_str(), nullptr); } @@ -1442,7 +1434,7 @@ void Object::openNewObjectEditor() editor->applyFontToAllText(Font(15)); copyAllExplicitColoursTo(*editor); - editor->setColour(TextEditor::textColourId, findColour(PlugDataColour::canvasTextColourId)); + editor->setColour(TextEditor::textColourId, getLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); editor->setColour(TextEditor::backgroundColourId, Colours::transparentBlack); editor->setColour(TextEditor::outlineColourId, Colours::transparentBlack); editor->setColour(TextEditor::focusedOutlineColourId, Colours::transparentBlack); diff --git a/Source/Object.h b/Source/Object.h index 755ee34300..ca4a54cd5c 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -146,8 +146,6 @@ class Object : public Component bool checkIfHvccCompatible() const; - bool shouldRenderToFramebuffer(); - void setSelected(bool shouldBeSelected); bool selectedFlag = false; bool selectionStateChanged = false; diff --git a/Source/Objects/BangObject.h b/Source/Objects/BangObject.h index dae7a4e2e1..9963eac9e9 100644 --- a/Source/Objects/BangObject.h +++ b/Source/Objects/BangObject.h @@ -107,8 +107,8 @@ class BangObject final : public ObjectBase { { auto b = getLocalBounds().toFloat().reduced(0.5f); - auto foregroundColour = convertColour(iemHelper.getForegroundColour()); // TODO: this is some bad threading practice! - auto backgroundColour = convertColour(iemHelper.getBackgroundColour()); + auto foregroundColour = convertColour(getValue(iemHelper.primaryColour)); // TODO: this is some bad threading practice! + auto backgroundColour = convertColour(getValue(iemHelper.secondaryColour)); auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); auto internalLineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour)); diff --git a/Source/Objects/RadioObject.h b/Source/Objects/RadioObject.h index bb774dc0c1..24acf80cb4 100644 --- a/Source/Objects/RadioObject.h +++ b/Source/Objects/RadioObject.h @@ -199,8 +199,8 @@ class RadioObject final : public ObjectBase { nvgStroke(nvg); } } - - nvgFillColor(nvg, convertColour(iemHelper.getForegroundColour())); + + nvgFillColor(nvg, convertColour(::getValue(iemHelper.primaryColour))); float selectionX = isVertical ? 0 : selected * size; float selectionY = isVertical ? selected * size : 0; diff --git a/Source/Objects/SliderObject.h b/Source/Objects/SliderObject.h index 0d63fe9d2b..58932cc507 100644 --- a/Source/Objects/SliderObject.h +++ b/Source/Objects/SliderObject.h @@ -324,7 +324,7 @@ class SliderObject : public ObjectBase { bool selected = object->isSelected() && !cnv->isGraph; auto outlineColour = LookAndFeel::getDefaultLookAndFeel().findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); - nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), convertColour(iemHelper.getBackgroundColour()), convertColour(outlineColour), Corners::objectCornerRadius); + nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), convertColour(getLookAndFeel().findColour(Slider::backgroundColourId)), convertColour(outlineColour), Corners::objectCornerRadius); slider.render(nvg); } diff --git a/Source/Objects/ToggleObject.h b/Source/Objects/ToggleObject.h index 40e694fec3..06dbaff378 100644 --- a/Source/Objects/ToggleObject.h +++ b/Source/Objects/ToggleObject.h @@ -72,9 +72,9 @@ class ToggleObject final : public ObjectBase { { auto b = getLocalBounds().toFloat().reduced(0.5f); - auto backgroundColour = convertColour(iemHelper.getBackgroundColour()); - auto toggledColour = convertColour(iemHelper.getForegroundColour()); // TODO: don't access audio thread variables in render loop - auto untoggledColour = convertColour(iemHelper.getForegroundColour().interpolatedWith(iemHelper.getBackgroundColour(), 0.8f)); + auto backgroundColour = convertColour(::getValue(iemHelper.secondaryColour)); + auto toggledColour = convertColour(::getValue(iemHelper.primaryColour)); // TODO: don't access audio thread variables in render loop + auto untoggledColour = convertColour(::getValue(iemHelper.primaryColour).interpolatedWith(::getValue(iemHelper.secondaryColour), 0.8f)); auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index 308de6aadd..c7b019509e 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -1,6 +1,7 @@ #pragma once #include +#include using namespace juce; @@ -60,7 +61,12 @@ inline T getValue(Value const& v) { if constexpr (std::is_same_v) { return v.toString(); - } else { + } + else if constexpr (std::is_same_v) + { + return Colour::fromString(v.toString()); + } + else { return static_cast(v.getValue()); } } From 92f444b22409257b11bd851f24eceb3e83578edb Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 24 Apr 2024 17:10:42 +0200 Subject: [PATCH 0632/1030] Fixed broken MIDI output in plugin --- Source/Pd/Instance.cpp | 9 ++++----- Source/Pd/Setup.cpp | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 78902c0637..7ceeab9c1d 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -274,12 +274,12 @@ void Instance::initialisePd(String& pdlua_version) register_gui_triggers(static_cast(instance), this, gui_trigger, message_trigger); - // Make sure we set the maininstance when initialising objects - // Whenever a new instance is created, the functions will be copied from this one - libpd_set_instance(libpd_main_instance()); - static bool initialised = false; if (!initialised) { + // Make sure we set the maininstance when initialising objects + // Whenever a new instance is created, the functions will be copied from this one + libpd_set_instance(libpd_main_instance()); + set_class_prefix(gensym("else")); class_set_extern_dir(gensym("9.else")); pd::Setup::initialiseELSE(); @@ -310,7 +310,6 @@ void Instance::initialisePd(String& pdlua_version) setThis(); pd::Setup::initialisePdLuaInstance(); - // ag: need to do this here to suppress noise from chatty externals printReceiver = pd::Setup::createPrintHook(this, reinterpret_cast(internal::instance_multi_print)); libpd_set_verbose(0); diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index 66c3a41743..98f6a24ab9 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -1270,15 +1270,8 @@ int Setup::initialisePd() { static int initialized = 0; if (!initialized) { - libpd_set_noteonhook(plugdata_noteon); - libpd_set_controlchangehook(plugdata_controlchange); - libpd_set_programchangehook(plugdata_programchange); - libpd_set_pitchbendhook(plugdata_pitchbend); - libpd_set_aftertouchhook(plugdata_aftertouch); - libpd_set_polyaftertouchhook(plugdata_polyaftertouch); - libpd_set_midibytehook(plugdata_midibyte); libpd_set_printhook(plugdata_print); - + // Initialise pd libpd_init(); @@ -1390,6 +1383,15 @@ void* Setup::createMIDIHook(void* ptr, x->x_hook_polyaftertouch = hook_polyaftertouch; x->x_hook_midibyte = hook_midibyte; } + + libpd_set_noteonhook(plugdata_noteon); + libpd_set_controlchangehook(plugdata_controlchange); + libpd_set_programchangehook(plugdata_programchange); + libpd_set_pitchbendhook(plugdata_pitchbend); + libpd_set_aftertouchhook(plugdata_aftertouch); + libpd_set_polyaftertouchhook(plugdata_polyaftertouch); + libpd_set_midibytehook(plugdata_midibyte); + return x; } From b95c14558e7b113420bb29ecd82232fedc60a5c7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Apr 2024 15:03:33 +0200 Subject: [PATCH 0633/1030] Compilation fix --- Libraries/JUCE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index 336a8e8397..d18a3121e0 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit 336a8e83973eb72b091c71ff6f0eb87a843500ad +Subproject commit d18a3121e0f9a24a93fc61895de42bb99ce9d5b9 From 50a9a4d44c8f8c625e893285b199e9f2e3058646 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Apr 2024 15:09:01 +0200 Subject: [PATCH 0634/1030] Compilation fix --- Libraries/JUCE | 2 +- Libraries/pd-lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index d18a3121e0..74fb0c93a1 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit d18a3121e0f9a24a93fc61895de42bb99ce9d5b9 +Subproject commit 74fb0c93a1470faa0dccab9ab182192235c40a1a diff --git a/Libraries/pd-lua b/Libraries/pd-lua index 34cd81550e..2962ea5c92 160000 --- a/Libraries/pd-lua +++ b/Libraries/pd-lua @@ -1 +1 @@ -Subproject commit 34cd81550e796ee58a25dd82c11ea8841938352a +Subproject commit 2962ea5c92f9e61dbd5da6247d27609b9f2251ca From 1c6b9ec755072c629e1acbae30363e32085a5a61 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Apr 2024 15:10:56 +0200 Subject: [PATCH 0635/1030] Another compilation fix --- Libraries/JUCE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index 74fb0c93a1..45574aa4b8 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit 74fb0c93a1470faa0dccab9ab182192235c40a1a +Subproject commit 45574aa4b8eeb9098c213170f85eb3bf6227a0ee From 8c997a7c9bf91835e00000050202dceaa1baaf7a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Apr 2024 15:14:04 +0200 Subject: [PATCH 0636/1030] Another compilation fix --- Libraries/JUCE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index 45574aa4b8..a08a665568 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit 45574aa4b8eeb9098c213170f85eb3bf6227a0ee +Subproject commit a08a665568a340941d03ab9f9ea8fe1a34165339 From fc003c0137301a2e9bd40f2aab5d6ed2da2e67ad Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Apr 2024 15:42:58 +0200 Subject: [PATCH 0637/1030] Fixed nanovg text rendering on Windows --- Source/Utility/NanoVGGraphicsContext.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Source/Utility/NanoVGGraphicsContext.cpp b/Source/Utility/NanoVGGraphicsContext.cpp index 65f89f3e9b..fa2433c4c1 100644 --- a/Source/Utility/NanoVGGraphicsContext.cpp +++ b/Source/Utility/NanoVGGraphicsContext.cpp @@ -399,16 +399,20 @@ void NanoVGGraphicsContext::drawLine (const juce::Line& line) void NanoVGGraphicsContext::setFont (const juce::Font& f) { font = f; - auto name = font.getTypefacePtr()->getName() + "-" + font.getTypefaceStyle(); + auto typefaceName = font.getTypefacePtr()->getName(); + if (typefaceName.contains(" ")) + typefaceName = typefaceName.replace(" ", "-"); + else + typefaceName = +"-" + font.getTypefaceStyle(); - if(!loadedFonts.count(name)) + if (!loadedFonts.count(typefaceName)) { - loadedFonts[name] = getGlyphToCharMapForFont(f); + loadedFonts[typefaceName] = getGlyphToCharMapForFont(f); } - currentGlyphToCharMap = &loadedFonts[name]; + currentGlyphToCharMap = &loadedFonts[typefaceName]; - nvgFontFace (nvg, name.toUTF8()); + nvgFontFace(nvg, typefaceName.toUTF8()); nvgFontSize (nvg, font.getHeight() * 0.86f); nvgTextLetterSpacing(nvg, -0.275f); } From f7ccba41a2bfe3d8fe8cf224229a3236144007ad Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Apr 2024 15:45:51 +0200 Subject: [PATCH 0638/1030] Small fix --- Source/Utility/NanoVGGraphicsContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Utility/NanoVGGraphicsContext.cpp b/Source/Utility/NanoVGGraphicsContext.cpp index fa2433c4c1..506c3885ef 100644 --- a/Source/Utility/NanoVGGraphicsContext.cpp +++ b/Source/Utility/NanoVGGraphicsContext.cpp @@ -403,7 +403,7 @@ void NanoVGGraphicsContext::setFont (const juce::Font& f) if (typefaceName.contains(" ")) typefaceName = typefaceName.replace(" ", "-"); else - typefaceName = +"-" + font.getTypefaceStyle(); + typefaceName += "-" + font.getTypefaceStyle(); if (!loadedFonts.count(typefaceName)) { From 643a627229b32bb9e34f030d262d1713ca0b8f52 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Apr 2024 16:57:09 +0200 Subject: [PATCH 0639/1030] Fixed keyboard bugs --- Source/Objects/KeyboardObject.h | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/Source/Objects/KeyboardObject.h b/Source/Objects/KeyboardObject.h index 3ed0116db5..fdee171979 100644 --- a/Source/Objects/KeyboardObject.h +++ b/Source/Objects/KeyboardObject.h @@ -8,8 +8,11 @@ class MIDIKeyboard : public MidiKeyboardState, public MidiKeyboardComponent { bool toggleMode = false; int lastKey = -1; - + public: + + int clickedKey = -1; + std::set heldKeys; std::set toggledKeys; std::function noteOn; @@ -51,6 +54,8 @@ class MIDIKeyboard : public MidiKeyboardState, public MidiKeyboardComponent { bool mouseDownOnKey(int midiNoteNumber, MouseEvent const& e) override { + clickedKey = midiNoteNumber; + if (e.mods.isShiftDown()) { if (toggledKeys.count(midiNoteNumber)) { toggledKeys.erase(midiNoteNumber); @@ -80,6 +85,8 @@ class MIDIKeyboard : public MidiKeyboardState, public MidiKeyboardComponent { bool mouseDraggedToKey(int midiNoteNumber, MouseEvent const& e) override { + clickedKey = midiNoteNumber; + if (!toggleMode && !e.mods.isShiftDown() && !heldKeys.count(midiNoteNumber)) { for (auto& note : heldKeys) { noteOff(note); @@ -106,6 +113,8 @@ class MIDIKeyboard : public MidiKeyboardState, public MidiKeyboardComponent { // So we completely replace mouseUpOnKey functionality here, mouseUp() will stop mouseUpOnKey() being called. void mouseUp(MouseEvent const& e) override { + clickedKey = -1; + if (!toggleMode && !e.mods.isShiftDown()) { heldKeys.erase(lastKey); noteOff(lastKey); @@ -257,7 +266,7 @@ class KeyboardObject final : public ObjectBase objectParameters.addParamReceiveSymbol(&receiveSymbol); objectParameters.addParamSendSymbol(&sendSymbol); - startTimer(150); + startTimer(50); } void update() override @@ -398,17 +407,19 @@ class KeyboardObject final : public ObjectBase void updateValue() { + int notes[256]; if (auto obj = ptr.get()) { + memcpy(notes, obj->x_tgl_notes, 256 * sizeof(int)); + } - for (int i = keyboard.getRangeStart(); i < keyboard.getRangeEnd(); i++) { - if (obj->x_tgl_notes[i] && !keyboard.heldKeys.contains(i)) { - keyboard.heldKeys.insert(i); - repaint(); - } - if (!obj->x_tgl_notes[i] && keyboard.heldKeys.contains(i)) { - keyboard.heldKeys.erase(i); - repaint(); - } + for (int i = keyboard.getRangeStart(); i <= keyboard.getRangeEnd(); i++) { + if (notes[i] && !keyboard.heldKeys.contains(i)) { + keyboard.heldKeys.insert(i); + repaint(); + } + if (!notes[i] && keyboard.heldKeys.contains(i) && keyboard.clickedKey != i) { + keyboard.heldKeys.erase(i); + repaint(); } } } From 31880d8e00b88210b3081550e990a1f5ebbea2f8 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Apr 2024 17:43:14 +0200 Subject: [PATCH 0640/1030] Update noteseq module --- Libraries/pd-lua | 2 +- Resources/Patches/Palettes/noteseq.m~.pd | 40 ++++++++++++------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Libraries/pd-lua b/Libraries/pd-lua index 2962ea5c92..de06a8d589 160000 --- a/Libraries/pd-lua +++ b/Libraries/pd-lua @@ -1 +1 @@ -Subproject commit 2962ea5c92f9e61dbd5da6247d27609b9f2251ca +Subproject commit de06a8d5892aab60336cd71eb47e724e6244e342 diff --git a/Resources/Patches/Palettes/noteseq.m~.pd b/Resources/Patches/Palettes/noteseq.m~.pd index 8b7c7ef2d8..da5e21b175 100644 --- a/Resources/Patches/Palettes/noteseq.m~.pd +++ b/Resources/Patches/Palettes/noteseq.m~.pd @@ -48,7 +48,6 @@ #X obj -96 316 pak s s s s s s s s; #X obj 154 569 zl lookup; #X obj -413 -79 loadmess S C4 D4 E4 F4 G4 A4 B4; -#X obj 154 601 note2pitch; #X obj 237 727 outlet; #X obj -410 -46 unpack s s s s s s s s; #X obj 161 249 nbx 4 18 -1e+37 1e+37 0 0 empty empty empty 0 -8 0 10 #e4e4e4 #373737 #373737 0 256; @@ -60,6 +59,7 @@ #X text 50 248 note duration (ms); #X obj 238 688 pack f f; #X obj 125 5 note 161 18 Inter empty 1 54 54 54 0 187 187 187 1 1 NOTE SEQUENCER; +#X obj 154 601 note2midi; #X connect 1 0 35 0; #X connect 2 0 37 0; #X connect 3 0 19 1; @@ -121,23 +121,23 @@ #X connect 44 0 46 7; #X connect 45 0 46 0; #X connect 46 0 47 1; -#X connect 47 0 49 0; -#X connect 48 0 51 0; -#X connect 49 0 57 0; -#X connect 51 0 45 0; -#X connect 51 1 38 0; -#X connect 51 2 39 0; -#X connect 51 3 40 0; -#X connect 51 4 41 0; -#X connect 51 5 42 0; -#X connect 51 6 43 0; -#X connect 51 7 44 0; -#X connect 52 0 57 2; -#X connect 53 0 57 1; -#X connect 55 0 56 0; -#X connect 56 0 52 0; -#X connect 56 1 53 0; -#X connect 57 0 59 0; -#X connect 57 1 59 1; -#X connect 59 0 50 0; +#X connect 47 0 60 0; +#X connect 48 0 50 0; +#X connect 50 0 45 0; +#X connect 50 1 38 0; +#X connect 50 2 39 0; +#X connect 50 3 40 0; +#X connect 50 4 41 0; +#X connect 50 5 42 0; +#X connect 50 6 43 0; +#X connect 50 7 44 0; +#X connect 51 0 56 2; +#X connect 52 0 56 1; +#X connect 54 0 55 0; +#X connect 55 0 51 0; +#X connect 55 1 52 0; +#X connect 56 0 58 0; +#X connect 56 1 58 1; +#X connect 58 0 49 0; +#X connect 60 0 56 0; #X coords 0 1 100 -1 406 276 2 3 0; From 9847547d4754596ed27f7c0f2057f1749cbbc7dd Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 25 Apr 2024 21:19:57 +0200 Subject: [PATCH 0641/1030] Update submodules --- Libraries/pd-else | 2 +- Libraries/pd-lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/pd-else b/Libraries/pd-else index 1d253e9879..5dc274e6d7 160000 --- a/Libraries/pd-else +++ b/Libraries/pd-else @@ -1 +1 @@ -Subproject commit 1d253e98794faa690a43c5dfcff3e55f699ede0b +Subproject commit 5dc274e6d7bae2f81795abf31c2569f30590e324 diff --git a/Libraries/pd-lua b/Libraries/pd-lua index de06a8d589..59ec850530 160000 --- a/Libraries/pd-lua +++ b/Libraries/pd-lua @@ -1 +1 @@ -Subproject commit de06a8d5892aab60336cd71eb47e724e6244e342 +Subproject commit 59ec850530b0fab06e846163af8950ba8fc89a36 From a25554168bf22108f2c63fa47092f537a8eab1cb Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Apr 2024 01:08:07 +0200 Subject: [PATCH 0642/1030] Fixed potential crash in DAW --- Source/Utility/StringUtils.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Utility/StringUtils.h b/Source/Utility/StringUtils.h index c6a7d69deb..a95505f700 100644 --- a/Source/Utility/StringUtils.h +++ b/Source/Utility/StringUtils.h @@ -88,6 +88,11 @@ struct CachedStringWidth { struct CachedFontStringWidth : public DeletedAtShutdown { + ~CachedFontStringWidth() + { + instance = nullptr; + } + float calculateSingleLineWidth(Font const& font, const String& singleLine) { auto stringHash = hash(singleLine); From 1f78fb90355979e385b748b635671243efdcc7f4 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 26 Apr 2024 15:02:47 +0930 Subject: [PATCH 0643/1030] change latency display button when hovered over to "Reset" --- Source/Statusbar.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 3ddc6fd5b1..4e2abb8e80 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -28,6 +28,7 @@ class LatencyDisplayButton : public Component, public MultiTimer, public Settabl Label icon; bool isHover = false; Colour bgColour; + int currentLatencyValue = 0; enum TimerRoutine { Timeout, Animate }; float alpha = 1.0f; @@ -89,7 +90,8 @@ class LatencyDisplayButton : public Component, public MultiTimer, public Settabl void setLatencyValue(const int value) { - latencyValue.setText(String(value) + " smpl", dontSendNotification); + currentLatencyValue = value; + updateValue(); if (value == 64) { startTimer(Timeout, 1000 / 3.0f); } else { @@ -102,6 +104,17 @@ class LatencyDisplayButton : public Component, public MultiTimer, public Settabl } } + void updateValue() + { + if (isHover) { + latencyValue.setJustificationType(Justification::centredLeft); + latencyValue.setText("Reset", dontSendNotification); + } else { + latencyValue.setJustificationType(Justification::centredRight); + latencyValue.setText(String(currentLatencyValue) + " smpl", dontSendNotification); + } + } + void mouseDown(const MouseEvent& e) override { onClick(); @@ -114,6 +127,8 @@ class LatencyDisplayButton : public Component, public MultiTimer, public Settabl icon.setColour(Label::textColourId, textColour); latencyValue.setColour(Label::textColourId, textColour); + updateValue(); + repaint(); } From be5e4baef4294a039231de8b483dc303eea3eeee Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 26 Apr 2024 15:08:36 +0930 Subject: [PATCH 0644/1030] use bitshift to represent bitwise overlay enums --- Source/Constants.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Source/Constants.h b/Source/Constants.h index 8fcb1db7fd..7e418cd652 100644 --- a/Source/Constants.h +++ b/Source/Constants.h @@ -400,15 +400,15 @@ struct Corners { enum Overlay { None = 0, - Origin = 1, - Border = 2, - Index = 4, - Coordinate = 8, - ActivationState = 16, - ConnectionActivity = 32, - Order = 64, - Direction = 128, - Behind = 256 + Origin = 1 << 0, + Border = 1 << 1, + Index = 1 << 2, + Coordinate = 1 << 3, + ActivationState = 1 << 4, + ConnectionActivity = 1 << 5, + Order = 1 << 6, + Direction = 1 << 7, + Behind = 1 << 8 }; enum Align { From 1cf9fa32f815af073c7b2da44e60ef41a9af16ae Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 26 Apr 2024 15:38:14 +0930 Subject: [PATCH 0645/1030] fix tooltip for latency display button --- Source/Statusbar.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 4e2abb8e80..7b87c59fc9 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -46,6 +46,11 @@ class LatencyDisplayButton : public Component, public MultiTimer, public Settabl setInterceptsMouseClicks(true, false); addMouseListener(this, true); + // we need to specifically turn off mouse intercept for child components for tooltip of parent to work + // setting child components intercept to false in parent is not enough + latencyValue.setInterceptsMouseClicks(false, false); + icon.setInterceptsMouseClicks(false, false); + setTooltip("Plugin latency, click to reset"); addAndMakeVisible(latencyValue); @@ -155,6 +160,8 @@ class LatencyDisplayButton : public Component, public MultiTimer, public Settabl icon.setBounds(0, 0, getHeight(), getHeight()); latencyValue.setBounds(getHeight(), 0, getWidth() - getHeight(), getHeight()); } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LatencyDisplayButton); }; class OversampleSelector : public TextButton { From 17316a89f298e3f5c774bf17ec58bf3bd2c8f8c4 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 26 Apr 2024 15:44:18 +0930 Subject: [PATCH 0646/1030] make latency button tooltip clearer --- Source/Statusbar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 7b87c59fc9..0ae9f2fa7d 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -51,7 +51,7 @@ class LatencyDisplayButton : public Component, public MultiTimer, public Settabl latencyValue.setInterceptsMouseClicks(false, false); icon.setInterceptsMouseClicks(false, false); - setTooltip("Plugin latency, click to reset"); + setTooltip("Plugin latency, click to reset to 64 samples"); addAndMakeVisible(latencyValue); addAndMakeVisible(icon); From 9267c6843a20d5f84b1e6933600765cfe71eed11 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Apr 2024 15:39:56 +0200 Subject: [PATCH 0647/1030] Small optimisation --- Libraries/pure-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/pure-data b/Libraries/pure-data index a5aa03b922..58d7212014 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit a5aa03b9229352c2156c07dac6ad92394e646a26 +Subproject commit 58d721201454a65f9bb4f2412dfcdac6ff864e86 From a75db28082e72c70c4b87e9a13ed93d3b5bc47f0 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 26 Apr 2024 17:18:08 +0200 Subject: [PATCH 0648/1030] Small optimisations --- Source/Object.cpp | 2 +- Source/Pd/MessageListener.h | 2 +- Source/Utility/AudioSampleRingBuffer.h | 21 ++++++++++++++++++--- Source/Utility/NanoVGGraphicsContext.h | 6 +++--- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index bfd588412d..58c8c3ac4d 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -1204,7 +1204,7 @@ void Object::updateFramebuffer(NVGcontext* nvg) void Object::render(NVGcontext* nvg) { - if(!activityOverlayImage || activityOverlayDirty) + if(showActiveState && (!activityOverlayImage || activityOverlayDirty)) { if(activityOverlayImage) nvgDeleteImage(nvg, activityOverlayImage); Path objectShadow; diff --git a/Source/Pd/MessageListener.h b/Source/Pd/MessageListener.h index db2f3527c7..1132984aac 100644 --- a/Source/Pd/MessageListener.h +++ b/Source/Pd/MessageListener.h @@ -156,7 +156,7 @@ class MessageDispatcher { // Queue to use in case our fast stack queue is full moodycamel::ConcurrentQueue backupQueue; - std::map>> messageListeners; + std::unordered_map>> messageListeners; CriticalSection messageListenerLock; }; diff --git a/Source/Utility/AudioSampleRingBuffer.h b/Source/Utility/AudioSampleRingBuffer.h index 64507480ee..0c30dc12d0 100644 --- a/Source/Utility/AudioSampleRingBuffer.h +++ b/Source/Utility/AudioSampleRingBuffer.h @@ -72,11 +72,26 @@ class AudioSampleRingBuffer { while (readPos >= buffer.getNumSamples()) readPos -= bufferSize; - + audioBufferMutex.lock(); for (int ch = 0; ch < std::min(2, peakBuffer.getNumChannels()); ch++) { - for (int i = 0; i < peakWindowSize; i++) { - peakBuffer.setSample(ch, i, buffer.getSample(ch, (readPos + i) % bufferSize)); + // Get pointers to the source and destination data + const auto* srcData = buffer.getReadPointer(ch); + auto* destData = peakBuffer.getWritePointer(ch); + + int readPosWrapped = (readPos + peakWindowSize) % bufferSize; + if (readPosWrapped < peakWindowSize) { + // Calculate the length of the first copy operation + int firstCopyLength = bufferSize - readPos; + + // Perform the first copy operation + juce::FloatVectorOperations::copy(destData, srcData + readPos, firstCopyLength); + + // Perform the second copy operation + juce::FloatVectorOperations::copy(destData + firstCopyLength, srcData, peakWindowSize - firstCopyLength); + } else { + // If no wrap-around is needed, perform a single SIMD copy operation + juce::FloatVectorOperations::copy(destData, srcData + readPos, peakWindowSize); } } audioBufferMutex.unlock(); diff --git a/Source/Utility/NanoVGGraphicsContext.h b/Source/Utility/NanoVGGraphicsContext.h index 8b3f6d2d46..af737779ef 100644 --- a/Source/Utility/NanoVGGraphicsContext.h +++ b/Source/Utility/NanoVGGraphicsContext.h @@ -83,12 +83,12 @@ class NanoVGGraphicsContext : public juce::LowLevelGraphicsContext juce::Font font; // Mapping glyph number to a character - using GlyphToCharMap = std::map; + using GlyphToCharMap = std::unordered_map; GlyphToCharMap getGlyphToCharMapForFont (const juce::Font& f); // Mapping font names to glyph-to-character tables - std::map loadedFonts; + std::unordered_map loadedFonts; const GlyphToCharMap* currentGlyphToCharMap; // Tracking images mapped tomtextures. @@ -98,5 +98,5 @@ class NanoVGGraphicsContext : public juce::LowLevelGraphicsContext int accessCounter {0}; ///< Usage counter. }; - std::map images; + std::unordered_map images; }; From 679ef33eff4efb3e7b3a17fe2b4e19cde43818c7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 28 Apr 2024 13:14:04 +0200 Subject: [PATCH 0649/1030] Fixed knob bug --- Source/Objects/AllGuis.h | 105 ++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 52 deletions(-) diff --git a/Source/Objects/AllGuis.h b/Source/Objects/AllGuis.h index 00eaaa8c9f..fdad877b97 100644 --- a/Source/Objects/AllGuis.h +++ b/Source/Objects/AllGuis.h @@ -230,58 +230,59 @@ struct t_fake_keyboard { // [else/knob] struct t_fake_knob { - t_object x_obj; - void* x_proxy; - t_glist* x_glist; - int x_size; - double x_pos; // 0-1 normalized position - t_float x_exp; - int x_expmode; - int x_log; - t_float x_load; // value when loading patch - t_float x_start; // arc start value - int x_start_angle; - int x_end_angle; - int x_range; - int x_offset; - int x_ticks; - int x_outline; - double x_min; - double x_max; - int x_clicked; - int x_sel; - int x_shift; - int x_edit; - int x_jump; - double x_fval; - t_symbol* x_fg; - t_symbol* x_mg; - t_symbol* x_bg; - t_symbol* x_snd; - t_symbol* x_snd_raw; - int x_flag; - int x_r_flag; - int x_s_flag; - int x_rcv_set; - int x_snd_set; - t_symbol* x_rcv; - t_symbol* x_rcv_raw; - int x_circular; - int x_arc; - int x_zoom; - int x_discrete; - char x_tag_obj[128]; - char x_tag_circle[128]; - char x_tag_bg_arc[128]; - char x_tag_arc[128]; - char x_tag_center[128]; - char x_tag_wiper[128]; - char x_tag_wpr_c[128]; - char x_tag_ticks[128]; - char x_tag_outline[128]; - char x_tag_in[128]; - char x_tag_out[128]; - char x_tag_sel[128]; + t_object x_obj; + void *x_proxy; + t_glist *x_glist; + int x_ctrl; + int x_size; + double x_pos; // 0-1 normalized position + t_float x_exp; + int x_expmode; + int x_log; + t_float x_load; // value when loading patch + t_float x_start; // arc start value + int x_start_angle; + int x_end_angle; + int x_range; + int x_offset; + int x_ticks; + int x_outline; + double x_min; + double x_max; + int x_clicked; + int x_sel; + int x_shift; + int x_edit; + int x_jump; + double x_fval; + t_symbol *x_fg; + t_symbol *x_mg; + t_symbol *x_bg; + t_symbol *x_snd; + t_symbol *x_snd_raw; + int x_flag; + int x_r_flag; + int x_s_flag; + int x_rcv_set; + int x_snd_set; + t_symbol *x_rcv; + t_symbol *x_rcv_raw; + int x_circular; + int x_arc; + int x_zoom; + int x_discrete; + char x_tag_obj[128]; + char x_tag_circle[128]; + char x_tag_bg_arc[128]; + char x_tag_arc[128]; + char x_tag_center[128]; + char x_tag_wiper[128]; + char x_tag_wpr_c[128]; + char x_tag_ticks[128]; + char x_tag_outline[128]; + char x_tag_in[128]; + char x_tag_out[128]; + char x_tag_sel[128]; }; // [else/messbox] From d6b86efdb59dfe09e8d32636c17ddb0397423366 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 28 Apr 2024 13:15:45 +0200 Subject: [PATCH 0650/1030] Optimisations for loading graphs, fixed memory leaks --- Libraries/pure-data | 2 +- Source/Canvas.cpp | 1 + Source/Connection.cpp | 6 ++++++ Source/LookAndFeel.cpp | 1 - Source/Object.cpp | 4 +++- Source/Objects/OpenFileObject.h | 4 +--- Source/Objects/TextObject.h | 2 ++ 7 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Libraries/pure-data b/Libraries/pure-data index 58d7212014..e11181b2a7 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit 58d721201454a65f9bb4f2412dfcdac6ff864e86 +Subproject commit e11181b2a7c4b5339591fe089de0deac54c3b965 diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 16d42a8ff0..27d2715449 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -175,6 +175,7 @@ Canvas::~Canvas() editor->removeModifierKeyListener(this); pd->unregisterMessageListener(patch.getPointer().get(), this); editor->nvgSurface.removeNVGContextListener(this); + if(ioletBuffer) nvgDeleteFramebuffer(ioletBuffer); } void Canvas::lookAndFeelChanged() diff --git a/Source/Connection.cpp b/Source/Connection.cpp index 09abc1efb7..0c6b77ac76 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -119,6 +119,12 @@ Connection::~Connection() if (inobj) { inobj->removeComponentListener(this); } + + auto* nvg = cnv->editor->nvgSurface.getRawContext(); + if(cacheId >= 0) nvgDeletePath(nvg, cacheId); + if (cacheId >= 0 && cableType == SignalCable) { + nvgDeletePath(nvg, std::numeric_limits::max() - cacheId); + } } void Connection::changeListenerCallback(ChangeBroadcaster* source) diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index 7d73e03aa4..60be50a9c7 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -261,7 +261,6 @@ class PlugData_DocumentWindowButton : public Button { PlugDataLook::PlugDataLook() { - setDefaultSansSerifTypeface(Fonts::getCurrentFont().getTypefacePtr()); } void PlugDataLook::fillResizableWindowBackground(Graphics& g, int w, int h, BorderSize const& border, ResizableWindow& window) diff --git a/Source/Object.cpp b/Source/Object.cpp index 58c8c3ac4d..76307a4b5d 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -345,6 +345,8 @@ void Object::applyBounds() } void Object::updateBounds() { + if(cnv->isGraph && gui && gui->hideInGraph()) return; + // only update if we have a gui and the object isn't been moved by the user // otherwise PD hasn't been informed of the new position 'while' we are dragging // so we don't need to update the bounds when an object is being interacted with @@ -567,7 +569,7 @@ void Object::resized() void Object::updateTooltips() { - if (!gui) + if (!gui || cnv->isGraph) return; auto objectInfo = cnv->pd->objectLibrary->getObjectInfo(gui->getType()); diff --git a/Source/Objects/OpenFileObject.h b/Source/Objects/OpenFileObject.h index 7c68542933..9ea68ae5b2 100644 --- a/Source/Objects/OpenFileObject.h +++ b/Source/Objects/OpenFileObject.h @@ -86,9 +86,7 @@ class OpenFileObject final : public TextBase { } void updateTextLayout() override - { - if(cnv->isGraph) return; // Text layouting is expensive, so skip if it's not necessary - + { auto objText = getLinkText(); if (editor && cnv->suggestor && cnv->suggestor->getText().isNotEmpty()) { objText = cnv->suggestor->getText(); diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index 7015fa2cee..096b37cf87 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -301,6 +301,8 @@ class TextBase : public ObjectBase virtual void updateTextLayout() { + if(cnv->isGraph) return; // Text layouting is expensive, so skip if it's not necessary + auto objText = editor ? editor->getText() : objectText; if (editor && cnv->suggestor && cnv->suggestor->getText().isNotEmpty()) { objText = cnv->suggestor->getText(); From 906f5de8abe39d9f58cb870138eac25a54c71923 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sun, 28 Apr 2024 21:04:17 +0930 Subject: [PATCH 0651/1030] fix latency display for multi-editor, reset latency safely --- Source/Statusbar.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 0ae9f2fa7d..6a83fd9609 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -111,7 +111,7 @@ class LatencyDisplayButton : public Component, public MultiTimer, public Settabl void updateValue() { - if (isHover) { + if (isHover && !fading) { latencyValue.setJustificationType(Justification::centredLeft); latencyValue.setText("Reset", dontSendNotification); } else { @@ -139,18 +139,12 @@ class LatencyDisplayButton : public Component, public MultiTimer, public Settabl void mouseEnter(const MouseEvent& e) override { - if (fading) - return; - isHover = true; buttonStateChanged(); } void mouseExit(const MouseEvent& e) override { - if (fading) - return; - isHover = false; buttonStateChanged(); } @@ -781,9 +775,7 @@ Statusbar::Statusbar(PluginProcessor* processor) latencyDisplayButton = std::make_unique(); addChildComponent(latencyDisplayButton.get()); latencyDisplayButton->onClick = [this](){ - currentLatency = 64; - pd->setLatencySamples(64); - latencyDisplayButton->setVisible(false); + pd->performLatencyCompensationChange(64); }; powerButton.setButtonText(Icons::Power); @@ -915,6 +907,8 @@ Statusbar::Statusbar(PluginProcessor* processor) alignmentButton.setTooltip(String("Alignment tools")); + setLatencyDisplay(pd->getLatencySamples()); + setSize(getWidth(), statusbarHeight); } From eb2a1cd10939de1f7be21372b2fc202b704d1b7d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 28 Apr 2024 15:56:45 +0200 Subject: [PATCH 0652/1030] Updated ELSE --- Libraries/pd-else | 2 +- Resources/Scripts/package_resources.py | 1 + Source/Utility/Config.h | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Libraries/pd-else b/Libraries/pd-else index 5dc274e6d7..d7ee562606 160000 --- a/Libraries/pd-else +++ b/Libraries/pd-else @@ -1 +1 @@ -Subproject commit 5dc274e6d7bae2f81795abf31c2569f30590e324 +Subproject commit d7ee562606adfb4c9b56955614fcd472d4bebe18 diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index f684a2cc90..3d6a7680aa 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -80,6 +80,7 @@ def splitFile(file, num_files): globCopy("../../Libraries/pd-else/Code_source/Abstractions/control/*.pd", "./Abstractions/else") globCopy("../../Libraries/pd-else/Code_source/Abstractions/audio/*.pd", "./Abstractions/else") globCopy("../../Libraries/pd-else/Code_source/Abstractions/extra_abs/*.pd", "./Abstractions/else") +globCopy("../../Libraries/pd-else/Code_source/Abstractions/extra_abs/*.pd_lua", "./Abstractions/else") copyFile("../Patches/playhead.pd", "./Abstractions") copyFile("../Patches/param.pd", "./Abstractions") copyFile("../Patches/daw_storage.pd", "./Abstractions") diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index c7b019509e..df8ebeef37 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -52,7 +52,7 @@ struct ProjectInfo { #else static inline File const appDataDir = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory).getChildFile("plugdata"); #endif - static inline String const versionSuffix = "-test3"; + static inline String const versionSuffix = "-test4"; static inline File const versionDataDir = appDataDir.getChildFile("Versions").getChildFile(ProjectInfo::versionString + versionSuffix); }; From 95452af18e6c658b8cf5df14ace7c79b766294d4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 28 Apr 2024 15:57:39 +0200 Subject: [PATCH 0653/1030] Update Pd --- Libraries/pure-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/pure-data b/Libraries/pure-data index e11181b2a7..515af37c31 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit e11181b2a7c4b5339591fe089de0deac54c3b965 +Subproject commit 515af37c31a036f817094b5995bac4a0a650ff9b From 60be870e5d8eb8db02850442e7ea65389616c8cd Mon Sep 17 00:00:00 2001 From: alcomposer Date: Mon, 29 Apr 2024 14:47:28 +0930 Subject: [PATCH 0654/1030] add copyright info for object theme manager --- Source/Utility/ObjectThemeManager.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Utility/ObjectThemeManager.h b/Source/Utility/ObjectThemeManager.h index 817c0fe858..e32b4ebf4e 100644 --- a/Source/Utility/ObjectThemeManager.h +++ b/Source/Utility/ObjectThemeManager.h @@ -1,3 +1,9 @@ +/* + // Copyright (c) 2023 Timothy Schoen and Alex Mitchell + // For information on usage and redistribution, and for a DISCLAIMER OF ALL + // WARRANTIES, see the file, "LICENSE.txt," in this distribution. +*/ + #pragma once #include From c4649b83c9f7c76cd3b95898cc8848524e458b91 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Mon, 29 Apr 2024 16:27:34 +0930 Subject: [PATCH 0655/1030] fix double opening of lua text editor when editing abstracton / subpatch luaobject --- Source/Objects/LuaObject.h | 19 ++++++++++++++++++- Source/PluginEditor.h | 2 ++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Source/Objects/LuaObject.h b/Source/Objects/LuaObject.h index 2cfa4dd0d4..5110767bab 100644 --- a/Source/Objects/LuaObject.h +++ b/Source/Objects/LuaObject.h @@ -577,9 +577,14 @@ class LuaObject : public ObjectBase, public Timer, public NVGContextListener { textEditor->toFront(true); return; } + + if (cnv->editor->openTextEditors.contains(ptr)) + return; + textEditor.reset( Dialogs::showTextEditorDialog(fileToOpen.loadFileAsString(), "lua: " + getText(), [this, fileToOpen](String const& newText, bool hasChanged) { if (!hasChanged) { + cnv->editor->openTextEditors.removeAllInstancesOf(ptr); textEditor.reset(nullptr); return; } @@ -597,17 +602,20 @@ class LuaObject : public ObjectBase, public Timer, public NVGContextListener { auto* patch = cnv->patch.getPointer().get(); pd::Interface::recreateTextObject(patch, pdlua.cast()); } + cnv->editor->openTextEditors.removeAllInstancesOf(ptr); textEditor.reset(nullptr); cnv->performSynchronise(); } if (result == 1) { + cnv->editor->openTextEditors.removeAllInstancesOf(ptr); textEditor.reset(nullptr); } }, 15, false); })); + if (textEditor) + cnv->editor->openTextEditors.addIfNotAlreadyThere(ptr); } - }; // A non-GUI Lua object, that we would still like to have clickable for opening the editor @@ -645,9 +653,14 @@ class LuaTextObject final : public TextBase { textEditor->toFront(true); return; } + + if (cnv->editor->openTextEditors.contains(ptr)) + return; + textEditor.reset( Dialogs::showTextEditorDialog(fileToOpen.loadFileAsString(), "lua: " + getText(), [this, fileToOpen](String const& newText, bool hasChanged) { if (!hasChanged) { + cnv->editor->openTextEditors.removeAllInstancesOf(ptr); textEditor.reset(nullptr); return; } @@ -665,14 +678,18 @@ class LuaTextObject final : public TextBase { auto* patch = cnv->patch.getPointer().get(); pd::Interface::recreateTextObject(patch, pdlua.cast()); } + cnv->editor->openTextEditors.removeAllInstancesOf(ptr); textEditor.reset(nullptr); cnv->performSynchronise(); } if (result == 1) { + cnv->editor->openTextEditors.removeAllInstancesOf(ptr); textEditor.reset(nullptr); } }, 15, false); })); + if (textEditor) + cnv->editor->openTextEditors.addIfNotAlreadyThere(ptr); } }; diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index e4ef2d9bf1..b8eb785d81 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -148,6 +148,8 @@ class PluginEditor : public AudioProcessorEditor bool highlightSearchTarget(void* target, bool openNewTabIfNeeded); + Array openTextEditors; + TabComponent* getActiveTabbar(); PluginProcessor* pd; From 3c283e145d86dc70e85da61447f375606046316d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 29 Apr 2024 13:33:08 +0200 Subject: [PATCH 0656/1030] Fixed object index display --- Source/Object.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index 76307a4b5d..cc90a77e14 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -1326,16 +1326,13 @@ void Object::performRender(NVGcontext* nvg) int halfHeight = 5; auto text = std::to_string(cnv->objects.indexOf(this)); - int textWidth = static_cast(nvgTextBounds(nvg, 0, 0, text.c_str(), nullptr, nullptr)) + 5; - int left = std::min(b.getWidth() - (1.5 * margin), b.getWidth() - textWidth); + int textWidth = 6 + text.length() * 4; + auto indexBounds = b.withSizeKeepingCentre(b.getWidth() + doubleMargin, halfHeight * 2).removeFromRight(textWidth); + + auto fillColour = convertColour(getLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + nvgDrawRoundedRect(nvg, indexBounds.getX(), indexBounds.getY(), indexBounds.getWidth(), indexBounds.getHeight(), fillColour, fillColour, 2.0f); - auto indexBounds = Rectangle(left, (b.getHeight() / 2) - halfHeight, b.getWidth() - left, halfHeight * 2); - nvgBeginPath(nvg); - nvgFillColor(nvg, convertColour(getLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId))); // Adjust fill color as needed - nvgRoundedRect(nvg, indexBounds.getX(), indexBounds.getY(), indexBounds.getWidth(), indexBounds.getHeight(), 2.0f); - nvgFill(nvg); - nvgFontSize(nvg, 8.0f); nvgFontFace(nvg, "Inter"); nvgTextAlign(nvg, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER); From 94b802b76f3dc9d2975814efd72e72b9ea225315 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 29 Apr 2024 13:41:35 +0200 Subject: [PATCH 0657/1030] Fixed theme updating for text objects --- Source/Objects/CommentObject.h | 5 +++++ Source/Objects/MessageObject.h | 5 +++++ Source/Objects/TextObject.h | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/Source/Objects/CommentObject.h b/Source/Objects/CommentObject.h index 48b417c8e0..75f4840f1d 100644 --- a/Source/Objects/CommentObject.h +++ b/Source/Objects/CommentObject.h @@ -183,6 +183,11 @@ class CommentObject final : public ObjectBase return {textWidth, textSize.getHeight()}; } + void lookAndFeelChanged() override + { + updateTextLayout(); + } + void updateTextLayout() { auto objText = editor ? editor->getText() : objectText; diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index 0e671d6c3a..e10f4a814d 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -217,6 +217,11 @@ class MessageObject final : public ObjectBase } } + void lookAndFeelChanged() override + { + updateTextLayout(); + } + bool isEditorShown() override { return editor != nullptr; diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index 096b37cf87..038461995f 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -479,6 +479,11 @@ class TextBase : public ObjectBase return false; } + void lookAndFeelChanged() override + { + updateTextLayout(); + } + void resized() override { updateTextLayout(); From 1e818ad7f5dec447ec956b53d0cc9a10c9f4294a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 30 Apr 2024 14:08:50 +0200 Subject: [PATCH 0658/1030] Improvements for [pic]: Fixed bug, improved performance, fixed rendering large images with Metal, fixed broken outline property --- Source/Object.cpp | 20 ++++-- Source/Objects/PictureObject.h | 114 ++++++++++++++++++++++++++++----- 2 files changed, 110 insertions(+), 24 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index cc90a77e14..5e306b973a 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -573,12 +573,15 @@ void Object::updateTooltips() return; auto objectInfo = cnv->pd->objectLibrary->getObjectInfo(gui->getType()); - - // Set object tooltip - gui->setTooltip(objectInfo.getProperty("description").toString()); - - // Check pd library for pddp tooltips, those have priority - auto ioletTooltips = cnv->pd->objectLibrary->parseIoletTooltips(objectInfo.getChildWithName("iolets"), gui->getText(), numInputs, numOutputs); + + std::array ioletTooltips; + + if(objectInfo.isValid()) { + // Set object tooltip + gui->setTooltip(objectInfo.getProperty("description").toString()); + // Check pd library for pddp tooltips, those have priority + ioletTooltips = cnv->pd->objectLibrary->parseIoletTooltips(objectInfo.getChildWithName("iolets"), gui->getText(), numInputs, numOutputs); + } // First clear all tooltips, so we can see later if it has already been set or not for (auto iolet : iolets) { @@ -1160,9 +1163,12 @@ void Object::mouseDrag(MouseEvent const& e) } } - void Object::updateFramebuffer(NVGcontext* nvg) { + // For very large objects, buffering is just gonna take up GPU memory, with minimal performance benefits + // Also, Metal has a limitation on image size, so this will also prevent crashing + if(getWidth() * 3 * cnv->getRenderScale() > 8192 || getHeight() * 3 * cnv->getRenderScale() > 8192) return; + auto b = getLocalBounds(); bool boundsChanged = b.getWidth() != fbWidth || b.getHeight() != fbHeight; if(fbDirty || boundsChanged) diff --git a/Source/Objects/PictureObject.h b/Source/Objects/PictureObject.h index 04abb671b8..c43aa13829 100644 --- a/Source/Objects/PictureObject.h +++ b/Source/Objects/PictureObject.h @@ -5,7 +5,7 @@ */ // ELSE pic -class PictureObject final : public ObjectBase { +class PictureObject final : public ObjectBase, public NVGContextListener { Value path = SynchronousValue(); Value latch = SynchronousValue(); @@ -17,9 +17,10 @@ class PictureObject final : public ObjectBase { File imageFile; Image img; + std::vector>> imageBuffers; + bool imageNeedsReload = false; + uint8 pixelDataBuffer[8192 * 8192 * 4]; - std::unique_ptr nvgCtx = nullptr; - public: PictureObject(pd::WeakReference ptr, Object* object) : ObjectBase(ptr, object) @@ -44,6 +45,15 @@ class PictureObject final : public ObjectBase { objectParameters.addParamBool("Report Size", cAppearance, &reportSize, { "No", "Yes" }, 0); objectParameters.addParamReceiveSymbol(&receiveSymbol); objectParameters.addParamSendSymbol(&sendSymbol); + + cnv->editor->nvgSurface.addNVGContextListener(this); + } + + ~PictureObject() + { + // TODO: delete image buffers! + + cnv->editor->nvgSurface.addNVGContextListener(this); } bool isTransparent() override @@ -66,6 +76,10 @@ class PictureObject final : public ObjectBase { } } } + + void nvgContextDeleted(NVGcontext* nvg) override { + imageNeedsReload = true; + }; void mouseUp(MouseEvent const& e) override { @@ -121,31 +135,96 @@ class PictureObject final : public ObjectBase { } } } + + int convertImage(NVGcontext* nvg, Image& image, Rectangle bounds) + { + Image::BitmapData imageData(image, Image::BitmapData::readOnly); + for (int y = 0; y < bounds.getHeight(); y++) + { + auto* scanLine = (uint32*) imageData.getLinePointer(y + bounds.getY()); + + for (int x = 0; x < bounds.getWidth(); x++) + { + uint32 argb = scanLine[x + bounds.getX()]; + int bufferPos = (y * bounds.getWidth() + x) * 4; + + pixelDataBuffer[bufferPos + 0] = (argb >> 16) & 0xFF; // Red + pixelDataBuffer[bufferPos + 1] = (argb >> 8) & 0xFF; // Green + pixelDataBuffer[bufferPos + 2] = argb & 0xFF; // Blue + pixelDataBuffer[bufferPos + 3] = (argb >> 24) & 0xFF; // Alpha + } + } + + return nvgCreateImageRGBA(nvg, bounds.getWidth(), bounds.getHeight(), NVG_IMAGE_PREMULTIPLIED, pixelDataBuffer); + } - void render(NVGcontext* nvg) override + void updateImage(NVGcontext* nvg) { - if(!nvgCtx || nvgCtx->getContext() != nvg) nvgCtx = std::make_unique(nvg); - Graphics g(*nvgCtx); + for(auto& [image, bounds] : imageBuffers) { - paintEntireComponent(g, true); + nvgDeleteImage(nvg, image); } + imageBuffers.clear(); + + int imageWidth = img.getWidth(); + int imageHeight = img.getHeight(); + int x = 0; + while(x < imageWidth) + { + int y = 0; + int width = std::min(8192, imageWidth - x); + while(y < imageHeight) + { + int height = std::min(8192, imageHeight - y); + auto bounds = Rectangle(x, y, width, height); + imageBuffers.emplace_back(convertImage(nvg, img, bounds), bounds); + y += 8192; + + } + x += 8192; + } + + imageNeedsReload = false; } - - void paint(Graphics& g) override + + void render(NVGcontext* nvg) override { - if (imageFile.existsAsFile()) { - g.drawImageAt(img, 0, 0); - } else { - Fonts::drawText(g, "?", getLocalBounds(), LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId), 30, Justification::centred); + if(imageNeedsReload) updateImage(nvg); + + auto b = getLocalBounds().toFloat(); + + nvgSave(nvg); + nvgIntersectScissor(nvg, 0, 0, getWidth(), getHeight()); + if(imageBuffers.empty()) + { + nvgFontSize(nvg, 20); + nvgFontFace(nvg, "Inter-Regular"); + nvgTextAlign(nvg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); + nvgFillColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId))); + nvgText(nvg, b.getCentreX(), b.getCentreY(), "?", 0); } - + else { + for(auto& [image, bounds] : imageBuffers) + { + nvgBeginPath(nvg); + nvgRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); + nvgFillPaint(nvg, nvgImagePattern(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), 0, image, 1.0f)); + nvgFill(nvg); + } + } + bool selected = object->isSelected() && !cnv->isGraph; auto outlineColour = LookAndFeel::getDefaultLookAndFeel().findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); if (getValue(outline)) { - g.setColour(outlineColour); - g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(0.5f), Corners::objectCornerRadius, 1.0f); + nvgBeginPath(nvg); + nvgRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), Corners::objectCornerRadius); + nvgStrokeWidth(nvg, 1.0f); + nvgStrokeColor(nvg, convertColour(outlineColour)); + nvgStroke(nvg); } + + nvgRestore(nvg); } void valueChanged(Value& value) override @@ -171,7 +250,7 @@ class PictureObject final : public ObjectBase { pic->x_latch = getValue(latch); } else if (value.refersToSameSourceAs(outline)) { if (auto pic = ptr.get()) - pic->x_outline = getValue(latch); + pic->x_outline = getValue(outline); } else if (value.refersToSameSourceAs(reportSize)) { if (auto pic = ptr.get()) pic->x_size = getValue(reportSize); @@ -267,6 +346,7 @@ class PictureObject final : public ObjectBase { auto* rawPath = pathString.toRawUTF8(); img = ImageFileFormat::loadFrom(imageFile); + imageNeedsReload = true; if (auto pic = ptr.get()) { pic->x_filename = pd->generateSymbol(rawFileName); From 9cd3c3e6df1ecda5ea82499eb4e43cd879cd4b0f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 30 Apr 2024 14:09:04 +0200 Subject: [PATCH 0659/1030] Improve tooltip lookup performance --- Source/Components/SuggestionComponent.h | 93 +++++++++++++------------ Source/Dialogs/ObjectBrowserDialog.h | 14 ++-- Source/Dialogs/ObjectReferenceDialog.h | 2 + Source/Pd/Library.cpp | 25 ++++++- Source/Pd/Library.h | 1 + Source/PluginEditor.cpp | 1 - 6 files changed, 85 insertions(+), 51 deletions(-) diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index f8ad985a62..3181316942 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -644,47 +644,49 @@ class SuggestionComponent : public Component state = ShowingArguments; auto name = currentText.upToFirstOccurrenceOf(" ", false, false); auto objectInfo = library->getObjectInfo(name); - auto found = objectInfo.getChildWithName("arguments").createCopy(); - for (auto flag : objectInfo.getChildWithName("flags")) { - auto flagCopy = flag.createCopy(); - auto name = flagCopy.getProperty("name").toString().trim(); - - if (!name.startsWith("-")) - name = "-" + name; - - flagCopy.setProperty("type", name, nullptr); - found.appendChild(flagCopy, nullptr); - } - - numOptions = std::min(buttons.size(), found.getNumChildren()); - for (int i = 0; i < numOptions; i++) { - auto type = found.getChild(i).getProperty("type").toString(); - auto description = found.getChild(i).getProperty("description").toString(); - auto def = found.getChild(i).getProperty("default").toString(); - - if (def.isNotEmpty()) - description += " (default: " + def + ")"; - - buttons[i]->setText(type, description, false); - buttons[i]->setInterceptsMouseClicks(false, false); - buttons[i]->setToggleState(false, dontSendNotification); - } - - for (int i = numOptions; i < buttons.size(); i++) { - buttons[i]->setText("", "", false); - buttons[i]->setToggleState(false, dontSendNotification); - } - - setVisible(numOptions); - - if (autoCompleteComponent) { - autoCompleteComponent->enableAutocomplete(false); - currentObject->updateBounds(); + if(objectInfo.isValid()) { + auto found = objectInfo.getChildWithName("arguments").createCopy(); + for (auto flag : objectInfo.getChildWithName("flags")) { + auto flagCopy = flag.createCopy(); + auto name = flagCopy.getProperty("name").toString().trim(); + + if (!name.startsWith("-")) + name = "-" + name; + + flagCopy.setProperty("type", name, nullptr); + found.appendChild(flagCopy, nullptr); + } + + numOptions = std::min(buttons.size(), found.getNumChildren()); + for (int i = 0; i < numOptions; i++) { + auto type = found.getChild(i).getProperty("type").toString(); + auto description = found.getChild(i).getProperty("description").toString(); + auto def = found.getChild(i).getProperty("default").toString(); + + if (def.isNotEmpty()) + description += " (default: " + def + ")"; + + buttons[i]->setText(type, description, false); + buttons[i]->setInterceptsMouseClicks(false, false); + buttons[i]->setToggleState(false, dontSendNotification); + } + + for (int i = numOptions; i < buttons.size(); i++) { + buttons[i]->setText("", "", false); + buttons[i]->setToggleState(false, dontSendNotification); + } + + setVisible(numOptions); + + if (autoCompleteComponent) { + autoCompleteComponent->enableAutocomplete(false); + currentObject->updateBounds(); + } + + resized(); + + return; } - - resized(); - - return; } if (isPositiveAndBelow(currentidx, buttons.size())) { @@ -742,7 +744,10 @@ class SuggestionComponent : public Component for (int i = 0; i < std::min(buttons.size(), numOptions); i++) { auto& name = suggestions[i]; - auto description = library->getObjectInfo(name).getProperty("description").toString(); + auto info = library->getObjectInfo(name); + if(!info.isValid()) continue; + + auto description = info.getProperty("description").toString(); buttons[i]->setText(name, description, true); buttons[i]->setInterceptsMouseClicks(true, false); @@ -828,8 +833,10 @@ class SuggestionComponent : public Component continue; auto info = cnv->pd->objectLibrary->getObjectInfo(objectName); - auto methods = info.getChildWithName("methods"); - objects.add({ objectName, methods, distance }); + if(info.isValid()) { + auto methods = info.getChildWithName("methods"); + objects.add({ objectName, methods, distance }); + } } // Sort by distance diff --git a/Source/Dialogs/ObjectBrowserDialog.h b/Source/Dialogs/ObjectBrowserDialog.h index fdba045c49..6a41893933 100644 --- a/Source/Dialogs/ObjectBrowserDialog.h +++ b/Source/Dialogs/ObjectBrowserDialog.h @@ -198,7 +198,7 @@ class ObjectsListBox : public ListBox for (auto const& object : library.getAllObjects()) { auto info = library.getObjectInfo(object); - if (info.hasProperty("name") && info.hasProperty("description")) { + if (info.isValid() && info.hasProperty("name") && info.hasProperty("description")) { descriptions[info.getProperty("name").toString()] = info.getProperty("description").toString(); } } @@ -495,7 +495,9 @@ class ObjectViewer : public Component { void showObject(String const& name) { - bool valid = name.isNotEmpty(); + auto objectInfo = library.getObjectInfo(name); + bool valid = name.isNotEmpty() && objectInfo.isValid(); + // openHelp.setVisible(valid); openReference.setVisible(valid); objectDragArea.setVisible(valid); @@ -513,8 +515,7 @@ class ObjectViewer : public Component { bool hasUnknownInletLayout = false; bool hasUnknownOutletLayout = false; - - auto objectInfo = library.getObjectInfo(name); + auto ioletDescriptions = objectInfo.getChildWithName("iolets"); for (auto iolet : ioletDescriptions) { auto variable = iolet.getProperty("variable").toString() == "1"; @@ -819,7 +820,10 @@ class ObjectBrowserDialog : public Component { auto& library = *editor->pd->objectLibrary; for (auto& object : library.getAllObjects()) { - auto categoriesTree = library.getObjectInfo(object).getChildWithName("categories"); + auto info = library.getObjectInfo(object); + if(!info.isValid()) continue; + + auto categoriesTree = info.getChildWithName("categories"); for (auto category : categoriesTree) { auto cat = category.getProperty("name").toString(); diff --git a/Source/Dialogs/ObjectReferenceDialog.h b/Source/Dialogs/ObjectReferenceDialog.h index 1bd3a626cc..a99496d490 100644 --- a/Source/Dialogs/ObjectReferenceDialog.h +++ b/Source/Dialogs/ObjectReferenceDialog.h @@ -377,6 +377,8 @@ class ObjectReferenceDialog : public Component { bool hasUnknownOutletLayout = false; auto objectInfo = library.getObjectInfo(name); + if(!objectInfo.isValid()) return; + auto ioletDescriptions = objectInfo.getChildWithName("iolets"); for (auto iolet : ioletDescriptions) { auto variable = iolet.getProperty("variable").toString() == "1"; diff --git a/Source/Pd/Library.cpp b/Source/Pd/Library.cpp index bdaeae3514..0ad94371b1 100644 --- a/Source/Pd/Library.cpp +++ b/Source/Pd/Library.cpp @@ -99,6 +99,25 @@ Library::Library(pd::Instance* instance) MemoryInputStream instream(BinaryData::Documentation_bin, BinaryData::Documentation_binSize, false); documentationTree = ValueTree::readFromStream(instream); + for(auto child : documentationTree) + { + auto categoriesTree = child.getChildWithName("categories"); + + String origin; + for (auto category : categoriesTree) { + auto cat = category.getProperty("name").toString(); + if (objectOrigins.contains(cat)) { + origin = cat; + } + } + + documentationIndex[hash(child.getProperty("name").toString())] = child; + if(origin.isNotEmpty()) { + documentationIndex[hash(origin + "/" + child.getProperty("name").toString())] = child; + } + } + + watcher.addFolder(ProjectInfo::appDataDir); watcher.addListener(this); @@ -149,9 +168,11 @@ void Library::getExtraSuggestions(int currentNumSuggestions, String const& query StringArray result; StringArray matches; + // TODO: why not iterate the documentation tree directly?? for (const auto& object : getAllObjects()) { auto info = getObjectInfo(object); - + if(!info.isValid()) continue; + auto description = info.getProperty("description").toString(); auto iolets = info.getChildWithName("iolets"); @@ -188,7 +209,7 @@ void Library::getExtraSuggestions(int currentNumSuggestions, String const& query ValueTree Library::getObjectInfo(String const& name) { - return documentationTree.getChildWithProperty("name", name.fromLastOccurrenceOf("/", false, false)); + return documentationIndex[hash(name)]; } std::array Library::parseIoletTooltips(ValueTree const& iolets, String const& name, int numIn, int numOut) diff --git a/Source/Pd/Library.h b/Source/Pd/Library.h index ceff3fe9e3..497681e026 100644 --- a/Source/Pd/Library.h +++ b/Source/Pd/Library.h @@ -77,6 +77,7 @@ class Library : public FileSystemWatcher::Listener { ThreadPool objectSearchThread = ThreadPool(1); ValueTree documentationTree; + std::unordered_map documentationIndex; }; } // namespace pd diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 603b619fe3..12847c4ae3 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1645,7 +1645,6 @@ bool PluginEditor::perform(InvocationInfo const& info) return true; } case CommandIDs::ConnectionStyle: { - bool noneSegmented = true; for (auto* con : cnv->getSelectionOfType()) { if (con->isSegmented()) From cae3f8b4def2eaab965d9784adbbb8e0ec9b9735 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 30 Apr 2024 14:11:55 +0200 Subject: [PATCH 0660/1030] Fixed tooltip lookup issues --- Source/Pd/Library.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Source/Pd/Library.cpp b/Source/Pd/Library.cpp index 0ad94371b1..fc207ed65e 100644 --- a/Source/Pd/Library.cpp +++ b/Source/Pd/Library.cpp @@ -110,9 +110,16 @@ Library::Library(pd::Instance* instance) origin = cat; } } - - documentationIndex[hash(child.getProperty("name").toString())] = child; - if(origin.isNotEmpty()) { + + if(origin.isEmpty()) { + documentationIndex[hash(child.getProperty("name").toString())] = child; + } + else if(origin == "Gem") + { + documentationIndex[hash(origin + "/" + child.getProperty("name").toString())] = child; + } + else { + documentationIndex[hash(child.getProperty("name").toString())] = child; documentationIndex[hash(origin + "/" + child.getProperty("name").toString())] = child; } } From 5acd92c50bd7a8c1bf3ae192d3faa0a0ef12c986 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 30 Apr 2024 16:48:00 +0200 Subject: [PATCH 0661/1030] Fixed pdlua bugs --- Libraries/pd-lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/pd-lua b/Libraries/pd-lua index 59ec850530..747d4b9e2c 160000 --- a/Libraries/pd-lua +++ b/Libraries/pd-lua @@ -1 +1 @@ -Subproject commit 59ec850530b0fab06e846163af8950ba8fc89a36 +Subproject commit 747d4b9e2c6fac308cf7fa90538e2fa9add2095a From a60702ccf99d6bfbed36372cd79a24c6d18fdf19 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 30 Apr 2024 18:10:09 +0200 Subject: [PATCH 0662/1030] Fixed rendering for non-ARGB images with [else/pic] --- Libraries/nanovg | 2 +- Source/Objects/PictureObject.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Libraries/nanovg b/Libraries/nanovg index 7a204ed79c..cccaf8952c 160000 --- a/Libraries/nanovg +++ b/Libraries/nanovg @@ -1 +1 @@ -Subproject commit 7a204ed79cab6f7f81c978721d57cf1286895305 +Subproject commit cccaf8952cdb3a67aef855bebd541d7c63e00ca5 diff --git a/Source/Objects/PictureObject.h b/Source/Objects/PictureObject.h index c43aa13829..3abba9ea2a 100644 --- a/Source/Objects/PictureObject.h +++ b/Source/Objects/PictureObject.h @@ -139,6 +139,9 @@ class PictureObject final : public ObjectBase, public NVGContextListener { int convertImage(NVGcontext* nvg, Image& image, Rectangle bounds) { Image::BitmapData imageData(image, Image::BitmapData::readOnly); + const auto argbImage = image.convertedToFormat(Image::ARGB); + const Image::BitmapData imageData(argbImage, Image::BitmapData::readOnly); + for (int y = 0; y < bounds.getHeight(); y++) { auto* scanLine = (uint32*) imageData.getLinePointer(y + bounds.getY()); From 30c69d2ca8d091785b791927ab9736a7861a78ef Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 30 Apr 2024 18:13:37 +0200 Subject: [PATCH 0663/1030] Compilation fix --- Source/Objects/PictureObject.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Objects/PictureObject.h b/Source/Objects/PictureObject.h index 3abba9ea2a..4e88f3281b 100644 --- a/Source/Objects/PictureObject.h +++ b/Source/Objects/PictureObject.h @@ -138,7 +138,6 @@ class PictureObject final : public ObjectBase, public NVGContextListener { int convertImage(NVGcontext* nvg, Image& image, Rectangle bounds) { - Image::BitmapData imageData(image, Image::BitmapData::readOnly); const auto argbImage = image.convertedToFormat(Image::ARGB); const Image::BitmapData imageData(argbImage, Image::BitmapData::readOnly); From 76009422431af3d94065a38e141a8899a0e5fb1d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 30 Apr 2024 21:03:02 +0200 Subject: [PATCH 0664/1030] Compilation fix --- Libraries/nanovg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/nanovg b/Libraries/nanovg index cccaf8952c..3e913a7f23 160000 --- a/Libraries/nanovg +++ b/Libraries/nanovg @@ -1 +1 @@ -Subproject commit cccaf8952cdb3a67aef855bebd541d7c63e00ca5 +Subproject commit 3e913a7f232701d818536b5a3036ca43285e783f From 0e96b0e309301cc0eeccd7d4a108d453320215f6 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 1 May 2024 01:14:10 +0200 Subject: [PATCH 0665/1030] Fixed broken autocompletion --- Source/Canvas.cpp | 2 ++ Source/Components/SuggestionComponent.h | 31 ++++++++++++++++++++----- Source/Pd/Library.cpp | 3 ++- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 27d2715449..e7fab7af13 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -478,6 +478,8 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) nvgStroke(nvg); } + suggestor->renderAutocompletion(nvg); + nvgRestore(nvg); // Draw scrollbars diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index 3181316942..002542ae97 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -16,15 +16,16 @@ int is_gem_object(const char* sym); // Component that sits on top of a TextEditor and will draw auto-complete suggestions over it class AutoCompleteComponent - : public Component + : public Component, public NVGComponent , public ComponentListener { String suggestion; Canvas* cnv; Component::SafePointer editor; - + std::unique_ptr nvgCtx; + public: AutoCompleteComponent(TextEditor* e, Canvas* c) - : cnv(c) + : NVGComponent(this), cnv(c) , editor(e) { setAlwaysOnTop(true); @@ -77,7 +78,7 @@ class AutoCompleteComponent return; auto editorText = editor->getText(); - + if (editorText.startsWith(suggestionText)) { suggestion = ""; repaint(); @@ -102,7 +103,20 @@ class AutoCompleteComponent suggestion = suggestion.upToFirstOccurrenceOf(" ", false, false); repaint(); } + + void render(NVGcontext* nvg) override + { + nvgSave(nvg); + nvgTranslate(nvg, getX(), getY()); + if(!nvgCtx || nvgCtx->getContext() != nvg) nvgCtx = std::make_unique(nvg); + Graphics g(*nvgCtx); + { + paintEntireComponent(g, true); + } + nvgRestore(nvg); + } + private: bool shouldAutocomplete = true; String stashedText; @@ -131,7 +145,7 @@ class AutoCompleteComponent auto completionBounds = getLocalBounds().toFloat().withTrimmedLeft(editorTextWidth + 7.5f); auto colour = findColour(PlugDataColour::canvasTextColourId).withAlpha(0.65f); - Fonts::drawText(g, suggestion, completionBounds.translated(-1, -1), colour); + Fonts::drawText(g, suggestion, completionBounds.translated(-1.25f, -0.25f), colour); } }; // Suggestions component that shows up when objects are edited @@ -293,6 +307,11 @@ class SuggestionComponent : public Component { buttons.clear(); } + + void renderAutocompletion(NVGcontext* nvg) + { + if(autoCompleteComponent) autoCompleteComponent->render(nvg); + } void createCalloutBox(Object* object, TextEditor* editor) { @@ -893,7 +912,7 @@ class SuggestionComponent : public Component return nearbyMethods; } - + void deselectAll() { for (auto* button : buttons) { diff --git a/Source/Pd/Library.cpp b/Source/Pd/Library.cpp index fc207ed65e..2079344276 100644 --- a/Source/Pd/Library.cpp +++ b/Source/Pd/Library.cpp @@ -334,9 +334,10 @@ File Library::findHelpfile(t_gobj* obj, File const& parentPatchFile) auto findHelpPatch = [&firstName, &secondName](File const& searchDir) -> File { for (const auto& file : OSUtils::iterateDirectory(searchDir, false, true)) { auto pathName = file.getFullPathName().replace("\\", "/").trimCharactersAtEnd("/"); - // Hack to make it find else/cyclone helpfiles... + // Hack to make it find else/cyclone/Gem helpfiles... pathName = pathName.replace("/else", "/9.else"); pathName = pathName.replace("/cyclone", "/10.else"); + pathName = pathName.replace("/Gem", "/14.gem"); if (pathName.endsWith("/" + firstName) || pathName.endsWith("/" + secondName)) { return file; From d23b6898c7492f58dfbe2f6e6e37918e7530d63e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 1 May 2024 01:21:27 +0200 Subject: [PATCH 0666/1030] Fixed helpfile searching --- Source/Pd/Library.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Pd/Library.cpp b/Source/Pd/Library.cpp index 2079344276..d419deb74d 100644 --- a/Source/Pd/Library.cpp +++ b/Source/Pd/Library.cpp @@ -335,9 +335,9 @@ File Library::findHelpfile(t_gobj* obj, File const& parentPatchFile) for (const auto& file : OSUtils::iterateDirectory(searchDir, false, true)) { auto pathName = file.getFullPathName().replace("\\", "/").trimCharactersAtEnd("/"); // Hack to make it find else/cyclone/Gem helpfiles... - pathName = pathName.replace("/else", "/9.else"); - pathName = pathName.replace("/cyclone", "/10.else"); - pathName = pathName.replace("/Gem", "/14.gem"); + pathName = pathName.replace("/9.else", "/else"); + pathName = pathName.replace("/10.cyclone", "/cyclone"); + pathName = pathName.replace("/14.gem", "/Gem"); if (pathName.endsWith("/" + firstName) || pathName.endsWith("/" + secondName)) { return file; From 2b5a2286ad978e0767adc69554d1e6016e24e6a6 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 1 May 2024 01:46:52 +0200 Subject: [PATCH 0667/1030] Make md documentation correctly differentiate vanilla, ELSE, cyclone and Gem objects --- Source/Canvas.cpp | 2 +- Source/Components/SuggestionComponent.h | 4 ++-- Source/Dialogs/Dialogs.cpp | 4 ++-- Source/Object.cpp | 11 +++++---- Source/Object.h | 2 +- Source/Objects/ObjectBase.cpp | 14 ++++++++++++ Source/Objects/ObjectBase.h | 3 +++ Source/Pd/Instance.cpp | 6 ++--- Source/Pd/Library.cpp | 30 ++++++++++++++++++++++++- Source/Pd/Library.h | 2 ++ 10 files changed, 64 insertions(+), 14 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index e7fab7af13..ef2a91a4c8 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -1068,7 +1068,7 @@ void Canvas::updateSidebarSelection() if (!allParameters.isEmpty() || editor->sidebar->isPinned()) { String objectName = "(" + String(lassoSelection.size()) + " selected)"; if (lassoSelection.size() == 1 && lassoSelection.getFirst()) { - objectName = lassoSelection.getFirst()->getType(); + objectName = lassoSelection.getFirst()->getType(false); } editor->sidebar->showParameters(objectName, allParameters); diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index 002542ae97..65cb00682d 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -615,7 +615,7 @@ class SuggestionComponent : public Component return suggestions; }; - if (currentObject->gui && currentObject->gui->getType() == "message") { + if (currentObject->gui && currentObject->getType(false) == "message") { auto nearbyMethods = findNearbyMethods(currentText); numOptions = std::min(buttons.size(), nearbyMethods.size()); @@ -843,7 +843,7 @@ class SuggestionComponent : public Component if (!obj->getPointer() || obj == currentObject || distance > 300) continue; - auto objectName = obj->gui->getType(); + auto objectName = obj->getType(); auto alreadyExists = std::find_if(objects.begin(), objects.end(), [objectName](auto const& toCompare) { return std::get<0>(toCompare) == objectName; }) != objects.end(); diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index b38e254a4a..ff8bad2240 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -633,7 +633,7 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent cnv->pd->unlockAudioThread(); Array parameters = { object->gui->getParameters() }; - editor->sidebar->showParameters(object->gui->getType(), parameters); + editor->sidebar->showParameters(object->getType(false), parameters); } return; @@ -726,7 +726,7 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent object->openHelpPatch(); break; case Reference: - Dialogs::showObjectReferenceDialog(&editor->openedDialog, editor, object->gui->getType()); + Dialogs::showObjectReferenceDialog(&editor->openedDialog, editor, object->getType()); break; default: break; diff --git a/Source/Object.cpp b/Source/Object.cpp index 5e306b973a..f264c18ac8 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -479,9 +479,11 @@ Array> Object::getCorners() const return corners; } -String Object::getType() const +String Object::getType(bool withOriginPrefix) const { - return gui ? gui->getType() : String(); + if(gui && withOriginPrefix) return gui->getTypeWithOriginPrefix(); + else if(gui) return gui->getType(); + return String(); } void Object::triggerOverlayActiveState() @@ -571,8 +573,9 @@ void Object::updateTooltips() { if (!gui || cnv->isGraph) return; - - auto objectInfo = cnv->pd->objectLibrary->getObjectInfo(gui->getType()); + + + auto objectInfo = cnv->pd->objectLibrary->getObjectInfo(gui->getTypeWithOriginPrefix()); std::array ioletTooltips; diff --git a/Source/Object.h b/Source/Object.h index ca4a54cd5c..61d08f1718 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -68,7 +68,7 @@ class Object : public Component void nvgContextDeleted(NVGcontext* nvg) override; - String getType() const; + String getType(bool withOriginPrefix = true) const; Rectangle getSelectableBounds(); Rectangle getObjectBounds(); diff --git a/Source/Objects/ObjectBase.cpp b/Source/Objects/ObjectBase.cpp index cb5b179d2f..2c71320fa8 100644 --- a/Source/Objects/ObjectBase.cpp +++ b/Source/Objects/ObjectBase.cpp @@ -230,6 +230,20 @@ String ObjectBase::getText() return ""; } +String ObjectBase::getTypeWithOriginPrefix() const +{ + if (auto obj = ptr.get()) { + auto origin = pd::Library::getObjectOrigin(obj.get()); + auto type = getType(); + + if(origin.isEmpty()) return type; + + return origin + "/" + type; + } + + return {}; +} + String ObjectBase::getType() const { if (auto obj = ptr.get()) { diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index acf5f3258c..9552c9b4be 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -169,6 +169,9 @@ class ObjectBase : public Component // Returns the Pd class name of the object String getType() const; + // Returns the Pd class name of the object with the library prefix in front of it, eg "else" + String getTypeWithOriginPrefix() const; + void moveToFront(); void moveForward(); void moveBackward(); diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 7ceeab9c1d..1383ee0ad2 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -281,15 +281,15 @@ void Instance::initialisePd(String& pdlua_version) libpd_set_instance(libpd_main_instance()); set_class_prefix(gensym("else")); - class_set_extern_dir(gensym("9.else")); + class_set_extern_dir(gensym("else")); pd::Setup::initialiseELSE(); set_class_prefix(gensym("cyclone")); - class_set_extern_dir(gensym("10.cyclone")); + class_set_extern_dir(gensym("cyclone")); pd::Setup::initialiseCyclone(); set_class_prefix(gensym("Gem")); - class_set_extern_dir(gensym("14.gem")); + class_set_extern_dir(gensym("Gem")); pd::Setup::initialiseGem(ProjectInfo::appDataDir.getChildFile("Extra").getChildFile("Gem").getFullPathName().toStdString()); class_set_extern_dir(gensym("")); diff --git a/Source/Pd/Library.cpp b/Source/Pd/Library.cpp index d419deb74d..5a2c8c20e5 100644 --- a/Source/Pd/Library.cpp +++ b/Source/Pd/Library.cpp @@ -282,6 +282,34 @@ void Library::filesystemChanged() updateLibrary(); } +String Library::getObjectOrigin(t_gobj* obj) +{ + auto* pdclass = pd_class(reinterpret_cast(obj)); + + if (pdclass == canvas_class && canvas_isabstraction(reinterpret_cast(obj))) { + auto* cnv = reinterpret_cast(obj); + auto parentPath = String::fromUTF8(canvas_getenv(cnv)->ce_dir->s_name); + for(auto& origin : objectOrigins) + { + if(parentPath.containsIgnoreCase("/" + origin)) + { + return origin; + } + } + } + + auto externDir = String::fromUTF8(pdclass->c_externdir->s_name); + for(auto& origin : objectOrigins) + { + if(externDir.containsIgnoreCase(origin)) + { + return origin; + } + } + + return {}; +} + File Library::findHelpfile(t_gobj* obj, File const& parentPatchFile) { String helpName; @@ -308,7 +336,7 @@ File Library::findHelpfile(t_gobj* obj, File const& parentPatchFile) auto patchHelpPaths = Array(); // Add abstraction dir to search paths - if (pd_class(reinterpret_cast(obj)) == canvas_class && canvas_isabstraction(reinterpret_cast(obj))) { + if (pdclass == canvas_class && canvas_isabstraction(reinterpret_cast(obj))) { auto* cnv = reinterpret_cast(obj); patchHelpPaths.add(File(String::fromUTF8(canvas_getenv(cnv)->ce_dir->s_name))); if (helpDir.isNotEmpty()) { diff --git a/Source/Pd/Library.h b/Source/Pd/Library.h index 497681e026..e70966749d 100644 --- a/Source/Pd/Library.h +++ b/Source/Pd/Library.h @@ -36,6 +36,8 @@ class Library : public FileSystemWatcher::Listener { static File findHelpfile(t_gobj* obj, File const& parentPatchFile); ValueTree getObjectInfo(String const& name); + + static String getObjectOrigin(t_gobj* obj); StringArray getAllObjects(); From 20837d9bcec5a89cad6968f66ab568dba3b80192 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 1 May 2024 01:54:12 +0200 Subject: [PATCH 0668/1030] More helpfile lookup fixes --- Source/Pd/Instance.cpp | 6 +++--- Source/Pd/Library.h | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 1383ee0ad2..7ceeab9c1d 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -281,15 +281,15 @@ void Instance::initialisePd(String& pdlua_version) libpd_set_instance(libpd_main_instance()); set_class_prefix(gensym("else")); - class_set_extern_dir(gensym("else")); + class_set_extern_dir(gensym("9.else")); pd::Setup::initialiseELSE(); set_class_prefix(gensym("cyclone")); - class_set_extern_dir(gensym("cyclone")); + class_set_extern_dir(gensym("10.cyclone")); pd::Setup::initialiseCyclone(); set_class_prefix(gensym("Gem")); - class_set_extern_dir(gensym("Gem")); + class_set_extern_dir(gensym("14.gem")); pd::Setup::initialiseGem(ProjectInfo::appDataDir.getChildFile("Extra").getChildFile("Gem").getFullPathName().toStdString()); class_set_extern_dir(gensym("")); diff --git a/Source/Pd/Library.h b/Source/Pd/Library.h index e70966749d..be89cffd25 100644 --- a/Source/Pd/Library.h +++ b/Source/Pd/Library.h @@ -53,6 +53,7 @@ class Library : public FileSystemWatcher::Listener { ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("10.cyclone"), ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("11.heavylib"), ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("13.pdlua"), + ProjectInfo::appDataDir.getChildFile("Documentation").getChildFile("14.gem"), ProjectInfo::appDataDir.getChildFile("Extra"), ProjectInfo::appDataDir.getChildFile("Externals") }; From 5a884d60d7df2f467a051b2692c9c84ca7fb0913 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 1 May 2024 02:01:01 +0200 Subject: [PATCH 0669/1030] Close file dialogs when closing Heavy panel --- Source/Dialogs/Dialogs.cpp | 5 +++++ Source/Dialogs/Dialogs.h | 2 ++ Source/Heavy/HeavyExportDialog.cpp | 2 ++ 3 files changed, 9 insertions(+) diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index ff8bad2240..119b04b5f0 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -744,6 +744,11 @@ void Dialogs::showObjectMenu(PluginEditor* editor, Component* target) AddObjectMenu::show(editor, target->getScreenBounds()); } +void Dialogs::dismissFileDialog() +{ + fileChooser.reset(nullptr); +} + void Dialogs::showOpenDialog(std::function const& callback, bool canSelectFiles, bool canSelectDirectories, String const& extension, String const& lastFileId, Component* parentComponent) { bool nativeDialog = SettingsFile::getInstance()->wantsNativeDialog(); diff --git a/Source/Dialogs/Dialogs.h b/Source/Dialogs/Dialogs.h index 93ffaedb10..d750daa5d1 100644 --- a/Source/Dialogs/Dialogs.h +++ b/Source/Dialogs/Dialogs.h @@ -184,6 +184,8 @@ struct Dialogs { static void showDeken(PluginEditor* editor); static void showPatchStorage(PluginEditor* editor); + static void dismissFileDialog(); + static void showOpenDialog(std::function const& callback, bool canSelectFiles, bool canSelectDirectories, String const& lastFileId, String const& extension, Component* parentComponent); static void showSaveDialog(std::function const& callback, String const& extension, String const& lastFileId, Component* parentComponent = nullptr, bool directoryMode = false); diff --git a/Source/Heavy/HeavyExportDialog.cpp b/Source/Heavy/HeavyExportDialog.cpp index 7bba181db5..be6f8c7693 100644 --- a/Source/Heavy/HeavyExportDialog.cpp +++ b/Source/Heavy/HeavyExportDialog.cpp @@ -242,6 +242,8 @@ HeavyExportDialog::HeavyExportDialog(Dialog* dialog) HeavyExportDialog::~HeavyExportDialog() { + Dialogs::dismissFileDialog(); + // Clean up temp files Toolchain::deleteTempFiles(); } From 8cc2dbe779dd4d7a92a6aa8b4fe2a9973239ce72 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 1 May 2024 02:28:03 +0200 Subject: [PATCH 0670/1030] Fix small potential issue --- Source/Pd/Library.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Source/Pd/Library.cpp b/Source/Pd/Library.cpp index 5a2c8c20e5..5a5c3eae5f 100644 --- a/Source/Pd/Library.cpp +++ b/Source/Pd/Library.cpp @@ -298,12 +298,14 @@ String Library::getObjectOrigin(t_gobj* obj) } } - auto externDir = String::fromUTF8(pdclass->c_externdir->s_name); - for(auto& origin : objectOrigins) - { - if(externDir.containsIgnoreCase(origin)) + if(pdclass->c_externdir) { + auto externDir = String::fromUTF8(pdclass->c_externdir->s_name); + for(auto& origin : objectOrigins) { - return origin; + if(externDir.containsIgnoreCase(origin)) + { + return origin; + } } } From 04302715b3173a2dd53d0f6a6f68be4ae5a60dc9 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 1 May 2024 14:28:24 +0200 Subject: [PATCH 0671/1030] Fixed bug in [pic] --- Source/Objects/PictureObject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Objects/PictureObject.h b/Source/Objects/PictureObject.h index 4e88f3281b..f0a2445de3 100644 --- a/Source/Objects/PictureObject.h +++ b/Source/Objects/PictureObject.h @@ -53,7 +53,7 @@ class PictureObject final : public ObjectBase, public NVGContextListener { { // TODO: delete image buffers! - cnv->editor->nvgSurface.addNVGContextListener(this); + cnv->editor->nvgSurface.removeNVGContextListener(this); } bool isTransparent() override From 31006f3f28b762e93e04539192797d32e8d13451 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 1 May 2024 15:39:58 +0200 Subject: [PATCH 0672/1030] Fixed various search path issues in plugin version --- Libraries/pure-data | 2 +- Source/Objects/PictureObject.h | 1 + Source/Pd/Interface.h | 20 +------------------- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/Libraries/pure-data b/Libraries/pure-data index 515af37c31..633ba75f2c 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit 515af37c31a036f817094b5995bac4a0a650ff9b +Subproject commit 633ba75f2c123691e5acb9fe703e1dac167a18dc diff --git a/Source/Objects/PictureObject.h b/Source/Objects/PictureObject.h index f0a2445de3..396fd76e3a 100644 --- a/Source/Objects/PictureObject.h +++ b/Source/Objects/PictureObject.h @@ -326,6 +326,7 @@ class PictureObject final : public ObjectBase, public NVGContextListener { // Get pd's search paths char* paths[1024]; int numItems; + pd->setThis(); pd::Interface::getSearchPaths(paths, &numItems); for (int i = 0; i < numItems; i++) { diff --git a/Source/Pd/Interface.h b/Source/Pd/Interface.h index a9aff4f4a7..7634581acf 100644 --- a/Source/Pd/Interface.h +++ b/Source/Pd/Interface.h @@ -53,7 +53,6 @@ struct Interface { auto* cnv = static_cast(libpd_openfile(name, path)); if (cnv) { canvas_vis(cnv, 1.f); - canvas_rename(cnv, gensym(name), gensym(path)); } return cnv; } @@ -90,24 +89,7 @@ struct Interface { static void getSearchPaths(char** paths, int* numItems) { - - t_namelist* pathList = STUFF->st_searchpath; - int i = 0; - while (pathList) { - i++; - pathList = pathList->nl_next; - } - - *numItems = i; - - pathList = STUFF->st_searchpath; - i = 0; - while (pathList) { - paths[i] = pathList->nl_string; - i++; - - pathList = pathList->nl_next; - } + libpd_get_search_paths(paths, numItems); } static t_object* checkObject(t_pd* obj) From 29e143e7df64fd5a59a113f16fe065aa6eb1dad0 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 1 May 2024 16:06:21 +0200 Subject: [PATCH 0673/1030] Fixed potential crash --- Source/Components/ObjectDragAndDrop.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Components/ObjectDragAndDrop.h b/Source/Components/ObjectDragAndDrop.h index 5e5ced4f93..6368cf55f2 100644 --- a/Source/Components/ObjectDragAndDrop.h +++ b/Source/Components/ObjectDragAndDrop.h @@ -23,7 +23,7 @@ class ObjectDragAndDrop : public Component { MouseCursor getMouseCursor() override { - if (editor->isDragAndDropActive()) + if (editor && editor->isDragAndDropActive()) return MouseCursor::DraggingHandCursor; return MouseCursor::PointingHandCursor; From 6e470b10f8e46be25e4e4d1fe10329e1aad200fe Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 1 May 2024 16:35:49 +0200 Subject: [PATCH 0674/1030] Make sure dialog doesn't get stuck on top on Linux --- Source/Dialogs/Dialogs.cpp | 4 ++++ Source/Dialogs/Dialogs.h | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index 119b04b5f0..aa7cc41e30 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -47,7 +47,11 @@ Dialog::Dialog(std::unique_ptr* ownerPtr, Component* editor, int childWi , owner(ownerPtr) , backgroundMargin(margin) { +#if JUCE_LINUX || JUCE_BSD + addToDesktop(0); +#else addToDesktop(ComponentPeer::windowIsTemporary); +#endif setVisible(true); setBounds(parentComponent->getScreenX(), parentComponent->getScreenY(), parentComponent->getWidth(), parentComponent->getHeight()); diff --git a/Source/Dialogs/Dialogs.h b/Source/Dialogs/Dialogs.h index d750daa5d1..e84d675550 100644 --- a/Source/Dialogs/Dialogs.h +++ b/Source/Dialogs/Dialogs.h @@ -33,7 +33,7 @@ class Dialog : public Component, public ComponentListener { } } } - + void componentMovedOrResized(Component& comp, bool wasMoved, bool wasResized) override { setBounds(parentComponent->getScreenX(), parentComponent->getScreenY(), parentComponent->getWidth(), parentComponent->getHeight()); @@ -104,7 +104,7 @@ class Dialog : public Component, public ComponentListener { void mouseDown(MouseEvent const& e) override { if(!hasKeyboardFocus(false)) { - parentComponent->toFront(true); + parentComponent->toFront(false); toFront(true); } From f932bb799df7e8ab7f882bff19515e7eca4ae64d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 2 May 2024 01:01:42 +0200 Subject: [PATCH 0675/1030] Feature: manipulating audio parameters (create, range, mode) with the [param] object --- Resources/Documentation/plugdata/param.md | 8 +++ Resources/Patches/param-help.pd | 31 +++++---- Resources/Patches/param.pd | 79 ++++++++++++++--------- Source/Pd/Instance.cpp | 35 ++++++++++ Source/Pd/Instance.h | 22 ++++--- Source/PluginProcessor.cpp | 61 +++++++++++++++++ Source/PluginProcessor.h | 3 + 7 files changed, 186 insertions(+), 53 deletions(-) diff --git a/Resources/Documentation/plugdata/param.md b/Resources/Documentation/plugdata/param.md index 520728a884..30cc40b899 100644 --- a/Resources/Documentation/plugdata/param.md +++ b/Resources/Documentation/plugdata/param.md @@ -8,6 +8,14 @@ categories: pdcategories: PlugData, UI +methods: + - type: create + description: activate the automation paramter + - type: range + description: set parameter range + - type: mode + description: set parameter mode (1=float, 2=integer, 3=logarithmic, 4=exponential) + arguments: - type: symbol description: parameter name diff --git a/Resources/Patches/param-help.pd b/Resources/Patches/param-help.pd index 7b33ad0186..5f522b07c0 100644 --- a/Resources/Patches/param-help.pd +++ b/Resources/Patches/param-help.pd @@ -1,6 +1,6 @@ #N canvas 637 37 657 479 10; -#X obj 5 364 cnv 3 550 4 empty empty outlets 8 12 0 13 #dcdcdc #000000 0; -#X obj 129 375 cnv 17 4 17 empty empty 0 5 9 0 16 #dcdcdc #9c9c9c 0; +#X obj 6 469 cnv 3 550 4 empty empty outlets 8 12 0 13 #dcdcdc #000000 0; +#X obj 82 480 cnv 17 4 17 empty empty 0 5 9 0 16 #dcdcdc #9c9c9c 0; #X obj 305 6 cnv 15 250 40 empty empty empty 12 13 0 18 #7c7c7c #e0e4dc 0; #N canvas 382 141 749 319 (subpatch) 0; #X coords 0 -1 1 1 252 42 2 0 0; @@ -11,21 +11,28 @@ #N canvas 0 22 450 278 (subpatch) 0; #X coords 0 1 100 -1 302 42 1 0 0; #X restore 4 5 graph; -#X obj 48 194 param param1, f 16; -#X obj 164 194 hsl 128 17 0 127 0 0 empty empty empty -2 -8 0 10 #e4e4e4 #5a5a5a #5a5a5a 0 1; -#X obj 119 157 tgl 25 0 empty empty empty 17 7 0 10 #e4e4e4 #5a5a5a #5a5a5a 0 1; +#X obj 91 187 param param1, f 16; +#X obj 216 187 hsl 128 17 0 127 0 0 empty empty empty -2 -8 0 10 #e4e4e4 #5a5a5a #5a5a5a 0 1; +#X obj 162 150 tgl 25 0 empty empty empty 17 7 0 10 #e4e4e4 #5a5a5a #5a5a5a 0 1; #X obj 377 187 param param2 1; #X obj 495 186 nbx 5 18 -1e+37 1e+37 0 0 param2-gui-s param2-gui-r empty 0 -8 0 10 #e4e4e4 #5a5a5a #5a5a5a 0 256; #X text 49 93 [param] sends and receives automation parameters to the DAW. You'll have to create an automation parameter in the sidebar. You, f 82; #X text 377 217 Use the paramname-gui-s and paramname-gui-r pair to send/receive values directly to GUI objects, f 55; #X obj 5 293 cnv 3 550 4 empty empty inlets 8 12 0 13 #dcdcdc #000000 0; -#X obj 129 304 cnv 17 4 17 empty empty 0 5 9 0 16 #dcdcdc #9c9c9c 0; -#X obj 129 334 cnv 17 4 17 empty empty 1 5 9 0 16 #dcdcdc #9c9c9c 0; -#X text 175 303 float - set DAW parameter value, f 38; -#X text 174 374 float - receive DAW parameter value; -#X obj 3 415 cnv 3 550 3 empty empty arguments 8 12 0 13 #dcdcdc #000000 0; -#X text 174 329 float - inform DAW about parameter change state (0 = reading \, 1 = writing), f 50; -#X text 145 426 1) float - automatically handle parameter change state when GUI objects are interacted with \, 1 is enabled (default 0), f 66; +#X obj 81 304 cnv 17 4 17 empty empty 0 5 9 0 16 #dcdcdc #9c9c9c 0; +#X obj 82 439 cnv 17 4 17 empty empty 1 5 9 0 16 #dcdcdc #9c9c9c 0; +#X text 127 303 float - set DAW parameter value, f 38; +#X text 127 479 float - receive DAW parameter value; +#X obj 4 520 cnv 3 550 3 empty empty arguments 8 12 0 13 #dcdcdc #000000 0; +#X text 127 434 float - inform DAW about parameter change state (0 = reading \, 1 = writing), f 50; +#X text 98 531 1) float - automatically handle parameter change state when GUI objects are interacted with \, 1 is enabled (default 0), f 66; +#X text 127 362 range - set parameter range, f 71; +#X msg 4 150 create; +#X msg 63 150 range 0 127; +#X text 127 322 create - activates the automation paramter \, making it visible in the DAW and sidebar panel, f 71; +#X text 127 388 mode - set parameter mode (1=float \, 2=integer \, 3=logarithmic \, 4=exponential), f 71; #X connect 8 0 9 0; #X connect 9 0 8 0; #X connect 10 0 8 1; +#X connect 24 0 8 0; +#X connect 25 0 8 0; diff --git a/Resources/Patches/param.pd b/Resources/Patches/param.pd index 7e0a0b3078..adca82a8d3 100644 --- a/Resources/Patches/param.pd +++ b/Resources/Patches/param.pd @@ -1,33 +1,48 @@ #N canvas 536 141 700 308 12; -#X obj 55 363 outlet; -#X obj 59 192 s param; -#X obj 382 214 s param_change, f 15; -#X obj 59 123 inlet; -#X obj 382 140 inlet; -#X obj 59 159 list prepend \$1; -#X obj 382 176 list prepend \$1; -#X obj 55 320 r \$1; -#X text 39 87 Receive value from pd and send to DAW; -#X text 39 284 Receive value from DAW and send to pd, f 35; -#X text 344 87 Receive param changing state from pd and send to DAW; -#X obj 110 363 s \$1-gui-r; -#X obj 110 123 r \$1-gui-s; -#X obj 382 346 loadmess \$2; -#X obj 382 439 gate; -#X obj 422 372 r gui; -#X obj 422 402 route mouse; -#X obj 382 474 s \$0-param-change; -#X obj 436 140 r \$0-param-change; -#X text 344 277 If the first argument is 1 \, automatically send DAW parameter state changes when GUI objects are interacted with, f 45; -#X connect 3 0 5 0 empty; -#X connect 4 0 6 0 empty; -#X connect 5 0 1 0 empty; -#X connect 6 0 2 0 empty; -#X connect 7 0 0 0 empty; -#X connect 7 0 11 0 empty; -#X connect 12 0 5 0 empty; -#X connect 13 0 14 0 empty; -#X connect 14 0 17 0 empty; -#X connect 15 0 16 0 empty; -#X connect 16 0 14 1 empty; -#X connect 18 0 6 0 empty; +#X obj 364 321 outlet; +#X obj 368 163 s param; +#X obj 691 172 s param_change, f 15; +#X obj 223 63 inlet; +#X obj 691 98 inlet; +#X obj 368 130 list prepend \$1; +#X obj 691 134 list prepend \$1; +#X obj 364 278 r \$1; +#X text 348 45 Receive value from pd and send to DAW; +#X text 348 242 Receive value from DAW and send to pd, f 35; +#X text 653 45 Receive param changing state from pd and send to DAW; +#X obj 419 321 s \$1-gui-r; +#X obj 419 81 r \$1-gui-s; +#X obj 691 304 loadmess \$2; +#X obj 691 397 gate; +#X obj 731 330 r gui; +#X obj 731 360 route mouse; +#X obj 691 432 s \$0-param-change; +#X obj 745 98 r \$0-param-change; +#X text 653 235 If the first argument is 1 \, automatically send DAW parameter state changes when GUI objects are interacted with, f 45; +#X obj 29 218 s param_create; +#X obj 29 180 symbol \$1; +#X text 15 98 Handle parameter create mesage; +#X obj 223 99 route create range mode; +#X obj 138 180 list prepend \$1; +#X obj 250 177 list prepend \$1; +#X obj 138 218 s param_range; +#X obj 250 215 s param_mode; +#X connect 3 0 23 0; +#X connect 4 0 6 0; +#X connect 5 0 1 0; +#X connect 6 0 2 0; +#X connect 7 0 0 0; +#X connect 7 0 11 0; +#X connect 12 0 5 0; +#X connect 13 0 14 0; +#X connect 14 0 17 0; +#X connect 15 0 16 0; +#X connect 16 0 14 1; +#X connect 18 0 6 0; +#X connect 21 0 20 0; +#X connect 23 0 21 0; +#X connect 23 1 24 0; +#X connect 23 2 25 0; +#X connect 23 3 5 0; +#X connect 24 0 26 0; +#X connect 25 0 27 0; diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 7ceeab9c1d..3e3809e9aa 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -144,6 +144,9 @@ Instance::~Instance() pd_free(static_cast(parameterReceiver)); pd_free(static_cast(pluginLatencyReceiver)); pd_free(static_cast(parameterChangeReceiver)); + pd_free(static_cast(parameterCreateReceiver)); + pd_free(static_cast(parameterRangeReceiver)); + pd_free(static_cast(parameterModeReceiver)); // JYG added this pd_free(static_cast(dataBufferReceiver)); @@ -206,6 +209,14 @@ void Instance::initialisePd(String& pdlua_version) parameterChangeReceiver = pd::Setup::createReceiver(this, "param_change", reinterpret_cast(internal::instance_multi_bang), reinterpret_cast(internal::instance_multi_float), reinterpret_cast(internal::instance_multi_symbol), reinterpret_cast(internal::instance_multi_list), reinterpret_cast(internal::instance_multi_message)); + parameterCreateReceiver = pd::Setup::createReceiver(this, "param_create", reinterpret_cast(internal::instance_multi_bang), reinterpret_cast(internal::instance_multi_float), reinterpret_cast(internal::instance_multi_symbol), + reinterpret_cast(internal::instance_multi_list), reinterpret_cast(internal::instance_multi_message)); + + parameterRangeReceiver = pd::Setup::createReceiver(this, "param_range", reinterpret_cast(internal::instance_multi_bang), reinterpret_cast(internal::instance_multi_float), reinterpret_cast(internal::instance_multi_symbol), + reinterpret_cast(internal::instance_multi_list), reinterpret_cast(internal::instance_multi_message)); + + parameterModeReceiver = pd::Setup::createReceiver(this, "param_mode", reinterpret_cast(internal::instance_multi_bang), reinterpret_cast(internal::instance_multi_float), reinterpret_cast(internal::instance_multi_symbol), + reinterpret_cast(internal::instance_multi_list), reinterpret_cast(internal::instance_multi_message)); atoms = malloc(sizeof(t_atom) * 512); // Register callback when pd's gui changes @@ -489,6 +500,30 @@ void Instance::processMessage(Message mess) { performParameterChange(0, name, value); } break; + case hash("param_create"): + if (mess.list.size() >= 1) { + if (!mess.list[0].isSymbol()) return; + auto name = mess.list[0].toString(); + enableAudioParameter(name); + } + break; + case hash("param_range"): + if (mess.list.size() >= 3) { + if (!mess.list[0].isSymbol() || !mess.list[1].isFloat() || !mess.list[2].isFloat()) return; + auto name = mess.list[0].toString(); + float min = mess.list[1].getFloat(); + float max = mess.list[2].getFloat(); + setParameterRange(name, min, max); + } + break; + case hash("param_mode"): + if (mess.list.size() >= 2) { + if (!mess.list[0].isSymbol() || !mess.list[1].isFloat()) return; + auto name = mess.list[0].toString(); + float mode = mess.list[1].getFloat(); + setParameterMode(name, mode); + } + break; case hash("param_change"): if (mess.list.size() >= 2) { if (!mess.list[0].isSymbol() || !mess.list[1].isFloat()) diff --git a/Source/Pd/Instance.h b/Source/Pd/Instance.h index 7d810b3858..00a91b1b9f 100644 --- a/Source/Pd/Instance.h +++ b/Source/Pd/Instance.h @@ -228,11 +228,9 @@ class Instance { static void registerLuaClass(const char* object); bool isLuaClass(hash32 objectNameHash); - virtual void receiveDSPState(bool dsp) { } + virtual void updateConsole(int numMessages, bool newWarning) = 0; - virtual void updateConsole(int numMessages, bool newWarning) { } - - virtual void titleChanged() { } + virtual void titleChanged() = 0; void enqueueFunctionAsync(std::function const& fn); @@ -257,13 +255,16 @@ class Instance { void updateObjectImplementations(); void clearObjectImplementationsForPatch(pd::Patch* p); - virtual void performParameterChange(int type, String const& name, float value) { } - - virtual void performLatencyCompensationChange(float value) { } + virtual void performParameterChange(int type, String const& name, float value) = 0; + virtual void enableAudioParameter(String const& name) = 0; + virtual void setParameterRange(String const& name, float min, float max) = 0; + virtual void setParameterMode(String const& name, int mode) = 0; + + virtual void performLatencyCompensationChange(float value) = 0; // JYG added this - virtual void fillDataBuffer(std::vector const& list) { } - virtual void parseDataBuffer(XmlElement const& xml) { } + virtual void fillDataBuffer(std::vector const& list) = 0; + virtual void parseDataBuffer(XmlElement const& xml) = 0; void logMessage(String const& message); void logError(String const& message); @@ -304,6 +305,9 @@ class Instance { void* parameterReceiver = nullptr; void* pluginLatencyReceiver = nullptr; void* parameterChangeReceiver = nullptr; + void* parameterCreateReceiver = nullptr; + void* parameterRangeReceiver = nullptr; + void* parameterModeReceiver = nullptr; void* midiReceiver = nullptr; void* printReceiver = nullptr; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 2e6871d4ec..a89e857e90 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1641,6 +1641,67 @@ void PluginProcessor::performLatencyCompensationChange(float value) triggerAsyncUpdate(); } +void PluginProcessor::setParameterRange(String const& name, float min, float max) +{ + for (auto* p : getParameters()) { + auto* param = dynamic_cast(p); + if (param->isEnabled() && param->getTitle() == name) { + max = std::max(max, min + 0.000001f); + param->setRange(min, max); + break; + } + } + + for(auto* editor : getEditors()) + { + editor->sidebar->updateAutomationParameters(); + } +} + +void PluginProcessor::setParameterMode(String const& name, int mode) +{ + for (auto* p : getParameters()) { + auto* param = dynamic_cast(p); + if (param->isEnabled() && param->getTitle() == name) { + param->setMode(static_cast(std::clamp(mode, 1, 4))); + break; + } + } + + for(auto* editor : getEditors()) + { + editor->sidebar->updateAutomationParameters(); + } +} + +void PluginProcessor::enableAudioParameter(String const& name) +{ + int numEnabled = 0; + for (auto* p : getParameters()) { + auto* param = dynamic_cast(p); + numEnabled += param->isEnabled(); + if (param->isEnabled() && param->getTitle() == name) { + return; + } + } + + for (auto* p : getParameters()) { + auto* param = dynamic_cast(p); + if (!param->isEnabled()) { + param->setEnabled(true); + param->setName(name); + param->setIndex(numEnabled + 1); + param->notifyDAW(); + break; + } + } + + for(auto* editor : getEditors()) + { + editor->sidebar->updateAutomationParameters(); + } +} + void PluginProcessor::performParameterChange(int type, String const& name, float value) { // Type == 1 means it sets the change gesture state diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index a656555335..ccc57a728d 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -115,6 +115,9 @@ class PluginProcessor : public AudioProcessor, public AsyncUpdater Array getEditors() const; void performParameterChange(int type, String const& name, float value) override; + void enableAudioParameter(String const& name) override; + void setParameterRange(String const& name, float min, float max) override; + void setParameterMode(String const& name, int mode) override; void performLatencyCompensationChange(float value) override; From 9854e510b5c3e9599a6b661dadd6792f39d3674b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 2 May 2024 01:12:49 +0200 Subject: [PATCH 0676/1030] Implement message for entering/exiting pluginmode --- Source/PluginProcessor.cpp | 9 +++++++++ Source/Utility/Config.h | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index a89e857e90..9abb45cc12 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1516,6 +1516,15 @@ void PluginProcessor::receiveSysMessage(String const& selector, std::vectorenablePluginMode(editor->getCurrentCanvas()); + } + }); + break; + } case hash("quit"): case hash("verifyquit"): { if (ProjectInfo::isStandalone) { diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index df8ebeef37..c6b0633efa 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -52,7 +52,7 @@ struct ProjectInfo { #else static inline File const appDataDir = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory).getChildFile("plugdata"); #endif - static inline String const versionSuffix = "-test4"; + static inline String const versionSuffix = "-test5"; static inline File const versionDataDir = appDataDir.getChildFile("Versions").getChildFile(ProjectInfo::versionString + versionSuffix); }; From 50a2ed05c9f7c8afc7ee32d19bd8eb1478d3e7bb Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 2 May 2024 01:16:25 +0200 Subject: [PATCH 0677/1030] Plugin mode control fixes --- Source/PluginProcessor.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 9abb45cc12..346c4b83f3 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1519,8 +1519,24 @@ void PluginProcessor::receiveSysMessage(String const& selector, std::vectorenablePluginMode(editor->getCurrentCanvas()); + if(!ProjectInfo::isStandalone) { + // TODO: it would be nicer if we could specifically target the correct patch here, instead of picking the first one and praying + patches[0]->openInPluginMode = true; + auto editors = getEditors(); + if(editors.size()) { + for(auto* canvas : openedEditors[0]->canvases) + { + if(patches[0] == canvas->patch) + { + editors[0]->enablePluginMode(canvas); + } + } + } + } + else { + for (auto* editor : getEditors()) { + editor->enablePluginMode(editor->getCurrentCanvas()); + } } }); break; From 7ec5d1222d417563f8da37fc8e3ca815dba27f83 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 2 May 2024 01:20:07 +0200 Subject: [PATCH 0678/1030] Small bugfix --- Source/PluginProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 346c4b83f3..613c0fa40a 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1524,7 +1524,7 @@ void PluginProcessor::receiveSysMessage(String const& selector, std::vectoropenInPluginMode = true; auto editors = getEditors(); if(editors.size()) { - for(auto* canvas : openedEditors[0]->canvases) + for(auto* canvas : editors[0]->canvases) { if(patches[0] == canvas->patch) { From 5b3c11085dfa4d14342487a8fb8179c99fae12a0 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 2 May 2024 13:10:56 +0200 Subject: [PATCH 0679/1030] Fixed broken iolet area colour --- Source/Objects/TextObject.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index 038461995f..4268024b7c 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -206,12 +206,25 @@ class TextBase : public ObjectBase auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); + auto ioletAreaColour = convertColour(object->findColour(PlugDataColour::ioletAreaColourId)); + if (!isValid) { outlineColour = convertColour(object->isSelected() ? Colours::red.brighter(1.5) : Colours::red); } nvgDrawRoundedRect(nvg, b.getX() + 0.5f, b.getY() + 0.5f, b.getWidth() - 1.0f, b.getHeight() - 1.0f, backgroundColour, object->isSelected() ? selectedOutlineColour : outlineColour, Corners::objectCornerRadius); + if (ioletAreaColour.r != backgroundColour.r || + ioletAreaColour.g != backgroundColour.g || + ioletAreaColour.b != backgroundColour.b || + ioletAreaColour.a != backgroundColour.a) { + nvgFillColor(nvg, ioletAreaColour); + nvgBeginPath(nvg); + nvgRoundedRect(nvg, 0.5f, 0, getWidth() - 1.0f, 3.5f, Corners::objectCornerRadius); + nvgRoundedRect(nvg, 0.5f, getHeight() - 3.5f, getWidth() - 1.0f, 3.5f, Corners::objectCornerRadius); + nvgFill(nvg); + } + if(editor && editor->isVisible()) { imageRenderer.renderComponentFromImage(nvg, *editor, getImageScale()); From 15b74e772202e00fcf613b9a65bcf19c45a8f89e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 2 May 2024 14:31:23 +0200 Subject: [PATCH 0680/1030] Fix support for mutliple displays with different DPI --- Source/NVGSurface.cpp | 5 ++++- Source/NVGSurface.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 7903f5d282..3d6a4e4fcc 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -292,6 +292,7 @@ void NVGSurface::render() } #endif + bool scaleChanged = false; if(makeContextActive()) { float pixelScale = getRenderScale(); int scaledWidth = getWidth() * pixelScale; @@ -303,6 +304,8 @@ void NVGSurface::render() fbWidth = scaledWidth; fbHeight = scaledHeight; invalidArea = getLocalBounds(); + scaleChanged = !approximatelyEqual(lastScaleFactor, pixelScale); + lastScaleFactor = pixelScale; } if(!invalidArea.isEmpty()) { @@ -350,7 +353,7 @@ void NVGSurface::render() #endif } - if(hasCanvas && !isAttached()) + if(hasCanvas && (!isAttached() || scaleChanged)) { setVisible(true); setInterceptsMouseClicks(false, false); diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 12413d1e4e..30cd6cc415 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -147,6 +147,7 @@ public Component, public Timer NVGframebuffer* mainFBO = nullptr; NVGframebuffer* invalidFBO = nullptr; int fbWidth = 0, fbHeight = 0; + float lastScaleFactor = 1.0f; bool hresize = false; bool resizing = false; From 38bdda19fdaa01c2aa80f66f984295143697bd99 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 3 May 2024 12:28:20 +0200 Subject: [PATCH 0681/1030] Fixed threading issues --- Source/PluginProcessor.cpp | 83 ++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 613c0fa40a..4d55d7c512 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1522,21 +1522,26 @@ void PluginProcessor::receiveSysMessage(String const& selector, std::vectoropenInPluginMode = true; - auto editors = getEditors(); - if(editors.size()) { - for(auto* canvas : editors[0]->canvases) - { - if(patches[0] == canvas->patch) + MessageManager::callAsync([this](){ + auto editors = getEditors(); + if(editors.size()) { + for(auto* canvas : editors[0]->canvases) { - editors[0]->enablePluginMode(canvas); + if(patches[0] == canvas->patch) + { + editors[0]->enablePluginMode(canvas); + } } } - } + }); + } else { - for (auto* editor : getEditors()) { - editor->enablePluginMode(editor->getCurrentCanvas()); - } + MessageManager::callAsync([this](){ + for (auto* editor : getEditors()) { + editor->enablePluginMode(editor->getCurrentCanvas()); + } + }); } }); break; @@ -1653,34 +1658,40 @@ void PluginProcessor::showTextEditor(unsigned long ptr, Rectangle bounds, S void PluginProcessor::handleAsyncUpdate() { + for (auto& editor : getEditors()) { + editor->statusbar->setLatencyDisplay(customLatencySamples); + } + setLatencySamples(customLatencySamples); } // set custom plugin latency void PluginProcessor::performLatencyCompensationChange(float value) { - customLatencySamples = floor(value); - for (auto& editor : getEditors()) { - editor->statusbar->setLatencyDisplay(customLatencySamples); + if(!approximatelyEqual(customLatencySamples, value)) + { + customLatencySamples = value; + triggerAsyncUpdate(); } - triggerAsyncUpdate(); } void PluginProcessor::setParameterRange(String const& name, float min, float max) { - for (auto* p : getParameters()) { - auto* param = dynamic_cast(p); - if (param->isEnabled() && param->getTitle() == name) { - max = std::max(max, min + 0.000001f); - param->setRange(min, max); - break; - } - } - - for(auto* editor : getEditors()) - { - editor->sidebar->updateAutomationParameters(); + for (auto* p : getParameters()) { + auto* param = dynamic_cast(p); + if (param->isEnabled() && param->getTitle() == name) { + max = std::max(max, min + 0.000001f); + param->setRange(min, max); + break; + } } + + MessageManager::callAsync([this](){ + for(auto* editor : getEditors()) + { + editor->sidebar->updateAutomationParameters(); + } + }); } void PluginProcessor::setParameterMode(String const& name, int mode) @@ -1693,10 +1704,12 @@ void PluginProcessor::setParameterMode(String const& name, int mode) } } - for(auto* editor : getEditors()) - { - editor->sidebar->updateAutomationParameters(); - } + MessageManager::callAsync([this](){ + for(auto* editor : getEditors()) + { + editor->sidebar->updateAutomationParameters(); + } + }); } void PluginProcessor::enableAudioParameter(String const& name) @@ -1721,10 +1734,12 @@ void PluginProcessor::enableAudioParameter(String const& name) } } - for(auto* editor : getEditors()) - { - editor->sidebar->updateAutomationParameters(); - } + MessageManager::callAsync([this](){ + for(auto* editor : getEditors()) + { + editor->sidebar->updateAutomationParameters(); + } + }); } void PluginProcessor::performParameterChange(int type, String const& name, float value) From be84d2fdbb05473c90b691d8ca0d11e1b0344e78 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 3 May 2024 14:01:07 +0200 Subject: [PATCH 0682/1030] Fixed pluginmode issues --- Source/Object.cpp | 2 +- Source/PluginMode.h | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index f264c18ac8..61d84401d2 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -752,7 +752,7 @@ void Object::mouseDown(MouseEvent const& e) { // Only show right-click menu in locked mode if the object can be opened // We don't allow alt+click for popupmenus here, as that will conflict with some object behaviour, like for [range.hsl] - if (e.mods.isRightButtonDown() && !cnv->editor->pluginMode && !cnv->isGraph) { + if (e.mods.isRightButtonDown() && !cnv->isGraph) { PopupMenu::dismissAllActiveMenus(); if (!getValue(locked)) { if(!e.mods.isAnyModifierKeyDown()) cnv->deselectAll(); diff --git a/Source/PluginMode.h b/Source/PluginMode.h index 0f426cbbe1..296866f43c 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -154,9 +154,15 @@ class PluginMode : public Component, public NVGComponent { nvgSave(nvg); nvgScale(nvg, pluginModeScale, pluginModeScale); - nvgTranslate(nvg, cnv->getX(), cnv->getY() - (isWindowFullscreen() ? 0 : 40)); + nvgTranslate(nvg, cnv->getX(), cnv->getY() - ((isWindowFullscreen() ? 0 : 40) / pluginModeScale)); - cnv->performRender(nvg, getLocalBounds().translated(cnv->canvasOrigin.x, cnv->canvasOrigin.y)); + auto bounds = getLocalBounds(); + bounds /= pluginModeScale; + bounds = bounds.translated(cnv->canvasOrigin.x, cnv->canvasOrigin.y); + + cnv->performRender(nvg, bounds); + + nvgRestore(nvg); } @@ -287,7 +293,7 @@ class PluginMode : public Component, public NVGComponent { auto b = getLocalBounds() + cnv->canvasOrigin; cnv->setTransform(cnv->getTransform().scale(scale)); - cnv->setBounds(-b.getX(), -b.getY() + 40, b.getWidth() + b.getX(), b.getHeight() + b.getY()); + cnv->setBounds(-b.getX(), -b.getY() + (titlebarHeight / scale), (b.getWidth() / scale) + b.getX(), (b.getHeight() / scale) + b.getY()); } repaint(); } From 291ac6e32572580dfd8ce68516e29ecc002e795f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 3 May 2024 14:15:29 +0200 Subject: [PATCH 0683/1030] Fixed [pad] object issue in pluginmode --- Source/Objects/MousePadObject.h | 2 +- Source/PluginEditor.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Objects/MousePadObject.h b/Source/Objects/MousePadObject.h index 805553c88b..9a76466544 100644 --- a/Source/Objects/MousePadObject.h +++ b/Source/Objects/MousePadObject.h @@ -169,7 +169,7 @@ class MousePadObject final : public ObjectBase { topLevel = nextCanvas; } - return static_cast(topLevel->locked.getValue() || topLevel->commandLocked.getValue()); + return static_cast(topLevel->locked.getValue() || topLevel->commandLocked.getValue()) || topLevel->isGraph; } void receiveObjectMessage(hash32 symbol, pd::Atom const atoms[8], int numAtoms) override diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 12847c4ae3..e05ed346c3 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1869,7 +1869,7 @@ void PluginEditor::enablePluginMode(Canvas* cnv) // Since objects like "keyname" need to be able to respond to any key as well, // it would be annoying to hear the bloop sound for every key that isn't a valid command bool PluginEditor::keyPressed(KeyPress const& key) -{ +{ // Claim tab keys on canvas to prevent cycling selection // The user might want to catch the tab key with an object, this behaviour just gets in the way // We do still want to allow tab cycling on other components, so if canvas doesn't have focus, don't grab the tab key From b3ab3a637c6a7e3304f9dc1bc03b0405faab78a2 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 3 May 2024 15:34:50 +0200 Subject: [PATCH 0684/1030] Don't claim space key in plugin mode --- Source/PluginEditor.cpp | 9 ++++++++- Source/PluginEditor.h | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index e05ed346c3..b456597509 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1229,7 +1229,7 @@ void PluginEditor::getCommandInfo(CommandID const commandID, ApplicationCommandI case CommandIDs::PanDragKey: { result.setInfo("Pan drag key", "Pan drag key", "View", 0); result.addDefaultKeypress(KeyPress::spaceKey, ModifierKeys::noModifiers); - result.setActive(hasCanvas && !isDragging); + result.setActive(hasCanvas && !isDragging && !pluginMode); break; } case CommandIDs::ZoomIn: { @@ -1870,12 +1870,19 @@ void PluginEditor::enablePluginMode(Canvas* cnv) // it would be annoying to hear the bloop sound for every key that isn't a valid command bool PluginEditor::keyPressed(KeyPress const& key) { + //if(pluginMode) return false; // Claim tab keys on canvas to prevent cycling selection // The user might want to catch the tab key with an object, this behaviour just gets in the way // We do still want to allow tab cycling on other components, so if canvas doesn't have focus, don't grab the tab key return getCurrentCanvas()->hasKeyboardFocus(true) || key.getKeyCode() != KeyPress::tabKey; } +void PluginEditor::parentHierarchyChanged() +{ + if(isShowing() || isOnDesktop()) + grabKeyboardFocus(); +} + void PluginEditor::commandKeyChanged(bool isHeld) { if (isHeld) { diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index b8eb785d81..5c9a0070e1 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -87,6 +87,7 @@ class PluginEditor : public AudioProcessorEditor void resized() override; void parentSizeChanged() override; + void parentHierarchyChanged() override; // For dragging parent window void mouseDrag(MouseEvent const& e) override; From 3b21de975e08e55e3e9b526b36df98c53cf894a9 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 3 May 2024 16:06:25 +0200 Subject: [PATCH 0685/1030] Slightly improve performance when reopening plugin window --- Source/Components/SuggestionComponent.h | 2 +- Source/PluginEditor.cpp | 1 - Source/Utility/MidiDeviceManager.h | 2 +- Source/Utility/ValueTreeViewer.h | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index 65cb00682d..e15a5086d4 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -564,7 +564,7 @@ class SuggestionComponent : public Component { } - int compareElements(String const& a, String const& b) + int compareElements(String const& a, String const& b) const { // Check if suggestion exacly matches query if (a == query) { diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index b456597509..a7975495a8 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1870,7 +1870,6 @@ void PluginEditor::enablePluginMode(Canvas* cnv) // it would be annoying to hear the bloop sound for every key that isn't a valid command bool PluginEditor::keyPressed(KeyPress const& key) { - //if(pluginMode) return false; // Claim tab keys on canvas to prevent cycling selection // The user might want to catch the tab key with an object, this behaviour just gets in the way // We do still want to allow tab cycling on other components, so if canvas doesn't have focus, don't grab the tab key diff --git a/Source/Utility/MidiDeviceManager.h b/Source/Utility/MidiDeviceManager.h index f91f5e7351..5a5209e6d0 100644 --- a/Source/Utility/MidiDeviceManager.h +++ b/Source/Utility/MidiDeviceManager.h @@ -214,7 +214,7 @@ class MidiDeviceManager : public ChangeListener , isInput(in) { } - int compareElements(MidiDeviceInfo const& dev1, MidiDeviceInfo const& dev2) + int compareElements(MidiDeviceInfo const& dev1, MidiDeviceInfo const& dev2) const { auto id1 = dev1.identifier; auto id2 = dev2.identifier; diff --git a/Source/Utility/ValueTreeViewer.h b/Source/Utility/ValueTreeViewer.h index aa21b7c795..294a579431 100644 --- a/Source/Utility/ValueTreeViewer.h +++ b/Source/Utility/ValueTreeViewer.h @@ -34,7 +34,7 @@ class ValueTreeNodeComponent : public Component for (int i = 0; i < valueTreeNode.getNumChildren(); ++i) { auto* childComponent = nodes.add(new ValueTreeNodeComponent(valueTreeNode.getChild(i), this, prepend)); - addAndMakeVisible(childComponent); + addChildComponent(childComponent); } } @@ -575,7 +575,7 @@ class ValueTreeViewerComponent : public Component, public KeyListener, public Se struct { bool sortDirection = false; - int compareElements (const ValueTreeNodeComponent* a, const ValueTreeNodeComponent* b) + int compareElements (const ValueTreeNodeComponent* a, const ValueTreeNodeComponent* b) const { auto firstIdx = a->valueTreeNode. getParent().indexOf(a->valueTreeNode); auto secondIdx = b->valueTreeNode.getParent().indexOf (b->valueTreeNode); From 4dad0e43d8c6fd68d1ba3a75e8c067c3ac268a1b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 3 May 2024 16:49:01 +0200 Subject: [PATCH 0686/1030] Fixed tab not becoming active when loading a patch --- Source/PluginProcessor.cpp | 1 - Source/PluginProcessor.h | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 4d55d7c512..c2e2e0d158 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1263,7 +1263,6 @@ pd::Patch::Ptr PluginProcessor::loadPatch(URL const& patchURL, PluginEditor* edi // First, check if patch is already opened for (auto const& patch : patches) { if (patch->getCurrentFile() == patchFile) { - MessageManager::callAsync([this, patch]() mutable { for (auto* editor : getEditors()) { for (auto* cnv : editor->canvases) { diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index ccc57a728d..1ca1a92786 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -126,8 +126,8 @@ class PluginProcessor : public AudioProcessor, public AsyncUpdater void parseDataBuffer(XmlElement const& xml) override; std::unique_ptr extraData; - pd::Patch::Ptr loadPatch(String patch, PluginEditor* editor, int splitIndex = 0); - pd::Patch::Ptr loadPatch(URL const& patchURL, PluginEditor* editor, int splitIndex = 0); + pd::Patch::Ptr loadPatch(String patch, PluginEditor* editor, int splitIndex = -1); + pd::Patch::Ptr loadPatch(URL const& patchURL, PluginEditor* editor, int splitIndex = -1); void titleChanged() override; From d998773fdae332f4e4cb8c5488767b228056f6ae Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 4 May 2024 14:45:11 +0200 Subject: [PATCH 0687/1030] Transparent array background --- Source/Objects/ArrayObject.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index bd3eba8390..65f20e27ab 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -1219,7 +1219,7 @@ class ArrayObject final : public ObjectBase { void render(NVGcontext* nvg) override { auto b = getLocalBounds().toFloat().reduced(0.5f); - auto backgroundColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectBackgroundColourId)); + auto backgroundColour = nvgRGBA(0, 0, 0, 0); auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); @@ -1236,6 +1236,8 @@ class ArrayObject final : public ObjectBase { } } + bool isTransparent() override { return true; }; + void updateGraphs() { pd->lockAudioThread(); From e5999677cbca4f64416279bef867c9f67bbf0183 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 4 May 2024 16:21:49 +0200 Subject: [PATCH 0688/1030] Fixed potential threading problems with daw_storage and other things --- Source/Pd/Instance.cpp | 4 +++- Source/PluginProcessor.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 3e3809e9aa..8e49626ba8 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -647,11 +647,13 @@ void Instance::sendDirectMessage(void* object, float const msg) void Instance::sendMessagesFromQueue() { libpd_set_instance(static_cast(instance)); - + + sys_lock(); std::function callback; while (functionQueue.try_dequeue(callback)) { callback(); } + sys_unlock(); } String Instance::getExtraInfo(File const& toOpen) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index c2e2e0d158..184daacfd8 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -141,8 +141,6 @@ PluginProcessor::PluginProcessor() atoms_playhead.reserve(3); atoms_playhead.resize(1); - sendMessagesFromQueue(); - auto themeName = settingsFile->getProperty("theme"); // Make sure theme exists @@ -173,6 +171,8 @@ PluginProcessor::PluginProcessor() setLatencySamples(pd::Instance::getBlockSize()); settingsFile->startChangeListener(); + + sendMessagesFromQueue(); } PluginProcessor::~PluginProcessor() From 9a8e6a8b9fdb125c3da5ccae51c9ff3cc4913629 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 4 May 2024 17:12:12 +0200 Subject: [PATCH 0689/1030] Fixed potential crash when reopening plugin window --- Source/NVGSurface.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 3d6a4e4fcc..91557a4364 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -97,12 +97,14 @@ NVGSurface::NVGSurface(PluginEditor* e) : editor(e) // Start rendering asynchronously, so we are sure the window has been added to the desktop // kind of a hack, but works well enough - MessageManager::callAsync([this](){ - // Render on vblank - vBlankAttachment = std::make_unique(this, [this](){ - editor->pd->messageDispatcher->dequeueMessages(); - render(); - }); + MessageManager::callAsync([_this = SafePointer(this)](){ + if(_this) { + // Render on vblank + _this->vBlankAttachment = std::make_unique(_this.getComponent(), [_this](){ + _this->editor->pd->messageDispatcher->dequeueMessages(); + _this->render(); + }); + } }); } From 301f758311436159db2490f5845ab9b802dfcf0b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 4 May 2024 17:27:59 +0200 Subject: [PATCH 0690/1030] Fixed plugin crash on macOS --- Source/NVGSurface.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 91557a4364..8e57accfd4 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -101,8 +101,10 @@ NVGSurface::NVGSurface(PluginEditor* e) : editor(e) if(_this) { // Render on vblank _this->vBlankAttachment = std::make_unique(_this.getComponent(), [_this](){ - _this->editor->pd->messageDispatcher->dequeueMessages(); - _this->render(); + if(_this) { + _this->editor->pd->messageDispatcher->dequeueMessages(); + _this->render(); + } }); } }); @@ -278,7 +280,7 @@ void NVGSurface::render() } #if NANOVG_METAL_IMPLEMENTATION - auto contextInvalidated = metalActivityChecker.checkIfWindowActivityChanged(); + auto contextInvalidated = ProjectInfo::isStandalone && metalActivityChecker.checkIfWindowActivityChanged(); if(contextInvalidated) { sendContextDeleteMessage(); if(invalidFBO) nvgDeleteFramebuffer(invalidFBO); @@ -367,6 +369,7 @@ void NVGSurface::render() if(nvg) nvgDeleteContext(nvg); invalidFBO = nullptr; mainFBO = nullptr; + nvg = nullptr; #ifdef NANOVG_METAL_IMPLEMENTATION auto* peer = getPeer()->getNativeHandle(); From bd6d93e78118b29ed9bcb2d902d6823fdd3f5d32 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 4 May 2024 19:30:36 +0200 Subject: [PATCH 0691/1030] No longer need to disable transparency in Logic --- Source/Utility/Config.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Source/Utility/Config.cpp b/Source/Utility/Config.cpp index 001225c840..60f3c564cd 100644 --- a/Source/Utility/Config.cpp +++ b/Source/Utility/Config.cpp @@ -77,15 +77,6 @@ bool ProjectInfo::canUseSemiTransparentWindows() #if !JUCE_MAC || PLUGDATA_STANDALONE return Desktop::canUseSemiTransparentWindows(); #else - - // Apple's plugin hosts will show an ugly pink edge below transparent edges - // This is a "security feature" and will probably not be removed anytime soon - - auto hostType = PluginHostType(); - if (hostType.isLogic() || hostType.isGarageBand() || hostType.isMainStage()) { - return false; - } - return Desktop::canUseSemiTransparentWindows(); #endif } From 3e1724f941756607be009206395dea98d58203a4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 4 May 2024 21:14:15 +0200 Subject: [PATCH 0692/1030] Allow arguments after Gem object name --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index a75dd3260f..0b9eb944ad 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit a75dd3260ff7315fa467e79e389321e39476e9e4 +Subproject commit 0b9eb944add217e352ecda93a1da57d25350dac4 From 14d59a5db684fe221240e9e9714e23c27da07b82 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 4 May 2024 21:43:54 +0200 Subject: [PATCH 0693/1030] Fixed Gem compilation for MSVC --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 0b9eb944ad..63dd1c6ffd 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 0b9eb944add217e352ecda93a1da57d25350dac4 +Subproject commit 63dd1c6ffd69dba33bcfec7a8774a39e51fc6897 From 420f605743c85baf31f0243e825ea32e33a255f6 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sun, 5 May 2024 18:52:56 +0930 Subject: [PATCH 0694/1030] fix python script to deal with filenames >260 chars & trigger build error if python copy has failed --- CMakeLists.txt | 12 ++++++++++-- Resources/Scripts/package_resources.py | 4 ++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5280d4a35b..3bb3847699 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,10 +94,18 @@ else() endif() message(STATUS "Preparing documentation") -execute_process(COMMAND python3 parse_documentation.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Scripts) +execute_process(COMMAND python3 parse_documentation.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Scripts RESULT_VARIABLE PREPARE_DOCUMENTATION_RESULT) + +if(NOT PREPARE_DOCUMENTATION_RESULT EQUAL 0) + message(FATAL_ERROR "Preparing documentation failed with error code ${PREPARE_DOCUMENTATION_RESULT}") +endif() message(STATUS "Packaging resources") -execute_process(COMMAND python3 package_resources.py ${ENABLE_GEM} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Scripts) +execute_process(COMMAND python3 package_resources.py ${ENABLE_GEM} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Scripts RESULT_VARIABLE PACKAGE_RESOURCES_RESULT) + +if(NOT PACKAGE_RESOURCES_RESULT EQUAL 0) + message(FATAL_ERROR "Resource packaging failed with error code ${PACKAGE_RESOURCES_RESULT}") +endif() set(PLUGDATA_VERSION "0.9.0") set(PLUGDATA_COMPANY_NAME "plugdata") diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index 3d6a7680aa..c58e5106dc 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -25,6 +25,10 @@ def moveFile(src, dst): shutil.copy(src, dst) def copyDir(src, dst): + # Prepend paths with \\?\ to deal with file names >260 chars in ELSE and DOS + if (platform.system().lower() == "windows"): + src = '\\\\?\\' + os.path.abspath(src) + dst = '\\\\?\\' + os.path.abspath(dst) shutil.copytree(src, dst) def globCopy(srcs, dst): From fe164040760a6cad956a296e774a6ad54a2a65e5 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 5 May 2024 12:39:39 +0200 Subject: [PATCH 0695/1030] Fixed crash when opening empty array --- Source/Objects/ArrayObject.h | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index 65f20e27ab..f4ce5aaf05 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -995,7 +995,9 @@ class ArrayEditorDialog : public Component { auto* list = lists.add(new ArrayListView(pd, arr)); addChildComponent(list); } - graphs[0]->setVisible(true); + if(graphs.size()) { + graphs[0]->setVisible(true); + } for(int i = 0; i < graphs.size(); i++) { @@ -1390,11 +1392,17 @@ class ArrayObject final : public ObjectBase { dialog->toFront(true); return; } - - dialog = std::make_unique(cnv->pd, getArrays(), object); - dialog->onClose = [this]() { - dialog.reset(nullptr); - }; + + auto arrays = getArrays(); + if(arrays.size()) { + dialog = std::make_unique(cnv->pd, arrays, object); + dialog->onClose = [this]() { + dialog.reset(nullptr); + }; + } + else { + pd->logWarning("Can't open: contains no arrays"); + } } void receiveObjectMessage(hash32 symbol, const pd::Atom atoms[8], int numAtoms) override From dad064dce566ae98af738141641170477b22e652 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 5 May 2024 13:16:48 +0200 Subject: [PATCH 0696/1030] Fixed crash in FL Studio when enabling aux channels --- Source/PluginProcessor.cpp | 11 +++++++++++ Source/PluginProcessor.h | 1 + 2 files changed, 12 insertions(+) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 184daacfd8..e673af7be5 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -451,6 +451,17 @@ void PluginProcessor::setProtectedMode(bool enabled) protectedMode = enabled; } + +void PluginProcessor::numChannelsChanged() +{ + auto blockSize = AudioProcessor::getBlockSize(); + auto sampleRate = AudioProcessor::getSampleRate(); + + suspendProcessing(true); + prepareToPlay(sampleRate, blockSize); + suspendProcessing(false); +} + void PluginProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) { float oversampleFactor = 1 << oversampling; diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 1ca1a92786..db92fff87c 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -39,6 +39,7 @@ class PluginProcessor : public AudioProcessor, public AsyncUpdater void setOversampling(int amount); void setProtectedMode(bool enabled); void prepareToPlay(double sampleRate, int samplesPerBlock) override; + void numChannelsChanged() override; void releaseResources() override; void updateAllEditorsLNF(); From ed3b4b968f27febae2fdf11f6a59155284c08c73 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 6 May 2024 15:21:00 +0200 Subject: [PATCH 0697/1030] Small object fixes --- Source/Object.cpp | 1 + Source/Objects/GraphOnParent.h | 2 +- Source/Objects/ObjectBase.cpp | 4 +++- Source/Objects/ObjectBase.h | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index 61d84401d2..b29d515c6d 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -506,6 +506,7 @@ void Object::triggerOverlayActiveState() void Object::lookAndFeelChanged() { activityOverlayDirty = true; + if(gui) gui->updateLabel(); } void Object::resized() diff --git a/Source/Objects/GraphOnParent.h b/Source/Objects/GraphOnParent.h index 4dfdbd3d01..510fca10cb 100644 --- a/Source/Objects/GraphOnParent.h +++ b/Source/Objects/GraphOnParent.h @@ -238,7 +238,7 @@ class GraphOnParent final : public ObjectBase { nvgSave(nvg); nvgIntersectRoundedScissor(nvg, b.getX() + 0.75f, b.getY() + 0.75f, b.getWidth() - 1.5f, b.getHeight() - 1.5f, Corners::objectCornerRadius); nvgTranslate(nvg, canvas->getX(), canvas->getY()); - canvas->performRender(nvg, invalidArea); // TODO: more precise invalidation? + canvas->performRender(nvg, invalidArea); nvgRestore(nvg); } diff --git a/Source/Objects/ObjectBase.cpp b/Source/Objects/ObjectBase.cpp index 2c71320fa8..7ad7b417c8 100644 --- a/Source/Objects/ObjectBase.cpp +++ b/Source/Objects/ObjectBase.cpp @@ -233,9 +233,11 @@ String ObjectBase::getText() String ObjectBase::getTypeWithOriginPrefix() const { if (auto obj = ptr.get()) { - auto origin = pd::Library::getObjectOrigin(obj.get()); auto type = getType(); + if(type.contains("/")) return type; + auto origin = pd::Library::getObjectOrigin(obj.get()); + if(origin.isEmpty()) return type; return origin + "/" + type; diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index 9552c9b4be..fb40eb553d 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -46,7 +46,7 @@ class ObjectLabel : public Label, public NVGComponent, public NVGContextListener { surface.removeNVGContextListener(this); } - + void nvgContextDeleted(NVGcontext* nvg) { if(imageId) nvgDeleteImage(nvg, imageId); imageId = 0; From 63ebbafb95949751d66dd936c2209126686e8863 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 6 May 2024 15:21:15 +0200 Subject: [PATCH 0698/1030] Search full object text in search panel --- Source/Sidebar/SearchPanel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index f73e43535d..9ecf821608 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -239,7 +239,7 @@ class SearchPanel : public Component, public KeyListener, public Timer element.setProperty("Object", reinterpret_cast(object.cast()), nullptr); element.setProperty("TopLevel", reinterpret_cast(top), nullptr); } else { - element.setProperty("Name", name.upToFirstOccurrenceOf(" ", false, false), nullptr); + element.setProperty("Name", name, nullptr); element.setProperty("RightText", positionText, nullptr); element.setProperty("Icon", Icons::Object, nullptr); element.setProperty("Object", reinterpret_cast(object.cast()), nullptr); From 86fe4ec5e7417d99779136213e21019b1359ac60 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 6 May 2024 15:21:28 +0200 Subject: [PATCH 0699/1030] Removed unncessary metal rendering code --- Source/NVGSurface.cpp | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 8e57accfd4..8f9cfa08a3 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -278,24 +278,7 @@ void NVGSurface::render() break; } } - -#if NANOVG_METAL_IMPLEMENTATION - auto contextInvalidated = ProjectInfo::isStandalone && metalActivityChecker.checkIfWindowActivityChanged(); - if(contextInvalidated) { - sendContextDeleteMessage(); - if(invalidFBO) nvgDeleteFramebuffer(invalidFBO); - if(mainFBO) nvgDeleteFramebuffer(mainFBO); - if(nvg) nvgDeleteContext(nvg); - invalidFBO = nullptr; - mainFBO = nullptr; - nvg = nullptr; - detachContext(); - invalidateAll(); - return; - } -#endif - bool scaleChanged = false; if(makeContextActive()) { float pixelScale = getRenderScale(); @@ -303,6 +286,8 @@ void NVGSurface::render() int scaledHeight = getHeight() * pixelScale; if(fbWidth != scaledWidth || fbHeight != scaledHeight || !mainFBO) { + if(invalidFBO) nvgDeleteFramebuffer(invalidFBO); + if(mainFBO) nvgDeleteFramebuffer(mainFBO); mainFBO = nvgCreateFramebuffer(nvg, scaledWidth, scaledHeight, NVG_IMAGE_PREMULTIPLIED); invalidFBO = nvgCreateFramebuffer(nvg, scaledWidth, scaledHeight, NVG_IMAGE_PREMULTIPLIED); fbWidth = scaledWidth; @@ -311,8 +296,7 @@ void NVGSurface::render() scaleChanged = !approximatelyEqual(lastScaleFactor, pixelScale); lastScaleFactor = pixelScale; } - - if(!invalidArea.isEmpty()) { + else if(!invalidArea.isEmpty()) { auto invalidated = invalidArea.expanded(1); // First, draw only the invalidated region to a separate framebuffer From 420de3f397fbb60a25afdbda647b3e203ab78342 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 6 May 2024 15:21:38 +0200 Subject: [PATCH 0700/1030] Updated pd --- Libraries/pure-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/pure-data b/Libraries/pure-data index 633ba75f2c..a68c0ccfea 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit 633ba75f2c123691e5acb9fe703e1dac167a18dc +Subproject commit a68c0ccfea4c93dedca85c93d2812e75ef86a1b8 From cb87fdfab21bd28df2b9df4562b5c2c1bd287407 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 6 May 2024 15:21:55 +0200 Subject: [PATCH 0701/1030] Make sure object reference files don't get mixed up --- Source/Pd/Library.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Source/Pd/Library.cpp b/Source/Pd/Library.cpp index 5a5c3eae5f..d7d46dbece 100644 --- a/Source/Pd/Library.cpp +++ b/Source/Pd/Library.cpp @@ -111,16 +111,21 @@ Library::Library(pd::Instance* instance) } } + auto name = child.getProperty("name").toString(); if(origin.isEmpty()) { - documentationIndex[hash(child.getProperty("name").toString())] = child; + documentationIndex[hash(name)] = child; } else if(origin == "Gem") { - documentationIndex[hash(origin + "/" + child.getProperty("name").toString())] = child; + documentationIndex[hash(origin + "/" + name)] = child; + } + else if(documentationIndex.count(hash(name))) + { + documentationIndex[hash(origin + "/" + name)] = child; } else { - documentationIndex[hash(child.getProperty("name").toString())] = child; - documentationIndex[hash(origin + "/" + child.getProperty("name").toString())] = child; + documentationIndex[hash(name)] = child; + documentationIndex[hash(origin + "/" + name)] = child; } } From 3f664fa1a939c9ee411a5001b8d3685607ef5ac8 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 6 May 2024 17:21:27 +0200 Subject: [PATCH 0702/1030] Increment version suffix --- Source/Utility/Config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index c6b0633efa..dd27b6b4de 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -52,7 +52,7 @@ struct ProjectInfo { #else static inline File const appDataDir = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory).getChildFile("plugdata"); #endif - static inline String const versionSuffix = "-test5"; + static inline String const versionSuffix = "-test6"; static inline File const versionDataDir = appDataDir.getChildFile("Versions").getChildFile(ProjectInfo::versionString + versionSuffix); }; From 756d379e72ae981910e6a953326610ba7a7d3f86 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Tue, 7 May 2024 19:32:40 +0930 Subject: [PATCH 0703/1030] initial work to allow search to display updated object text (te_binbuf is not updated as patch changes) --- Source/Sidebar/SearchPanel.h | 47 +++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index 9ecf821608..f152ce7a23 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -208,29 +208,58 @@ class SearchPanel : public Component, public KeyListener, public Timer ValueTree generatePatchTree(pd::Patch::Ptr patch, void* topLevel = nullptr) { + auto patchString = patch->getCanvasContent(); + StringArray patchStringArray; + patchStringArray.addTokens(patchString, ";", ""); + ValueTree patchTree("Patch"); + int objectIndex = 0; for (auto objectPtr : patch->getObjects()) { + objectIndex++; if (auto object = objectPtr.get()) { auto* top = topLevel ? topLevel : object.get(); - String type = pd::Interface::getObjectClassName(object.get()); + auto type = pd::Interface::getObjectClassName(object.get()); - if (!pd::Interface::checkObject(object.get())) + if (!pd::Interface::checkObject(object.get())) { continue; - - char* objectText; - int len; - pd::Interface::getObjectText(object.cast(), &objectText, &len); + } int x, y, w, h; pd::Interface::getObjectBounds(patch->getPointer().get(), object.cast(), &x, &y, &w, &h); - auto name = String::fromUTF8(objectText, len); - auto positionText = " (" + String(x) + ":" + String(y) + ")"; - ValueTree element("Object"); + + auto invertType = [](String type){ + switch(hash(type)){ + case hash("vsl"): + return "hsl"; + case hash("hsl"): + return "vsl"; + case hash("hradio"): + return "vradio"; + case hash("vradio"): + return "hradio"; + default: + return "unknown type"; + } + }; + + auto const& rawName = patchStringArray[objectIndex]; + auto name = rawName.fromFirstOccurrenceOf(type, true, true); + // vsl / hsl & vraido / hradio can both describe the same object. + // If the formated name string is empty here, check it's other inverted type + if (name.isEmpty()) + name = rawName.fromFirstOccurrenceOf(invertType(type), true, true); + + auto const& positionText = " (" + String(x) + ":" + String(y) + ")"; + if (type == "canvas" || type == "graph") { pd::Patch::Ptr subpatch = new pd::Patch(objectPtr, editor->pd, false); ValueTree subpatchTree = generatePatchTree(subpatch, top); + + // canvas or graph has coords & restore objects, so we have to skip over them on the root patch + objectIndex += (subpatchTree.getNumChildren() + 2); + element.copyPropertiesAndChildrenFrom(subpatchTree, nullptr); element.setProperty("Name", name, nullptr); From 30f7774a04f4346da8765c01c9d4a72d4d62a4c5 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 7 May 2024 12:39:13 +0200 Subject: [PATCH 0704/1030] Fixed search panel bug --- Source/Sidebar/SearchPanel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index f152ce7a23..7de9eeb7c7 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -218,7 +218,7 @@ class SearchPanel : public Component, public KeyListener, public Timer objectIndex++; if (auto object = objectPtr.get()) { auto* top = topLevel ? topLevel : object.get(); - auto type = pd::Interface::getObjectClassName(object.get()); + auto type = String::fromUTF8(pd::Interface::getObjectClassName(object.get())); if (!pd::Interface::checkObject(object.get())) { continue; From b275b2c7f2d80597efc7a16026754148439b19ac Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 8 May 2024 01:24:28 +0930 Subject: [PATCH 0705/1030] simpler fix for showing search result arguments --- Source/Sidebar/SearchPanel.h | 83 +++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index 7de9eeb7c7..929e56dd61 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -208,58 +208,29 @@ class SearchPanel : public Component, public KeyListener, public Timer ValueTree generatePatchTree(pd::Patch::Ptr patch, void* topLevel = nullptr) { - auto patchString = patch->getCanvasContent(); - StringArray patchStringArray; - patchStringArray.addTokens(patchString, ";", ""); - ValueTree patchTree("Patch"); - int objectIndex = 0; for (auto objectPtr : patch->getObjects()) { - objectIndex++; if (auto object = objectPtr.get()) { auto* top = topLevel ? topLevel : object.get(); - auto type = String::fromUTF8(pd::Interface::getObjectClassName(object.get())); + String type = String::fromUTF8(pd::Interface::getObjectClassName(object.get())); - if (!pd::Interface::checkObject(object.get())) { + if (!pd::Interface::checkObject(object.get())) continue; - } + + char* objectText; + int len; + pd::Interface::getObjectText(object.cast(), &objectText, &len); int x, y, w, h; pd::Interface::getObjectBounds(patch->getPointer().get(), object.cast(), &x, &y, &w, &h); - ValueTree element("Object"); - - auto invertType = [](String type){ - switch(hash(type)){ - case hash("vsl"): - return "hsl"; - case hash("hsl"): - return "vsl"; - case hash("hradio"): - return "vradio"; - case hash("vradio"): - return "hradio"; - default: - return "unknown type"; - } - }; - - auto const& rawName = patchStringArray[objectIndex]; - auto name = rawName.fromFirstOccurrenceOf(type, true, true); - // vsl / hsl & vraido / hradio can both describe the same object. - // If the formated name string is empty here, check it's other inverted type - if (name.isEmpty()) - name = rawName.fromFirstOccurrenceOf(invertType(type), true, true); - - auto const& positionText = " (" + String(x) + ":" + String(y) + ")"; + auto name = String::fromUTF8(objectText, len); + auto positionText = " (" + String(x) + ":" + String(y) + ")"; + ValueTree element("Object"); if (type == "canvas" || type == "graph") { pd::Patch::Ptr subpatch = new pd::Patch(objectPtr, editor->pd, false); ValueTree subpatchTree = generatePatchTree(subpatch, top); - - // canvas or graph has coords & restore objects, so we have to skip over them on the root patch - objectIndex += (subpatchTree.getNumChildren() + 2); - element.copyPropertiesAndChildrenFrom(subpatchTree, nullptr); element.setProperty("Name", name, nullptr); @@ -268,7 +239,41 @@ class SearchPanel : public Component, public KeyListener, public Timer element.setProperty("Object", reinterpret_cast(object.cast()), nullptr); element.setProperty("TopLevel", reinterpret_cast(top), nullptr); } else { - element.setProperty("Name", name, nullptr); + auto includeNumArguments = [](const juce::String& mainString, int upto) + { + StringArray words; + words.addTokens(mainString, " ", ""); + + int textToInclude = upto + 1; + + String rtnString; + for (int i = 0; i < jmin(words.size(), textToInclude); i++) { + rtnString += words[i] + " "; + if (textToInclude == i) + break; + } + return rtnString; + }; + + auto nameOnly = name.upToFirstOccurrenceOf(" ", false, false); + + String finalFormatedName; + + switch(hash(nameOnly)){ + case hash("s"): + case hash("send"): + case hash("r"): + case hash("receive"): + finalFormatedName = includeNumArguments(name, 1); + break; + case hash("metro"): + finalFormatedName = includeNumArguments(name, 3); + break; + default: + finalFormatedName = nameOnly; + break; + } + element.setProperty("Name", finalFormatedName, nullptr); element.setProperty("RightText", positionText, nullptr); element.setProperty("Icon", Icons::Object, nullptr); element.setProperty("Object", reinterpret_cast(object.cast()), nullptr); From 9852e22b17d36eeae4e49b2b9c9b1c13c71fc84c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 7 May 2024 18:34:14 +0200 Subject: [PATCH 0706/1030] Fixed threading problem in autosave --- Source/Utility/Autosave.h | 47 ++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/Source/Utility/Autosave.h b/Source/Utility/Autosave.h index f9c81c6979..80a14320ac 100644 --- a/Source/Utility/Autosave.h +++ b/Source/Utility/Autosave.h @@ -88,36 +88,37 @@ class Autosave : public Timer void save() { - for (auto& patch : pd->patches) { - if (!patch->isDirty()) - continue; - - // Check if patch is a root canvas - bool isRootCanvas = false; - for (auto* x = pd_getcanvaslist(); x; x = x->gl_next) { - if (x == patch->getPointer().get()) { - isRootCanvas = true; - break; + const ScopedTryLock stl (pd->patches.getLock()); + if (stl.isLocked()) + { + for (auto& patch : pd->patches) { + auto* patchPtr = patch->getPointer().get(); + if (!patchPtr->gl_dirty) + continue; + + // Check if patch is a root canvas + for (auto* x = pd_getcanvaslist(); x; x = x->gl_next) { + if (x == patchPtr) { + + auto patchFile = patch->getPatchFile(); + + // Simple way to filter out plugdata default patches which we don't want to save. + if (!isInternalPatch(patchFile)) { + autoSaveQueue.enqueue({ patchFile.getFullPathName(), patch->getCanvasContent() }); + } + + triggerAsyncUpdate(); + break; + } } } - if (!isRootCanvas) - continue; - - auto patchFile = patch->getPatchFile(); - - // Simple way to filter out plugdata default patches which we don't want to save. - if (!isInternalPatch(patchFile)) { - autoSaveQueue.enqueue({ patchFile.getFullPathName(), patch->getCanvasContent() }); - } } - - triggerAsyncUpdate(); } bool isInternalPatch(File const& patch) { - auto const pathName = patch.getFullPathName(); - return pathName.contains("Documents/plugdata/Abstractions") || pathName.contains("Documents\\plugdata\\Abstractions") || pathName.contains("Documents/plugdata/Documentation") || pathName.contains("Documents\\plugdata\\Documentation") || pathName.contains("Documents/plugdata/Extra") || pathName.contains("Documents\\plugdata\\Extra") || patch.getParentDirectory() == File::getSpecialLocation(File::tempDirectory); + auto const pathName = patch.getFullPathName().replace("\\", "/"); + return pathName.contains("Documents/plugdata/Abstractions") || pathName.contains("Documents/plugdata/Documentation") || pathName.contains("Documents/plugdata/Extra") || patch.getParentDirectory() == File::getSpecialLocation(File::tempDirectory); } void handleAsyncUpdate() override From 87ae1ad20c62be9c6bc45871ca9220284b2ceee2 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 7 May 2024 18:34:34 +0200 Subject: [PATCH 0707/1030] Fixed more data races --- Libraries/nanovg | 2 +- Source/NVGSurface.cpp | 7 ++++--- Source/PluginProcessor.cpp | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Libraries/nanovg b/Libraries/nanovg index 3e913a7f23..735596c3b6 160000 --- a/Libraries/nanovg +++ b/Libraries/nanovg @@ -1 +1 @@ -Subproject commit 3e913a7f232701d818536b5a3036ca43285e783f +Subproject commit 735596c3b6671c79b0c932ea981482514ec4ffe5 diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 8f9cfa08a3..4e778022da 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -194,7 +194,7 @@ void NVGSurface::resized() #ifdef NANOVG_METAL_IMPLEMENTATION if(auto* view = getView()) { auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - auto renderScale = OSUtils::MTLGetPixelScale(view); + auto renderScale = OSUtils::MTLGetPixelScale(view); // TODO: we can simplify with getRenderScale() function, but needs testing on iOS auto* topLevel = getTopLevelComponent(); auto bounds = topLevel->getLocalArea(this, getLocalBounds()) * desktopScale; #if JUCE_IOS @@ -294,7 +294,6 @@ void NVGSurface::render() fbHeight = scaledHeight; invalidArea = getLocalBounds(); scaleChanged = !approximatelyEqual(lastScaleFactor, pixelScale); - lastScaleFactor = pixelScale; } else if(!invalidArea.isEmpty()) { auto invalidated = invalidArea.expanded(1); @@ -357,7 +356,7 @@ void NVGSurface::render() #ifdef NANOVG_METAL_IMPLEMENTATION auto* peer = getPeer()->getNativeHandle(); - auto renderScale = Desktop::getInstance().getGlobalScaleFactor() * OSUtils::MTLGetPixelScale(peer); + auto renderScale = getRenderScale(); auto* view = OSUtils::MTLCreateView(peer, 0, 0, getWidth(), getHeight()); setView(view); nvg = nvgCreateContext(view, NVG_ANTIALIAS | NVG_TRIPLE_BUFFER, getWidth() * renderScale, getHeight() * renderScale); @@ -370,6 +369,8 @@ void NVGSurface::render() glContext->initialiseOnThread(); nvg = nvgCreateContext(NVG_ANTIALIAS); #endif + lastScaleFactor = renderScale; + invalidateAll(); if (!nvg) std::cerr << "could not initialise nvg" << std::endl; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index e673af7be5..9cb58bf34b 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -464,6 +464,8 @@ void PluginProcessor::numChannelsChanged() void PluginProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) { + if(approximatelyEqual(sampleRate, 0.0)) return; + float oversampleFactor = 1 << oversampling; auto maxChannels = std::max(getTotalNumInputChannels(), getTotalNumOutputChannels()); From f2cdf96a3d6e316bff04158ad1aee59e04d9da4a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 7 May 2024 19:07:54 +0200 Subject: [PATCH 0708/1030] Search panel improvements --- Source/Sidebar/SearchPanel.h | 88 ++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index 929e56dd61..7a4275a8d8 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -225,6 +225,7 @@ class SearchPanel : public Component, public KeyListener, public Timer pd::Interface::getObjectBounds(patch->getPointer().get(), object.cast(), &x, &y, &w, &h); auto name = String::fromUTF8(objectText, len); + auto nameWithoutArgs = name.upToFirstOccurrenceOf(" ", false, false); auto positionText = " (" + String(x) + ":" + String(y) + ")"; ValueTree element("Object"); @@ -232,47 +233,78 @@ class SearchPanel : public Component, public KeyListener, public Timer pd::Patch::Ptr subpatch = new pd::Patch(objectPtr, editor->pd, false); ValueTree subpatchTree = generatePatchTree(subpatch, top); element.copyPropertiesAndChildrenFrom(subpatchTree, nullptr); - + + if(auto patchPtr = subpatch->getPointer()) + { + if(patchPtr->gl_list) + { + t_class* c = patchPtr->gl_list->g_pd; + if (c && c->c_name && (String::fromUTF8(c->c_name->s_name) == "array")) { + name = "array"; // TODO: add array name + } else if (patchPtr->gl_isgraph) { + name = nameWithoutArgs; + } + } + else if(patchPtr->gl_isgraph) + { + name = nameWithoutArgs; + } + } + element.setProperty("Name", name, nullptr); element.setProperty("RightText", positionText, nullptr); element.setProperty("Icon", canvas_isabstraction(subpatch->getPointer().get()) ? Icons::File : Icons::Object, nullptr); element.setProperty("Object", reinterpret_cast(object.cast()), nullptr); element.setProperty("TopLevel", reinterpret_cast(top), nullptr); } else { - auto includeNumArguments = [](const juce::String& mainString, int upto) - { - StringArray words; - words.addTokens(mainString, " ", ""); - - int textToInclude = upto + 1; - - String rtnString; - for (int i = 0; i < jmin(words.size(), textToInclude); i++) { - rtnString += words[i] + " "; - if (textToInclude == i) - break; - } - return rtnString; - }; - - auto nameOnly = name.upToFirstOccurrenceOf(" ", false, false); - String finalFormatedName; - switch(hash(nameOnly)){ - case hash("s"): - case hash("send"): - case hash("r"): - case hash("receive"): - finalFormatedName = includeNumArguments(name, 1); + switch(hash(type)){ + case hash("bng"): + case hash("button"): + case hash("hsl"): + case hash("vsl"): + case hash("slider"): + case hash("tgl"): + case hash("nbx"): + case hash("numbox~"): + case hash("vradio"): + case hash("hradio"): + case hash("cnv"): + case hash("vu"): + case hash("pad"): + case hash("keyboard"): + case hash("pic"): + case hash("gatom"): + case hash("canvas"): + case hash("scope~"): + case hash("function"): + case hash("bicoeff"): + case hash("messbox"): + case hash("note"): + case hash("knob"): + { + finalFormatedName = nameWithoutArgs; + break; + } + case hash("message"): + { + finalFormatedName = "msg " + name; break; - case hash("metro"): - finalFormatedName = includeNumArguments(name, 3); + } + case hash("comment"): + { + finalFormatedName = "comment " + name; break; + } default: - finalFormatedName = nameOnly; + { + finalFormatedName = name; break; + } + } + element.setProperty("Name", finalFormatedName, nullptr); element.setProperty("RightText", positionText, nullptr); element.setProperty("Icon", Icons::Object, nullptr); From 7e1357ce23751037f5fb26a4807ef50e96bd37f6 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 7 May 2024 20:28:14 +0200 Subject: [PATCH 0709/1030] Also show array name in search panel --- Source/Sidebar/SearchPanel.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index 7a4275a8d8..c536bcce64 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -6,6 +6,7 @@ #include "Object.h" #include "Objects/ObjectBase.h" +#include "Objects/AllGuis.h" #include #include @@ -240,7 +241,8 @@ class SearchPanel : public Component, public KeyListener, public Timer { t_class* c = patchPtr->gl_list->g_pd; if (c && c->c_name && (String::fromUTF8(c->c_name->s_name) == "array")) { - name = "array"; // TODO: add array name + auto* array = reinterpret_cast(patchPtr->gl_list); + name = "array " + String::fromUTF8(array->x_name->s_name); } else if (patchPtr->gl_isgraph) { name = nameWithoutArgs; } @@ -302,7 +304,6 @@ class SearchPanel : public Component, public KeyListener, public Timer finalFormatedName = name; break; } - } element.setProperty("Name", finalFormatedName, nullptr); From 589ca6b53738e696625241715547f9082f0515da Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 8 May 2024 00:00:03 +0200 Subject: [PATCH 0710/1030] Compilation fix --- Source/NVGSurface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 4e778022da..6a4e8a2130 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -354,9 +354,9 @@ void NVGSurface::render() mainFBO = nullptr; nvg = nullptr; + auto renderScale = getRenderScale(); #ifdef NANOVG_METAL_IMPLEMENTATION auto* peer = getPeer()->getNativeHandle(); - auto renderScale = getRenderScale(); auto* view = OSUtils::MTLCreateView(peer, 0, 0, getWidth(), getHeight()); setView(view); nvg = nvgCreateContext(view, NVG_ANTIALIAS | NVG_TRIPLE_BUFFER, getWidth() * renderScale, getHeight() * renderScale); From 20eab6be385a9683bfdb6773f54dad9858affac3 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 8 May 2024 00:58:32 +0200 Subject: [PATCH 0711/1030] Trying to fix rendering problems for older macs --- Libraries/nanovg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/nanovg b/Libraries/nanovg index 735596c3b6..7f3dca94bc 160000 --- a/Libraries/nanovg +++ b/Libraries/nanovg @@ -1 +1 @@ -Subproject commit 735596c3b6671c79b0c932ea981482514ec4ffe5 +Subproject commit 7f3dca94bcf4e74a50581b354b0f0ccf947a4dc0 From 72a18055dad5f8d598d3c71f09d1e4931719294f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 8 May 2024 01:00:23 +0200 Subject: [PATCH 0712/1030] Updated Metal shader --- Libraries/nanovg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/nanovg b/Libraries/nanovg index 7f3dca94bc..8b22d88f16 160000 --- a/Libraries/nanovg +++ b/Libraries/nanovg @@ -1 +1 @@ -Subproject commit 7f3dca94bcf4e74a50581b354b0f0ccf947a4dc0 +Subproject commit 8b22d88f160c56d5fd21cb4334c120b75817c452 From 3c8f841ab87058340a9feafc5530eaa2b66b56ab Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 8 May 2024 11:57:14 +0930 Subject: [PATCH 0713/1030] Fix situation where some objects (mainly message) when DnD onto canvas created a connection at same time. Stop mouseup on iolet from running when canvas has recieved a DnD --- Source/Canvas.cpp | 1 + Source/Canvas.h | 2 ++ Source/Iolet.cpp | 8 ++++++++ 3 files changed, 11 insertions(+) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index ef2a91a4c8..876fc1fa3f 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -1237,6 +1237,7 @@ void Canvas::dragAndDropPaste(String const& patchString, Point mousePos, in { locked = false; presentationMode = false; + objectAdded = true; // force the valueChanged to run, and wait for them to return locked.getValueSource().sendChangeMessage(true); diff --git a/Source/Canvas.h b/Source/Canvas.h index f981a8eac9..12da0cbe52 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -251,6 +251,8 @@ class Canvas : public Component Array> drawables; + bool objectAdded = false; + private: GlobalMouseListener globalMouseListener; diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index 17bfc5f636..a26b953279 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -156,6 +156,14 @@ void Iolet::mouseUp(MouseEvent const& e) if (getValue(locked) || e.mods.isRightButtonDown()) return; + // If an object is addded to canvas, the mouse can potentially be over an iolet when dropped onto canvas + // This situation could cause a cable to be created when an object is added + // Disregard mouseup if this happens + if (cnv->objectAdded) { + cnv->objectAdded = false; + return; + } + // This might end up calling Canvas::synchronise, at which point we are not sure this class will survive, so we do an async call bool shiftIsDown = e.mods.isShiftDown(); From cc0c0a1050aaf51675b65dba00de9855fba70cc4 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 8 May 2024 12:47:29 +0930 Subject: [PATCH 0714/1030] fix message object index / sync bug --- Source/Objects/MessageObject.h | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index e10f4a814d..a9a739073b 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -273,16 +273,13 @@ class MessageObject final : public ObjectBase if (objectText != newText) { objectText = newText; + cnv->synchronise(); + object->updateBounds(); // Recalculate bounds + setPdBounds(object->getObjectBounds()); + setSymbol(objectText); } outgoingEditor.reset(); - - object->updateBounds(); // Recalculate bounds - - setPdBounds(object->getObjectBounds()); - - setSymbol(objectText); - repaint(); } } From c22211394fea675cb117f0754fdc1e9bca9defff Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 8 May 2024 13:51:01 +0930 Subject: [PATCH 0715/1030] make invalid new object background transparent & refactor findcolour into lookandfeelchanged() --- Source/Objects/TextObject.h | 43 ++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index 4268024b7c..3d444bd3df 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -175,6 +175,11 @@ class TextBase : public ObjectBase bool isValid = true; bool isLocked; + NVGcolor backgroundColour; + NVGcolor selectedOutlineColour; + NVGcolor outlineColour; + NVGcolor ioletAreaColour; + public: TextBase(pd::WeakReference obj, Object* parent, bool valid = true) : ObjectBase(obj, parent) @@ -186,7 +191,8 @@ class TextBase : public ObjectBase isLocked = getValue(cnv->locked); objectParameters.addParamInt("Width (chars)", cDimensions, &sizeProperty); - updateTextLayout(); + + lookAndFeelChanged(); } ~TextBase() override = default; @@ -197,24 +203,30 @@ class TextBase : public ObjectBase sizeProperty = TextObjectHelper::getWidthInChars(obj.get()); } } + + void lookAndFeelChanged() override + { + backgroundColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::textObjectBackgroundColourId)); + selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); + outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); + ioletAreaColour = convertColour(object->findColour(PlugDataColour::ioletAreaColourId)); + + updateTextLayout(); + } void render(NVGcontext* nvg) override { auto b = getLocalBounds(); - - auto backgroundColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::textObjectBackgroundColourId)); - auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); - auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); - - auto ioletAreaColour = convertColour(object->findColour(PlugDataColour::ioletAreaColourId)); + auto finalOutlineColour = outlineColour; + auto finalBackgroundColour = backgroundColour; + + // render invalid text objects with red outline & semi-transparent background if (!isValid) { - outlineColour = convertColour(object->isSelected() ? Colours::red.brighter(1.5) : Colours::red); + finalOutlineColour = convertColour(object->isSelected() ? Colours::red.brighter(1.5f) : Colours::red); + finalBackgroundColour = nvgRGBAf(outlineColour.r, outlineColour.g, outlineColour.b, 0.2f); } - - nvgDrawRoundedRect(nvg, b.getX() + 0.5f, b.getY() + 0.5f, b.getWidth() - 1.0f, b.getHeight() - 1.0f, backgroundColour, object->isSelected() ? selectedOutlineColour : outlineColour, Corners::objectCornerRadius); - - if (ioletAreaColour.r != backgroundColour.r || + else if (ioletAreaColour.r != backgroundColour.r || ioletAreaColour.g != backgroundColour.g || ioletAreaColour.b != backgroundColour.b || ioletAreaColour.a != backgroundColour.a) { @@ -225,6 +237,8 @@ class TextBase : public ObjectBase nvgFill(nvg); } + nvgDrawRoundedRect(nvg, b.getX() + 0.5f, b.getY() + 0.5f, b.getWidth() - 1.0f, b.getHeight() - 1.0f, finalBackgroundColour, object->isSelected() ? selectedOutlineColour : finalOutlineColour, Corners::objectCornerRadius); + if(editor && editor->isVisible()) { imageRenderer.renderComponentFromImage(nvg, *editor, getImageScale()); @@ -492,11 +506,6 @@ class TextBase : public ObjectBase return false; } - void lookAndFeelChanged() override - { - updateTextLayout(); - } - void resized() override { updateTextLayout(); From dbcb3854f8ffa8dbd4fda1d9a6ad14be5a8c57d7 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 8 May 2024 13:54:39 +0930 Subject: [PATCH 0716/1030] Add empty string for search result of empty object --- Source/Sidebar/SearchPanel.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index c536bcce64..cdb694201d 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -305,6 +305,9 @@ class SearchPanel : public Component, public KeyListener, public Timer break; } } + + if (finalFormatedName.isEmpty()) + finalFormatedName = String("empty"); element.setProperty("Name", finalFormatedName, nullptr); element.setProperty("RightText", positionText, nullptr); From a3a47afe61a1efe29f8233a4d89199bcaddb7320 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 8 May 2024 15:28:47 +0930 Subject: [PATCH 0717/1030] show real type of object name for floatatom, listbox, symbolbox. Show empty and unknown object in red with text. --- Source/Canvas.cpp | 9 +++++++ Source/Canvas.h | 2 ++ Source/PluginEditor.cpp | 9 +++++++ Source/PluginEditor.h | 2 ++ Source/Sidebar/SearchPanel.h | 44 +++++++++++++++++++++++++++----- Source/Utility/ValueTreeViewer.h | 5 +++- 6 files changed, 63 insertions(+), 8 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 876fc1fa3f..26a54de7e7 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -1233,6 +1233,15 @@ void Canvas::focusLost(FocusChangeType cause) }); } +Object* Canvas::getObjectForPointer(t_pd* pdObject) +{ + for (auto& object : objects) { + if (object->getPointer() == (_gobj*) pdObject) + return object; + } + return nullptr; +} + void Canvas::dragAndDropPaste(String const& patchString, Point mousePos, int patchWidth, int patchHeight, String name) { locked = false; diff --git a/Source/Canvas.h b/Source/Canvas.h index 12da0cbe52..e67cb0aa77 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -96,6 +96,8 @@ class Canvas : public Component int getOverlays() const; void updateOverlays(); + Object* getObjectForPointer(t_pd* pdObject); + void synchroniseSplitCanvas(); void synchronise(); void performSynchronise(); diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index a7975495a8..e5ac0e1e99 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -882,6 +882,15 @@ Canvas* PluginEditor::getCurrentCanvas() return nullptr; } +Canvas* PluginEditor::getCanvasForPatch(pd::Patch* patch) +{ + for (auto* cnv : canvases) { + if (cnv->patch == *patch) + return cnv; + } + return nullptr; +} + void PluginEditor::closeAllTabs(bool quitAfterComplete, Canvas* patchToExclude, std::function afterComplete) { if (!canvases.size()) { diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 5c9a0070e1..569420ffda 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -107,6 +107,8 @@ class PluginEditor : public AudioProcessorEditor Canvas* getCurrentCanvas(); + Canvas* getCanvasForPatch(pd::Patch* patch); + // Part of the ZoomableDragAndDropContainer, we give it the splitview // so it can check if the drag image is over the entire splitview // otherwise some objects inside the splitview will trigger a zoom diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index cdb694201d..3533abb446 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -242,7 +242,7 @@ class SearchPanel : public Component, public KeyListener, public Timer t_class* c = patchPtr->gl_list->g_pd; if (c && c->c_name && (String::fromUTF8(c->c_name->s_name) == "array")) { auto* array = reinterpret_cast(patchPtr->gl_list); - name = "array " + String::fromUTF8(array->x_name->s_name); + name = "array: " + String::fromUTF8(array->x_name->s_name); } else if (patchPtr->gl_isgraph) { name = nameWithoutArgs; } @@ -261,6 +261,16 @@ class SearchPanel : public Component, public KeyListener, public Timer } else { String finalFormatedName; + auto getRealNameFromCanvas = [this](pd::Patch* patch, t_pd* object){ + // search inside the canvas->objects to find it's real name + if (auto patchCnv = editor->getCanvasForPatch(patch)) { + if (auto cnvObject = patchCnv->getObjectForPointer(object)) { + return cnvObject->getType(); + } + } + return String(); + }; + switch(hash(type)){ case hash("bng"): case hash("button"): @@ -277,7 +287,6 @@ class SearchPanel : public Component, public KeyListener, public Timer case hash("pad"): case hash("keyboard"): case hash("pic"): - case hash("gatom"): case hash("canvas"): case hash("scope~"): case hash("function"): @@ -291,12 +300,36 @@ class SearchPanel : public Component, public KeyListener, public Timer } case hash("message"): { - finalFormatedName = "msg " + name; + finalFormatedName = "msg: " + name; break; } case hash("comment"): { - finalFormatedName = "comment " + name; + finalFormatedName = "comment: " + name; + break; + } + case hash("text"): + { + // for some reason invalid objects get type 'text', so we have to check if it's invalid + // or actually a text object + auto foundName = getRealNameFromCanvas(patch.get(), object.get()); + + // display invalid empty object with "empty" & change icon colour to red + if (foundName == "invalid") { + if (name.isEmpty()) + finalFormatedName = String("empty"); + else + finalFormatedName = String("unknown: ") + name; + + element.setProperty("IconColour", Colours::red.toString(), nullptr); + } else { + finalFormatedName = foundName + ": " + name; + } + break; + } + case hash("gatom"): + { + finalFormatedName = getRealNameFromCanvas(patch.get(), object.get()) + ": " + name; break; } default: @@ -306,9 +339,6 @@ class SearchPanel : public Component, public KeyListener, public Timer } } - if (finalFormatedName.isEmpty()) - finalFormatedName = String("empty"); - element.setProperty("Name", finalFormatedName, nullptr); element.setProperty("RightText", positionText, nullptr); element.setProperty("Icon", Icons::Object, nullptr); diff --git a/Source/Utility/ValueTreeViewer.h b/Source/Utility/ValueTreeViewer.h index 294a579431..0e669daa23 100644 --- a/Source/Utility/ValueTreeViewer.h +++ b/Source/Utility/ValueTreeViewer.h @@ -176,7 +176,10 @@ class ValueTreeNodeComponent : public Component if(valueTreeNode.hasProperty("Icon")) { - Fonts::drawIcon(g, valueTreeNode.getProperty("Icon"), itemBounds.removeFromLeft(22).reduced(2), colour, 12, false); + auto iconColour = colour; + if (valueTreeNode.hasProperty("IconColour")) + iconColour = Colour::fromString(valueTreeNode.getProperty("IconColour").toString()); + Fonts::drawIcon(g, valueTreeNode.getProperty("Icon"), itemBounds.removeFromLeft(22).reduced(2), iconColour, 12, false); } if(valueTreeNode.hasProperty("RightText")) { From 42aedcdf194c0e963355cb5d512ca2aa529d02e2 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 8 May 2024 14:42:43 +0200 Subject: [PATCH 0718/1030] Simplification for search panel --- Source/Canvas.cpp | 9 --------- Source/Canvas.h | 2 -- Source/Sidebar/SearchPanel.h | 39 ++++++++++++------------------------ 3 files changed, 13 insertions(+), 37 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 26a54de7e7..876fc1fa3f 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -1233,15 +1233,6 @@ void Canvas::focusLost(FocusChangeType cause) }); } -Object* Canvas::getObjectForPointer(t_pd* pdObject) -{ - for (auto& object : objects) { - if (object->getPointer() == (_gobj*) pdObject) - return object; - } - return nullptr; -} - void Canvas::dragAndDropPaste(String const& patchString, Point mousePos, int patchWidth, int patchHeight, String name) { locked = false; diff --git a/Source/Canvas.h b/Source/Canvas.h index e67cb0aa77..12da0cbe52 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -96,8 +96,6 @@ class Canvas : public Component int getOverlays() const; void updateOverlays(); - Object* getObjectForPointer(t_pd* pdObject); - void synchroniseSplitCanvas(); void synchronise(); void performSynchronise(); diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index 3533abb446..017b9a312f 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -261,16 +261,6 @@ class SearchPanel : public Component, public KeyListener, public Timer } else { String finalFormatedName; - auto getRealNameFromCanvas = [this](pd::Patch* patch, t_pd* object){ - // search inside the canvas->objects to find it's real name - if (auto patchCnv = editor->getCanvasForPatch(patch)) { - if (auto cnvObject = patchCnv->getObjectForPointer(object)) { - return cnvObject->getType(); - } - } - return String(); - }; - switch(hash(type)){ case hash("bng"): case hash("button"): @@ -310,26 +300,23 @@ class SearchPanel : public Component, public KeyListener, public Timer } case hash("text"): { - // for some reason invalid objects get type 'text', so we have to check if it's invalid - // or actually a text object - auto foundName = getRealNameFromCanvas(patch.get(), object.get()); - - // display invalid empty object with "empty" & change icon colour to red - if (foundName == "invalid") { - if (name.isEmpty()) - finalFormatedName = String("empty"); - else - finalFormatedName = String("unknown: ") + name; - - element.setProperty("IconColour", Colours::red.toString(), nullptr); - } else { - finalFormatedName = foundName + ": " + name; - } + if (name.isEmpty()) + finalFormatedName = String("empty"); + else + finalFormatedName = String("unknown: ") + name; + + element.setProperty("IconColour", Colours::red.toString(), nullptr); break; } case hash("gatom"): { - finalFormatedName = getRealNameFromCanvas(patch.get(), object.get()) + ": " + name; + auto* gatom = object.cast(); + if (gatom->a_flavor == A_FLOAT) + finalFormatedName = "floatbox"; + if (gatom->a_flavor == A_SYMBOL) + finalFormatedName = "symbolbox"; + if (gatom->a_flavor == A_NULL) + finalFormatedName = "listbox"; break; } default: From ff9de893c302cfeaa53ee7a6b9e1497f6bbcc097 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 8 May 2024 14:58:53 +0200 Subject: [PATCH 0719/1030] Removed obsolete function --- Source/PluginEditor.cpp | 9 --------- Source/PluginEditor.h | 2 -- 2 files changed, 11 deletions(-) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index e5ac0e1e99..a7975495a8 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -882,15 +882,6 @@ Canvas* PluginEditor::getCurrentCanvas() return nullptr; } -Canvas* PluginEditor::getCanvasForPatch(pd::Patch* patch) -{ - for (auto* cnv : canvases) { - if (cnv->patch == *patch) - return cnv; - } - return nullptr; -} - void PluginEditor::closeAllTabs(bool quitAfterComplete, Canvas* patchToExclude, std::function afterComplete) { if (!canvases.size()) { diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 569420ffda..5c9a0070e1 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -107,8 +107,6 @@ class PluginEditor : public AudioProcessorEditor Canvas* getCurrentCanvas(); - Canvas* getCanvasForPatch(pd::Patch* patch); - // Part of the ZoomableDragAndDropContainer, we give it the splitview // so it can check if the drag image is over the entire splitview // otherwise some objects inside the splitview will trigger a zoom From 529b1c9687015281ae89cd7f4cd37d85261ed551 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 8 May 2024 23:47:31 +0930 Subject: [PATCH 0720/1030] make activity glow have hole inside when gui's have transparent flag set --- Source/Object.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index b29d515c6d..e786748891 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -1221,7 +1221,7 @@ void Object::render(NVGcontext* nvg) if(activityOverlayImage) nvgDeleteImage(nvg, activityOverlayImage); Path objectShadow; objectShadow.addRoundedRectangle(getLocalBounds().reduced(Object::margin - 1), Corners::objectCornerRadius); - activityOverlayImage = StackShadow::createActivityDropShadowImage(nvg, getLocalBounds(), objectShadow, getLookAndFeel().findColour(PlugDataColour::dataColourId), 5.5f, { 0, 0 }, 0, gui && gui->getCanvas()); + activityOverlayImage = StackShadow::createActivityDropShadowImage(nvg, getLocalBounds(), objectShadow, getLookAndFeel().findColour(PlugDataColour::dataColourId), 5.5f, { 0, 0 }, 0, gui && (gui->getCanvas() || gui->isTransparent())); activityOverlayDirty = false; } From 813bff570212ed497b59ad13adce4b50c76fdc24 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 9 May 2024 00:13:41 +0930 Subject: [PATCH 0721/1030] allow intentional clicks on iolet to reset DnD check --- Source/Iolet.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index a26b953279..6e449b0f27 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -147,6 +147,9 @@ void Iolet::mouseDrag(MouseEvent const& e) void Iolet::mouseDown(MouseEvent const& e) { mouseIsDown = true; + + // make sure that intentional clicks on iolet get through after a DnD action + cnv->objectAdded = false; } void Iolet::mouseUp(MouseEvent const& e) From 4713010985ed664ebdd2aad8ad3b4910ce68e318 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 8 May 2024 16:49:13 +0200 Subject: [PATCH 0722/1030] Try using older version of Metal --- Libraries/nanovg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/nanovg b/Libraries/nanovg index 8b22d88f16..f3111d4c97 160000 --- a/Libraries/nanovg +++ b/Libraries/nanovg @@ -1 +1 @@ -Subproject commit 8b22d88f160c56d5fd21cb4334c120b75817c452 +Subproject commit f3111d4c978f419dba5f39d78aa6c4d9ae5c5840 From c81b30d383a781f13aa5d87c8fb64608e73b038b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 8 May 2024 17:49:32 +0200 Subject: [PATCH 0723/1030] Rolled back some nanovg changes --- Libraries/nanovg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/nanovg b/Libraries/nanovg index f3111d4c97..419aa16b86 160000 --- a/Libraries/nanovg +++ b/Libraries/nanovg @@ -1 +1 @@ -Subproject commit f3111d4c978f419dba5f39d78aa6c4d9ae5c5840 +Subproject commit 419aa16b86141001bcf95f23116026d1e03d08b5 From fc0ce3553bba5e385a91b15c8e58275f8205905b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 8 May 2024 17:55:23 +0200 Subject: [PATCH 0724/1030] Small fix --- Source/NVGSurface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 30cd6cc415..4d5b05fe6c 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -147,7 +147,7 @@ public Component, public Timer NVGframebuffer* mainFBO = nullptr; NVGframebuffer* invalidFBO = nullptr; int fbWidth = 0, fbHeight = 0; - float lastScaleFactor = 1.0f; + float lastScaleFactor = 0.0f; bool hresize = false; bool resizing = false; From 0a727748defa897397880db1f8f9e1608d19bc69 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 9 May 2024 14:25:06 +0930 Subject: [PATCH 0725/1030] simpler way to stop errant mouseup on iolet --- Source/Canvas.cpp | 1 - Source/Canvas.h | 2 -- Source/Iolet.cpp | 16 +++++----------- Source/Iolet.h | 2 +- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 876fc1fa3f..ef2a91a4c8 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -1237,7 +1237,6 @@ void Canvas::dragAndDropPaste(String const& patchString, Point mousePos, in { locked = false; presentationMode = false; - objectAdded = true; // force the valueChanged to run, and wait for them to return locked.getValueSource().sendChangeMessage(true); diff --git a/Source/Canvas.h b/Source/Canvas.h index 12da0cbe52..f981a8eac9 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -251,8 +251,6 @@ class Canvas : public Component Array> drawables; - bool objectAdded = false; - private: GlobalMouseListener globalMouseListener; diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index 6e449b0f27..a7f63f34ca 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -147,26 +147,20 @@ void Iolet::mouseDrag(MouseEvent const& e) void Iolet::mouseDown(MouseEvent const& e) { mouseIsDown = true; - - // make sure that intentional clicks on iolet get through after a DnD action - cnv->objectAdded = false; } void Iolet::mouseUp(MouseEvent const& e) { + // make sure only intentional mouse down/up events trigger iolet events + // click dragging objects onto canvas can give us an errant mouseup if cursor is over iolet + if (!mouseIsDown) + return; + mouseIsDown = false; if (getValue(locked) || e.mods.isRightButtonDown()) return; - // If an object is addded to canvas, the mouse can potentially be over an iolet when dropped onto canvas - // This situation could cause a cable to be created when an object is added - // Disregard mouseup if this happens - if (cnv->objectAdded) { - cnv->objectAdded = false; - return; - } - // This might end up calling Canvas::synchronise, at which point we are not sure this class will survive, so we do an async call bool shiftIsDown = e.mods.isShiftDown(); diff --git a/Source/Iolet.h b/Source/Iolet.h index 802a9b2d1c..60a7e56c95 100644 --- a/Source/Iolet.h +++ b/Source/Iolet.h @@ -53,7 +53,7 @@ class Iolet : public Component Canvas* cnv; private: - bool mouseIsDown; + bool mouseIsDown = false; bool const insideGraph; bool hideIolet = false; From a5a2e683dc4914f78add36b133ff15fde56cd351 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 9 May 2024 14:45:00 +0930 Subject: [PATCH 0726/1030] call cnv->synchronise() from inside callasync, in the unlikey event that the patch has removed the message object --- Source/Objects/MessageObject.h | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index a9a739073b..b45de3a119 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -273,10 +273,15 @@ class MessageObject final : public ObjectBase if (objectText != newText) { objectText = newText; - cnv->synchronise(); - object->updateBounds(); // Recalculate bounds - setPdBounds(object->getObjectBounds()); - setSymbol(objectText); + MessageManager::callAsync([this, _this = SafePointer(this)]() mutable { + // syncronising canvas may remove this object (highly unlikely) + if (!_this) + return; + cnv->synchronise(); + object->updateBounds(); // Recalculate bounds + setPdBounds(object->getObjectBounds()); + setSymbol(objectText); + }); } outgoingEditor.reset(); From b53699c6db9339da2f5c4a775b8eb38cf573ebf4 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 9 May 2024 15:18:48 +0930 Subject: [PATCH 0727/1030] fix search result for empty/unknown/comment All three are base class text, however empty/unknown are T_OBJECT, while comment is T_TEXT --- Source/Sidebar/SearchPanel.h | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index 017b9a312f..fd0cf65f40 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -300,12 +300,18 @@ class SearchPanel : public Component, public KeyListener, public Timer } case hash("text"): { - if (name.isEmpty()) - finalFormatedName = String("empty"); - else - finalFormatedName = String("unknown: ") + name; - - element.setProperty("IconColour", Colours::red.toString(), nullptr); + auto *text = object.cast(); + auto objectType = text->x_textbuf.b_ob.te_type; + if (objectType == T_TEXT) { + finalFormatedName = String("comment: ") + name; + } else if (objectType == T_OBJECT) { + element.setProperty("IconColour", Colours::red.toString(), nullptr); + + if (name.isEmpty()) + finalFormatedName = String("empty"); + else + finalFormatedName = String("unknown: ") + name; + } break; } case hash("gatom"): From f833fc00becb6b465f1076efc2a19825a75dcb3f Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 9 May 2024 15:41:02 +0930 Subject: [PATCH 0728/1030] cleanup --- Source/Sidebar/SearchPanel.h | 53 ++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index fd0cf65f40..9a2ace2f09 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -300,29 +300,46 @@ class SearchPanel : public Component, public KeyListener, public Timer } case hash("text"): { - auto *text = object.cast(); - auto objectType = text->x_textbuf.b_ob.te_type; - if (objectType == T_TEXT) { - finalFormatedName = String("comment: ") + name; - } else if (objectType == T_OBJECT) { - element.setProperty("IconColour", Colours::red.toString(), nullptr); - - if (name.isEmpty()) - finalFormatedName = String("empty"); - else - finalFormatedName = String("unknown: ") + name; + switch(object.cast()->x_textbuf.b_ob.te_type){ + case T_TEXT: + { + // if object & classname is text, then it's a comment + finalFormatedName = String("comment: ") + name; + break; + } + case T_OBJECT: + { + // if object is T_OBJECT but classname is 'text' object is in error state + element.setProperty("IconColour", Colours::red.toString(), nullptr); + + if (name.isEmpty()) + finalFormatedName = String("empty"); + else + finalFormatedName = String("unknown: ") + name; + + break; + } + default: + break; + } break; } case hash("gatom"): { - auto* gatom = object.cast(); - if (gatom->a_flavor == A_FLOAT) - finalFormatedName = "floatbox"; - if (gatom->a_flavor == A_SYMBOL) - finalFormatedName = "symbolbox"; - if (gatom->a_flavor == A_NULL) - finalFormatedName = "listbox"; + switch(object.cast()->a_flavor){ + case A_FLOAT: + finalFormatedName = "floatbox"; + break; + case A_SYMBOL: + finalFormatedName = "symbolbox"; + break; + case A_NULL: + finalFormatedName = "listbox"; + break; + default: + break; + } break; } default: From 0f0dcaab6714ab6934fb69f6a726d6f604f91b8d Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 9 May 2024 15:50:20 +0930 Subject: [PATCH 0729/1030] sync canvas when comment object hides editor also (if text has changed) --- Source/Objects/CommentObject.h | 15 +++++++++------ Source/Sidebar/SearchPanel.h | 1 - 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Source/Objects/CommentObject.h b/Source/Objects/CommentObject.h index 75f4840f1d..fb900cc884 100644 --- a/Source/Objects/CommentObject.h +++ b/Source/Objects/CommentObject.h @@ -88,14 +88,17 @@ class CommentObject final : public ObjectBase if (objectText != newText) { objectText = newText; + MessageManager::callAsync([this, _this = SafePointer(this)]() mutable { + // syncronising canvas may remove this object (highly unlikely) + if (!_this) + return; + cnv->synchronise(); + object->updateBounds(); // Recalculate bounds + setPdBounds(object->getObjectBounds()); + setSymbol(objectText); + }); } - outgoingEditor.reset(); - - object->updateBounds(); // Recalculate bounds - setPdBounds(object->getObjectBounds()); - - setSymbol(objectText); repaint(); } } diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index 9a2ace2f09..c0b96f5599 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -321,7 +321,6 @@ class SearchPanel : public Component, public KeyListener, public Timer } default: break; - } break; } From c1ef0cd1cb327d096bb279956d2466829579591b Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 9 May 2024 16:15:27 +0930 Subject: [PATCH 0730/1030] show multi-array in search, sync canvas when adding an array in array parameters --- Source/Objects/ArrayObject.h | 1 + Source/Sidebar/SearchPanel.h | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index f4ce5aaf05..a5a5748101 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -1215,6 +1215,7 @@ class ArrayObject final : public ObjectBase { if (auto glist = ptr.get<_glist>()) { graph_array(glist.get(), pd::Interface::getUnusedArrayName(), gensym("float"), 100, 0); } + cnv->synchronise(); reinitialiseGraphs(); } diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index c0b96f5599..3177275daa 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -241,8 +241,18 @@ class SearchPanel : public Component, public KeyListener, public Timer { t_class* c = patchPtr->gl_list->g_pd; if (c && c->c_name && (String::fromUTF8(c->c_name->s_name) == "array")) { - auto* array = reinterpret_cast(patchPtr->gl_list); - name = "array: " + String::fromUTF8(array->x_name->s_name); + StringArray arrays; + auto arrayIt = patchPtr->gl_list; + while(arrayIt) { + if (auto* array = reinterpret_cast(arrayIt)) + arrays.add(String::fromUTF8(array->x_name->s_name)); + arrayIt = arrayIt->g_next; + } + String formatedArraysText; + for (int i = 0; i < arrays.size(); i++) { + formatedArraysText += arrays[i] + String(i < arrays.size() - 1 ? ", " : ""); + } + name = "array: " + formatedArraysText; } else if (patchPtr->gl_isgraph) { name = nameWithoutArgs; } From c6a1e470be6e9bdf2beea932995960675c8a2f18 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 9 May 2024 16:46:44 +0930 Subject: [PATCH 0731/1030] sync canvas when array parameters are changed --- Source/Objects/ArrayObject.h | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index a5a5748101..3e3403e777 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -803,14 +803,17 @@ struct ArrayPropertiesPanel : public PropertiesPanelProperty, public Value::List AddArrayButton addButton; OwnedArray deleteButtons; Array nameValues; - - ArrayPropertiesPanel(std::function addArrayCallback) + + std::function syncCanvas = []() {}; + + ArrayPropertiesPanel(std::function addArrayCallback, std::function syncCanvasFunc) : PropertiesPanelProperty("array") { setHideLabel(true); addAndMakeVisible(addButton); addButton.onClick = addArrayCallback; + syncCanvas = syncCanvasFunc; } void reloadGraphs(const Array>& safeGraphs) @@ -853,6 +856,9 @@ struct ArrayPropertiesPanel : public PropertiesPanelProperty, public Value::List void valueChanged(Value& v) override { + // when array parameters are changed we need to resync the canavs to PD + // TODO: do we need to protect this in a callasync also? + syncCanvas(); repaint(); } @@ -1156,7 +1162,7 @@ class ArrayObject final : public ObjectBase { objectParameters.addParamSize(&sizeProperty); objectParameters.addParamCustom([_this = SafePointer(this)](){ - if(!_this) return static_cast(nullptr); + if(!_this) return static_cast(nullptr, nullptr); Array> safeGraphs; for(auto* graph : _this->graphs) @@ -1166,6 +1172,8 @@ class ArrayObject final : public ObjectBase { auto* panel = new ArrayPropertiesPanel([_this](){ if(_this) _this->addArray(); + }, [_this](){ + if(_this) _this->cnv->synchronise(); }); panel->reloadGraphs(safeGraphs); From 36cf31b8b2dbfef2a7bb3a26068c7f3060b85c8a Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 9 May 2024 17:14:54 +0930 Subject: [PATCH 0732/1030] fix text object inlet/outlet area rendering with invalid object transparency --- Source/Objects/TextObject.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index 3d444bd3df..c2994e7450 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -226,10 +226,13 @@ class TextBase : public ObjectBase finalOutlineColour = convertColour(object->isSelected() ? Colours::red.brighter(1.5f) : Colours::red); finalBackgroundColour = nvgRGBAf(outlineColour.r, outlineColour.g, outlineColour.b, 0.2f); } - else if (ioletAreaColour.r != backgroundColour.r || + + nvgDrawRoundedRect(nvg, b.getX() + 0.5f, b.getY() + 0.5f, b.getWidth() - 1.0f, b.getHeight() - 1.0f, finalBackgroundColour, object->isSelected() ? selectedOutlineColour : finalOutlineColour, Corners::objectCornerRadius); + + if (isValid && (ioletAreaColour.r != backgroundColour.r || ioletAreaColour.g != backgroundColour.g || ioletAreaColour.b != backgroundColour.b || - ioletAreaColour.a != backgroundColour.a) { + ioletAreaColour.a != backgroundColour.a)) { nvgFillColor(nvg, ioletAreaColour); nvgBeginPath(nvg); nvgRoundedRect(nvg, 0.5f, 0, getWidth() - 1.0f, 3.5f, Corners::objectCornerRadius); @@ -237,8 +240,6 @@ class TextBase : public ObjectBase nvgFill(nvg); } - nvgDrawRoundedRect(nvg, b.getX() + 0.5f, b.getY() + 0.5f, b.getWidth() - 1.0f, b.getHeight() - 1.0f, finalBackgroundColour, object->isSelected() ? selectedOutlineColour : finalOutlineColour, Corners::objectCornerRadius); - if(editor && editor->isVisible()) { imageRenderer.renderComponentFromImage(nvg, *editor, getImageScale()); From 29603517916c47d5b672243434669284e2156b9f Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 9 May 2024 18:00:12 +0930 Subject: [PATCH 0733/1030] improve iolet area drawing, use rounded scissor to round the edges of the rectangle --- Source/Objects/TextObject.h | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index c2994e7450..ec2f600df6 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -229,15 +229,35 @@ class TextBase : public ObjectBase nvgDrawRoundedRect(nvg, b.getX() + 0.5f, b.getY() + 0.5f, b.getWidth() - 1.0f, b.getHeight() - 1.0f, finalBackgroundColour, object->isSelected() ? selectedOutlineColour : finalOutlineColour, Corners::objectCornerRadius); + // if the object is valid & iolet area colour is differnet from background colour + // draw two non-rounded rectangles at top / bottom + // scissor with rounded rectangle + // ┌──────────────────┠+ // │┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼│ + // ├──────────────────┤ + // │ │ + // │ object │ + // │ │ + // ├──────────────────┤ + // │┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼│ + // └──────────────────┘ + if (isValid && (ioletAreaColour.r != backgroundColour.r || ioletAreaColour.g != backgroundColour.g || ioletAreaColour.b != backgroundColour.b || ioletAreaColour.a != backgroundColour.a)) { + nvgSave(nvg); + const float padding = 1.3f; + const float padding2x = padding * 2; + nvgRoundedScissor(nvg, padding, padding, getWidth() - padding2x, getHeight() - padding2x, jmax(0.0f, Corners::objectCornerRadius - 0.7f)); + nvgFillColor(nvg, ioletAreaColour); nvgBeginPath(nvg); - nvgRoundedRect(nvg, 0.5f, 0, getWidth() - 1.0f, 3.5f, Corners::objectCornerRadius); - nvgRoundedRect(nvg, 0.5f, getHeight() - 3.5f, getWidth() - 1.0f, 3.5f, Corners::objectCornerRadius); + nvgRect(nvg, 0, 0, getWidth(), 3.5f); + nvgRect(nvg, 0, getHeight() - 3.5f, getWidth(), 3.5f); nvgFill(nvg); + + nvgRestore(nvg); } if(editor && editor->isVisible()) From 2f99fc8187884724af9d3c35a52e3712325b7e19 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 10 May 2024 16:08:23 +0930 Subject: [PATCH 0734/1030] no need for callasync on cnv->synchronise() call --- Source/Objects/CommentObject.h | 13 ++++--------- Source/Objects/MessageObject.h | 13 ++++--------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/Source/Objects/CommentObject.h b/Source/Objects/CommentObject.h index fb900cc884..6db0b713e3 100644 --- a/Source/Objects/CommentObject.h +++ b/Source/Objects/CommentObject.h @@ -88,15 +88,10 @@ class CommentObject final : public ObjectBase if (objectText != newText) { objectText = newText; - MessageManager::callAsync([this, _this = SafePointer(this)]() mutable { - // syncronising canvas may remove this object (highly unlikely) - if (!_this) - return; - cnv->synchronise(); - object->updateBounds(); // Recalculate bounds - setPdBounds(object->getObjectBounds()); - setSymbol(objectText); - }); + object->updateBounds(); // Recalculate bounds + setPdBounds(object->getObjectBounds()); + setSymbol(objectText); + cnv->synchronise(); } outgoingEditor.reset(); repaint(); diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index b45de3a119..ed4f357da5 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -273,15 +273,10 @@ class MessageObject final : public ObjectBase if (objectText != newText) { objectText = newText; - MessageManager::callAsync([this, _this = SafePointer(this)]() mutable { - // syncronising canvas may remove this object (highly unlikely) - if (!_this) - return; - cnv->synchronise(); - object->updateBounds(); // Recalculate bounds - setPdBounds(object->getObjectBounds()); - setSymbol(objectText); - }); + object->updateBounds(); // Recalculate bounds + setPdBounds(object->getObjectBounds()); + setSymbol(objectText); + cnv->synchronise(); } outgoingEditor.reset(); From 3324ad2c6916b80ace29e4dc312eb06cbbdcbd60 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 10 May 2024 17:48:29 +0930 Subject: [PATCH 0735/1030] theme panel update colours for text correctly with LNF --- Source/Components/PropertiesPanel.h | 57 ++++++++++++++++++++++++----- Source/Dialogs/ThemePanel.h | 16 ++++++-- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/Source/Components/PropertiesPanel.h b/Source/Components/PropertiesPanel.h index 0122055be8..76f237c654 100644 --- a/Source/Components/PropertiesPanel.h +++ b/Source/Components/PropertiesPanel.h @@ -331,7 +331,12 @@ class PropertiesPanel : public Component { fontValue.setValue(options[comboBox.getSelectedItemIndex()]); }; + setLookAndFeel(&LookAndFeel::getDefaultLookAndFeel()); + addAndMakeVisible(comboBox); + + lookAndFeelChanged(); + } PropertiesPanelProperty* createCopy() override @@ -339,6 +344,11 @@ class PropertiesPanel : public Component { return new FontComponent(getName(), fontValue); } + void lookAndFeelChanged() override + { + comboBox.setColour(ComboBox::textColourId, findColour(PlugDataColour::panelTextColourId)); + } + void setFont(String const& fontName) { fontValue.setValue(fontValue); @@ -370,6 +380,8 @@ class PropertiesPanel : public Component { property->setHideLabel(true); addAndMakeVisible(property); } + + setLookAndFeel(&LookAndFeel::getDefaultLookAndFeel()); } MultiPropertyComponent(String const& propertyName, Array values, StringArray options) @@ -393,6 +405,13 @@ class PropertiesPanel : public Component { } } + void lookAndFeelChanged() override + { + for (auto& property : properties){ + property->setColour(ComboBox::textColourId, findColour(PlugDataColour::panelTextColourId)); + } + } + void setRoundedCorners(bool roundTop, bool roundBottom) override { properties.getLast()->setRoundedCorners(roundTop, roundBottom); @@ -426,34 +445,42 @@ class PropertiesPanel : public Component { BoolComponent(String const& propertyName, Value& value, StringArray options) : PropertiesPanelProperty(propertyName) , textOptions(std::move(options)) - , toggleStateValue(value) - { - toggleStateValue.addListener(this); + , toggleStateValue(value) { + init(); } // Also allow creating it without passing in a Value, makes it easier to derive from this class for custom bool components BoolComponent(String const& propertyName, StringArray options) : PropertiesPanelProperty(propertyName) - , textOptions(std::move(options)) - { - toggleStateValue.addListener(this); + , textOptions(std::move(options)) { + init(); } // Allow creation without an attached juce::Value, but with an initial value // We need this constructor sometimes to prevent feedback caused by the initial value being set after the listener is attached BoolComponent(String const& propertyName, bool initialValue, StringArray options) : PropertiesPanelProperty(propertyName) - , textOptions(std::move(options)) - { + , textOptions(std::move(options)) { toggleStateValue = initialValue; - toggleStateValue.addListener(this); + init(); } - ~BoolComponent() + void init() { + toggleStateValue.addListener(this); + setLookAndFeel(&LookAndFeel::getDefaultLookAndFeel()); + lookAndFeelChanged(); + } + + ~BoolComponent() { toggleStateValue.removeListener(this); } + void lookAndFeelChanged() + { + repaint(); + } + PropertiesPanelProperty* createCopy() override { return new BoolComponent(getName(), toggleStateValue, textOptions); @@ -571,6 +598,7 @@ class PropertiesPanel : public Component { : PropertiesPanelProperty(propertyName) , swatchComponent(value) { + currentColour.referTo(value); setWantsKeyboardFocus(true); @@ -589,6 +617,9 @@ class PropertiesPanel : public Component { addAndMakeVisible(swatchComponent); updateHexValue(); + + setLookAndFeel(&LookAndFeel::getDefaultLookAndFeel()); + repaint(); } @@ -597,6 +628,12 @@ class PropertiesPanel : public Component { currentColour.removeListener(this); } + void lookAndFeelChanged() override + { + // TextEditor is special, setColour() will only change newly typed text colour + hexValueEditor.applyColourToAllText(findColour(PlugDataColour::panelTextColourId)); + } + PropertiesPanelProperty* createCopy() override { return new ColourComponent(getName(), currentColour); diff --git a/Source/Dialogs/ThemePanel.h b/Source/Dialogs/ThemePanel.h index 3b13e6ec96..a88a6b8bbe 100644 --- a/Source/Dialogs/ThemePanel.h +++ b/Source/Dialogs/ThemePanel.h @@ -126,11 +126,10 @@ struct ThemeSelectorProperty : public PropertiesPanelProperty { callback(comboBox.getText()); }; - comboBox.setColour(ComboBox::backgroundColourId, Colours::transparentBlack); - comboBox.setColour(ComboBox::outlineColourId, Colours::transparentBlack); - comboBox.setColour(ComboBox::textColourId, findColour(PlugDataColour::panelTextColourId)); - addAndMakeVisible(comboBox); + + setLookAndFeel(&LookAndFeel::getDefaultLookAndFeel()); + lookAndFeelChanged(); } PropertiesPanelProperty* createCopy() override @@ -141,6 +140,13 @@ struct ThemeSelectorProperty : public PropertiesPanelProperty { return themeSelector; } + void lookAndFeelChanged() override + { + comboBox.setColour(ComboBox::backgroundColourId, Colours::transparentBlack); + comboBox.setColour(ComboBox::outlineColourId, Colours::transparentBlack); + comboBox.setColour(ComboBox::textColourId, findColour(PlugDataColour::panelTextColourId)); + } + String getText() const { return comboBox.getText(); @@ -562,6 +568,7 @@ class ThemePanel : public SettingsDialogPanel if (v.refersToSameSourceAs(swatches[themeName][colourName])) { theme.setProperty(colourName, v.toString(), nullptr); pd->setTheme(PlugDataLook::currentTheme, true); + sendLookAndFeelChange(); return; } } @@ -609,5 +616,6 @@ class ThemePanel : public SettingsDialogPanel updateSwatches(); pd->setTheme(PlugDataLook::selectedThemes[0], true); + sendLookAndFeelChange(); } }; From 5875aaad0f0e4f7a749b9cdb983e57450a632c17 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 10 May 2024 18:09:07 +0930 Subject: [PATCH 0736/1030] better handling of hex colour text editing --- Source/Components/PropertiesPanel.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Source/Components/PropertiesPanel.h b/Source/Components/PropertiesPanel.h index 76f237c654..d9073ba273 100644 --- a/Source/Components/PropertiesPanel.h +++ b/Source/Components/PropertiesPanel.h @@ -611,8 +611,16 @@ class PropertiesPanel : public Component { hexValueEditor.setColour(outlineColourId, Colour()); hexValueEditor.setJustification(Justification::centred); + hexValueEditor.onReturnKey = [this]() { + grabKeyboardFocus(); + }; + hexValueEditor.onTextChange = [this]() { - currentColour = String("ff") + hexValueEditor.getText().substring(1).toLowerCase(); + colour = String("ff") + hexValueEditor.getText().substring(1).toLowerCase(); + }; + + hexValueEditor.onFocusLost = [this]() { + currentColour.setValue(colour); }; addAndMakeVisible(swatchComponent); @@ -664,6 +672,7 @@ class PropertiesPanel : public Component { private: SwatchComponent swatchComponent; Value currentColour; + Value colour; TextEditor hexValueEditor; }; From b2a78168f79f8b0ee745e31cdbd93bf99860491f Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 10 May 2024 18:28:35 +0930 Subject: [PATCH 0737/1030] fix iolet visibility when it's symbol, in presentationMode or insideGraph --- Source/Iolet.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index a7f63f34ca..5d41d697a1 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -53,7 +53,9 @@ Rectangle Iolet::getCanvasBounds() void Iolet::render(NVGcontext* nvg) { - + if (getValue(presentationMode) || insideGraph || hideIolet) + return; + auto* fb = cnv->ioletBuffer; if(!fb) return; @@ -449,6 +451,5 @@ void Iolet::valueChanged(Value& v) void Iolet::setHidden(bool hidden) { hideIolet = hidden; - setVisible(!getValue(presentationMode) && !insideGraph && !hideIolet); repaint(); } From 070fe310ccb4eb8f242f445b348b2ec50619bef5 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 10 May 2024 12:44:44 +0200 Subject: [PATCH 0738/1030] Hide label when array text is empty --- Source/Objects/ArrayObject.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index f4ce5aaf05..28fdf9d118 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -1279,6 +1279,9 @@ class ArrayObject final : public ObjectBase { object->cnv->addAndMakeVisible(label.get()); } + else { + label.reset(nullptr); + } } Rectangle getPdBounds() override From 68eedb8ccb889ed20bc4b7a7a10d31ae6217c19a Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 10 May 2024 21:47:26 +0930 Subject: [PATCH 0739/1030] enable send/receive to be shown in search results --- Source/Sidebar/SearchPanel.h | 132 +++++++++++++++++++++++++++++------ 1 file changed, 111 insertions(+), 21 deletions(-) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index 3177275daa..3f6dc8a2ce 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -10,6 +10,14 @@ #include #include +extern "C" { +#include +} + +static int srl_is_valid(t_symbol const *s) { + return (s != nullptr && s != gensym("")); +} + class SearchPanelSettings : public Component { public: @@ -271,9 +279,16 @@ class SearchPanel : public Component, public KeyListener, public Timer } else { String finalFormatedName; + auto formatSendRecieve = [](const String& send, const String& receive) -> String { + const String formatedSen = (send.isEmpty() || (send == "empty")) ? "" : " send: " + send; + const String formatedRcv = (receive.isEmpty() || (receive == "empty")) ? "" : String(formatedSen.isNotEmpty()? "," : "") + " rec: " + receive; + + return String(formatedSen + formatedRcv); + }; + switch(hash(type)){ + // IEM send-receive symbols case hash("bng"): - case hash("button"): case hash("hsl"): case hash("vsl"): case hash("slider"): @@ -282,22 +297,105 @@ class SearchPanel : public Component, public KeyListener, public Timer case hash("numbox~"): case hash("vradio"): case hash("hradio"): - case hash("cnv"): case hash("vu"): - case hash("pad"): + case hash("cnv"): + { + String receiveSym, sendSym; + if (auto iemgui = objectPtr.get()) { + t_symbol* srlsym[3]; + iemgui_all_sym2dollararg(iemgui.get(), srlsym); + if (srl_is_valid(srlsym[0])) { + sendSym = String::fromUTF8(iemgui->x_snd_unexpanded->s_name); + } + if (srl_is_valid(srlsym[1])) { + receiveSym = String::fromUTF8(iemgui->x_rcv_unexpanded->s_name); + } + } + finalFormatedName = nameWithoutArgs + formatSendRecieve(sendSym, receiveSym); + break; + } case hash("keyboard"): + { + String receiveSym, sendSym; + if (auto keyboardObject = object.cast()) { + sendSym = String(keyboardObject->x_send->s_name); + receiveSym = String(keyboardObject->x_receive->s_name); + } + finalFormatedName = nameWithoutArgs + formatSendRecieve(sendSym, receiveSym); + break; + } case hash("pic"): - case hash("canvas"): + { + String receiveSym, sendSym; + if (auto picObject = object.cast()) { + sendSym = String(picObject->x_send->s_name); + receiveSym = String(picObject->x_receive->s_name); + } + finalFormatedName = nameWithoutArgs + formatSendRecieve(sendSym, receiveSym); + break; + } case hash("scope~"): + { + String receiveSym; + if (auto scopeObject = object.cast()){ + receiveSym = String(scopeObject->x_receive->s_name); + } + finalFormatedName = nameWithoutArgs + formatSendRecieve("", receiveSym); + break; + } case hash("function"): - case hash("bicoeff"): - case hash("messbox"): + { + String receiveSym, sendSym; + if (auto keyboardObject = object.cast()) { + sendSym = String(keyboardObject->x_send->s_name); + receiveSym = String(keyboardObject->x_receive->s_name); + + } + finalFormatedName = nameWithoutArgs + formatSendRecieve(sendSym, receiveSym); + break; + } case hash("note"): + { + String receiveSym; + if (auto noteObject = object.cast()) { + receiveSym = String(noteObject->x_receive->s_name); + } + finalFormatedName = nameWithoutArgs + formatSendRecieve("", receiveSym); + break; + } case hash("knob"): { - finalFormatedName = nameWithoutArgs; + String receiveSym, sendSym; + if (auto knobObj = object.cast()) { + sendSym = String(knobObj->x_snd->s_name); + receiveSym = String(knobObj->x_rcv->s_name); + } + finalFormatedName = nameWithoutArgs + formatSendRecieve(sendSym, receiveSym); + break; + } + case hash("gatom"): + { + auto gatomObject = object.cast(); + String gatomName; + switch(gatomObject->a_flavor){ + case A_FLOAT: + gatomName = "floatbox"; + break; + case A_SYMBOL: + gatomName = "symbolbox"; + break; + case A_NULL: + gatomName = "listbox"; + break; + default: + break; + } + const auto receive = String(gatomObject->a_symfrom->s_name); + const auto send = String(gatomObject->a_symto->s_name); + finalFormatedName = gatomName + formatSendRecieve(send, receive); break; } + // ============ no send-receive symbols ============ case hash("message"): { finalFormatedName = "msg: " + name; @@ -334,21 +432,13 @@ class SearchPanel : public Component, public KeyListener, public Timer } break; } - case hash("gatom"): + case hash("canvas"): + case hash("bicoeff"): + case hash("messbox"): + case hash("pad"): + case hash("button"): { - switch(object.cast()->a_flavor){ - case A_FLOAT: - finalFormatedName = "floatbox"; - break; - case A_SYMBOL: - finalFormatedName = "symbolbox"; - break; - case A_NULL: - finalFormatedName = "listbox"; - break; - default: - break; - } + finalFormatedName = nameWithoutArgs; break; } default: From d1c4d2a264ae39957225deea9ab143b8caf8ea89 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 10 May 2024 17:53:28 +0200 Subject: [PATCH 0740/1030] Fixed [keyboard] toggle mode --- Libraries/pure-data | 2 +- Source/Components/PropertiesPanel.h | 2 +- Source/Objects/KeyboardObject.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Libraries/pure-data b/Libraries/pure-data index a68c0ccfea..9edc79f3e9 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit a68c0ccfea4c93dedca85c93d2812e75ef86a1b8 +Subproject commit 9edc79f3e972036c8d4df7c6038d527e7a102688 diff --git a/Source/Components/PropertiesPanel.h b/Source/Components/PropertiesPanel.h index d9073ba273..7127ffce4e 100644 --- a/Source/Components/PropertiesPanel.h +++ b/Source/Components/PropertiesPanel.h @@ -476,7 +476,7 @@ class PropertiesPanel : public Component { toggleStateValue.removeListener(this); } - void lookAndFeelChanged() + void lookAndFeelChanged() override { repaint(); } diff --git a/Source/Objects/KeyboardObject.h b/Source/Objects/KeyboardObject.h index fdee171979..bdc6fed064 100644 --- a/Source/Objects/KeyboardObject.h +++ b/Source/Objects/KeyboardObject.h @@ -417,7 +417,7 @@ class KeyboardObject final : public ObjectBase keyboard.heldKeys.insert(i); repaint(); } - if (!notes[i] && keyboard.heldKeys.contains(i) && keyboard.clickedKey != i) { + if (!notes[i] && keyboard.heldKeys.contains(i) && keyboard.clickedKey != i && !getValue(toggleMode)) { keyboard.heldKeys.erase(i); repaint(); } From f26e712a6088d6f4700f7ba30635ea7c426179f7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 10 May 2024 17:56:43 +0200 Subject: [PATCH 0741/1030] Rolled back nanovg changes that cause problems --- Libraries/nanovg | 2 +- Source/NVGSurface.h | 24 ------------------------ 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/Libraries/nanovg b/Libraries/nanovg index 419aa16b86..b45a86d112 160000 --- a/Libraries/nanovg +++ b/Libraries/nanovg @@ -1 +1 @@ -Subproject commit 419aa16b86141001bcf95f23116026d1e03d08b5 +Subproject commit b45a86d1124468128ec21071d873a9ecb8f2a4e5 diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 4d5b05fe6c..d1a361efb9 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -21,26 +21,6 @@ using namespace juce::gl; #define NANOVG_GL_IMPLEMENTATION 1 #endif - -#if NANOVG_METAL_IMPLEMENTATION -// With metal, all images and buffers get invalidated when the window becomes inactive -// so, we need to get a callback when that happens to inform plugdata that all images/framebuffers are now invalid -struct BackgroundProcessChecker -{ - bool checkIfWindowActivityChanged() - { - auto wasForeground = isForeground; - isForeground = Process::isForegroundProcess(); - return wasForeground != isForeground; - } - -private: - bool isForeground = false; -}; -#endif - - - class NVGContextListener { public: @@ -161,9 +141,5 @@ public Component, public Timer std::unique_ptr glContext; #endif -#if NANOVG_METAL_IMPLEMENTATION - BackgroundProcessChecker metalActivityChecker; -#endif - std::unique_ptr frameTimer; }; From 58b287770cd901f6de199e786c6a473e3aabef70 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sat, 11 May 2024 18:11:16 +0930 Subject: [PATCH 0742/1030] implement send/receive as label tags in search * fix directory line highlighting when in search results mode * fix reindexing of search not taking into account search string * add "send" "receive" keywords to search --- Source/Sidebar/SearchPanel.h | 69 +++++++++++----------- Source/Utility/ValueTreeNodeBranchLine.cpp | 10 ++++ Source/Utility/ValueTreeNodeBranchLine.h | 10 +--- Source/Utility/ValueTreeViewer.h | 62 ++++++++++++++++--- 4 files changed, 98 insertions(+), 53 deletions(-) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index 3f6dc8a2ce..a3095c5db7 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -105,6 +105,7 @@ class SearchPanel : public Component, public KeyListener, public Timer addAndMakeVisible(patchTree); addAndMakeVisible(input); + input.setTooltip("Use \"send\" or \"receive\" keyword to search symbols"); input.setJustification(Justification::centredLeft); input.setBorder({ 1, 23, 5, 1 }); } @@ -195,6 +196,7 @@ class SearchPanel : public Component, public KeyListener, public Timer cnv->pd->lockAudioThread(); // It locks inside of this anyway, so we might as well lock around it to prevent constantly locking/unlocking auto tree = generatePatchTree(cnv->refCountedPatch); patchTree.setValueTree(tree); + patchTree.filterNodes(); cnv->pd->unlockAudioThread(); } } @@ -278,13 +280,8 @@ class SearchPanel : public Component, public KeyListener, public Timer element.setProperty("TopLevel", reinterpret_cast(top), nullptr); } else { String finalFormatedName; - - auto formatSendRecieve = [](const String& send, const String& receive) -> String { - const String formatedSen = (send.isEmpty() || (send == "empty")) ? "" : " send: " + send; - const String formatedRcv = (receive.isEmpty() || (receive == "empty")) ? "" : String(formatedSen.isNotEmpty()? "," : "") + " rec: " + receive; - - return String(formatedSen + formatedRcv); - }; + String sendSymbol; + String receiveSymbol; switch(hash(type)){ // IEM send-receive symbols @@ -300,77 +297,70 @@ class SearchPanel : public Component, public KeyListener, public Timer case hash("vu"): case hash("cnv"): { - String receiveSym, sendSym; if (auto iemgui = objectPtr.get()) { t_symbol* srlsym[3]; iemgui_all_sym2dollararg(iemgui.get(), srlsym); if (srl_is_valid(srlsym[0])) { - sendSym = String::fromUTF8(iemgui->x_snd_unexpanded->s_name); + sendSymbol = String::fromUTF8(iemgui->x_snd_unexpanded->s_name); } if (srl_is_valid(srlsym[1])) { - receiveSym = String::fromUTF8(iemgui->x_rcv_unexpanded->s_name); + receiveSymbol = String::fromUTF8(iemgui->x_rcv_unexpanded->s_name); } } - finalFormatedName = nameWithoutArgs + formatSendRecieve(sendSym, receiveSym); + finalFormatedName = nameWithoutArgs; break; } case hash("keyboard"): { - String receiveSym, sendSym; if (auto keyboardObject = object.cast()) { - sendSym = String(keyboardObject->x_send->s_name); - receiveSym = String(keyboardObject->x_receive->s_name); + sendSymbol = String(keyboardObject->x_send->s_name); + receiveSymbol = String(keyboardObject->x_receive->s_name); } - finalFormatedName = nameWithoutArgs + formatSendRecieve(sendSym, receiveSym); + finalFormatedName = nameWithoutArgs; break; } case hash("pic"): { - String receiveSym, sendSym; if (auto picObject = object.cast()) { - sendSym = String(picObject->x_send->s_name); - receiveSym = String(picObject->x_receive->s_name); + sendSymbol = String(picObject->x_send->s_name); + sendSymbol = String(picObject->x_receive->s_name); } - finalFormatedName = nameWithoutArgs + formatSendRecieve(sendSym, receiveSym); + finalFormatedName = nameWithoutArgs; break; } case hash("scope~"): { - String receiveSym; if (auto scopeObject = object.cast()){ - receiveSym = String(scopeObject->x_receive->s_name); + receiveSymbol = String(scopeObject->x_receive->s_name); } - finalFormatedName = nameWithoutArgs + formatSendRecieve("", receiveSym); + finalFormatedName = nameWithoutArgs; break; } case hash("function"): { - String receiveSym, sendSym; if (auto keyboardObject = object.cast()) { - sendSym = String(keyboardObject->x_send->s_name); - receiveSym = String(keyboardObject->x_receive->s_name); + sendSymbol = String(keyboardObject->x_send->s_name); + receiveSymbol = String(keyboardObject->x_receive->s_name); } - finalFormatedName = nameWithoutArgs + formatSendRecieve(sendSym, receiveSym); + finalFormatedName = nameWithoutArgs; break; } case hash("note"): { - String receiveSym; if (auto noteObject = object.cast()) { - receiveSym = String(noteObject->x_receive->s_name); + receiveSymbol = String(noteObject->x_receive->s_name); } - finalFormatedName = nameWithoutArgs + formatSendRecieve("", receiveSym); + finalFormatedName = nameWithoutArgs; break; } case hash("knob"): { - String receiveSym, sendSym; if (auto knobObj = object.cast()) { - sendSym = String(knobObj->x_snd->s_name); - receiveSym = String(knobObj->x_rcv->s_name); + sendSymbol = String(knobObj->x_snd->s_name); + receiveSymbol = String(knobObj->x_rcv->s_name); } - finalFormatedName = nameWithoutArgs + formatSendRecieve(sendSym, receiveSym); + finalFormatedName = nameWithoutArgs; break; } case hash("gatom"): @@ -390,9 +380,9 @@ class SearchPanel : public Component, public KeyListener, public Timer default: break; } - const auto receive = String(gatomObject->a_symfrom->s_name); - const auto send = String(gatomObject->a_symto->s_name); - finalFormatedName = gatomName + formatSendRecieve(send, receive); + receiveSymbol = String(gatomObject->a_symfrom->s_name); + sendSymbol = String(gatomObject->a_symto->s_name); + finalFormatedName = gatomName; break; } // ============ no send-receive symbols ============ @@ -449,6 +439,13 @@ class SearchPanel : public Component, public KeyListener, public Timer } element.setProperty("Name", finalFormatedName, nullptr); + // Add send/receive tags if they exist + if (sendSymbol.isNotEmpty() && (sendSymbol != "empty")) { + element.setProperty("SendSymbol", sendSymbol, nullptr); + } + if (receiveSymbol.isNotEmpty() && (receiveSymbol != "empty")) { + element.setProperty("ReceiveSymbol", receiveSymbol, nullptr); + } element.setProperty("RightText", positionText, nullptr); element.setProperty("Icon", Icons::Object, nullptr); element.setProperty("Object", reinterpret_cast(object.cast()), nullptr); diff --git a/Source/Utility/ValueTreeNodeBranchLine.cpp b/Source/Utility/ValueTreeNodeBranchLine.cpp index 665c64e4a7..6b414c3abd 100644 --- a/Source/Utility/ValueTreeNodeBranchLine.cpp +++ b/Source/Utility/ValueTreeNodeBranchLine.cpp @@ -20,4 +20,14 @@ void ValueTreeNodeBranchLine::mouseUp(MouseEvent const& e) viewerComponent->getViewport().setViewPosition(0, nodePos - mousePosInViewport + (node->getHeight() * 0.5f)); viewerComponent->repaint(); } +} + +void ValueTreeNodeBranchLine::paint(Graphics& g) +{ + if (!treeLine.isEmpty()) { + auto colour = (isHover && !node->isOpenInSearchMode()) ? findColour(PlugDataColour::objectSelectedOutlineColourId) : findColour(PlugDataColour::panelTextColourId).withAlpha(0.25f); + + g.reduceClipRegion(treeLineImage, AffineTransform()); + g.fillAll(colour); + } } \ No newline at end of file diff --git a/Source/Utility/ValueTreeNodeBranchLine.h b/Source/Utility/ValueTreeNodeBranchLine.h index 9d5ff50d9f..bc62e61513 100644 --- a/Source/Utility/ValueTreeNodeBranchLine.h +++ b/Source/Utility/ValueTreeNodeBranchLine.h @@ -15,15 +15,7 @@ class ValueTreeNodeBranchLine : public Component, public SettableTooltipClient { } - void paint(Graphics& g) override - { - if (!treeLine.isEmpty()) { - auto colour = isHover ? findColour(PlugDataColour::objectSelectedOutlineColourId) : findColour(PlugDataColour::panelTextColourId).withAlpha(0.25f); - - g.reduceClipRegion(treeLineImage, AffineTransform()); - g.fillAll(colour); - } - } + void paint(Graphics& g) override; void mouseEnter(const MouseEvent& e) override { diff --git a/Source/Utility/ValueTreeViewer.h b/Source/Utility/ValueTreeViewer.h index 0e669daa23..3a84c04375 100644 --- a/Source/Utility/ValueTreeViewer.h +++ b/Source/Utility/ValueTreeViewer.h @@ -181,16 +181,45 @@ class ValueTreeNodeComponent : public Component iconColour = Colour::fromString(valueTreeNode.getProperty("IconColour").toString()); Fonts::drawIcon(g, valueTreeNode.getProperty("Icon"), itemBounds.removeFromLeft(22).reduced(2), iconColour, 12, false); } + + auto nameText = valueTreeNode.getProperty("Name"); + auto nameLength = Font(15).getStringWidth(nameText); + Fonts::drawFittedText(g, nameText, itemBounds.removeFromLeft(nameLength), colour); + + // draw send symbol label tag + if(valueTreeNode.hasProperty("SendSymbol")) + { + auto sendSymbolText = "s: " + valueTreeNode.getProperty("SendSymbol").toString(); + auto length = Font(15).getStringWidth(sendSymbolText); + auto sendColour = findColour(PlugDataColour::objectSelectedOutlineColourId); + g.setColour(sendColour); + auto tagBounds = itemBounds.removeFromLeft(length).translated(4, 0).reduced(0, 5).expanded(2, 0); + //g.fillRect(tagBounds.withTop(getHeight() * 0.5f)); + g.fillRoundedRectangle(tagBounds.toFloat(), Corners::defaultCornerRadius); + Fonts::drawFittedText(g, sendSymbolText, tagBounds.translated(2,0), sendColour.contrasting()); + itemBounds.translate(8,0); + } + // draw receive symbol label tag + if(valueTreeNode.hasProperty("ReceiveSymbol")) + { + auto receiveSymbolText = "r: " + valueTreeNode.getProperty("ReceiveSymbol").toString(); + auto length = Font(15).getStringWidth(receiveSymbolText); + auto recColour = findColour(PlugDataColour::objectSelectedOutlineColourId).withRotatedHue(0.5f); + g.setColour(recColour); + auto tagBounds = itemBounds.removeFromLeft(length).translated(4, 0).reduced(0, 5).expanded(2, 0); + //g.fillRect(tagBounds.withBottom(getHeight() * 0.5f)); + g.fillRoundedRectangle(tagBounds.toFloat(), Corners::defaultCornerRadius); + Fonts::drawFittedText(g, receiveSymbolText, tagBounds.translated(2,0), recColour.contrasting()); + } + if(valueTreeNode.hasProperty("RightText")) { auto text = valueTreeNode.getProperty("Name").toString(); auto rightText = valueTreeNode.getProperty("RightText").toString(); if(Font(15).getStringWidth(text + rightText) < itemBounds.getWidth() - 16) { - Fonts::drawFittedText(g, valueTreeNode.getProperty("RightText"), itemBounds.removeFromRight(Font(15).getStringWidth(rightText) + 4), colour.withAlpha(0.5f)); + Fonts::drawFittedText(g, valueTreeNode.getProperty("RightText"), getLocalBounds().removeFromRight(Font(15).getStringWidth(rightText) + 4).removeFromTop(25), colour.withAlpha(0.5f), Justification::topLeft); } } - - Fonts::drawFittedText(g, valueTreeNode.getProperty("Name"), itemBounds, colour); } void resized() override @@ -253,6 +282,8 @@ class ValueTreeNodeComponent : public Component ValueTree valueTreeNode; + bool isOpenInSearchMode() { return isOpenedBySearch; }; + private: ValueTreeNodeComponent* parent; SafePointer previous, next; @@ -498,22 +529,26 @@ class ValueTreeViewerComponent : public Component, public KeyListener, public Se void setFilterString(const String& toFilter) { filterString = toFilter; - + filterNodes(); + } + + void filterNodes() + { if(filterString.isEmpty()) { for (auto* topLevelNode : nodes) { clearSearch(topLevelNode); } - + resized(); return; } - + for (auto* topLevelNode : nodes) { - searchInNode(topLevelNode); + searchInNode(topLevelNode); } resized(); @@ -547,7 +582,18 @@ class ValueTreeViewerComponent : public Component, public KeyListener, public Se bool searchInNode(ValueTreeNodeComponent* node) { // Check if the current node matches the filterString - bool found = filterString.isEmpty() || node->valueTreeNode.getProperty("Name").toString().containsIgnoreCase(filterString); + bool found = false; + if (filterString.isEmpty() || + node->valueTreeNode.getProperty("Name").toString().containsIgnoreCase(filterString) || + // search over the send/receive tags + node->valueTreeNode.getProperty("SendSymbol").toString().containsIgnoreCase(filterString) || + node->valueTreeNode.getProperty("ReceiveSymbol").toString().containsIgnoreCase(filterString) || + // return all nodes that have send/receive for the patch with the keywords: "send" "receive" + (node->valueTreeNode.hasProperty("SendSymbol") && filterString.containsIgnoreCase("send")) || + (node->valueTreeNode.hasProperty("ReceiveSymbol") && filterString.containsIgnoreCase("receive")) ) + { + found = true; + } for (auto* child : node->nodes) { From 7b9cd172983f359414445451bd5a580c20092b30 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sat, 11 May 2024 18:21:54 +0930 Subject: [PATCH 0743/1030] make send receive search label corner less rounded --- Source/Utility/ValueTreeViewer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Utility/ValueTreeViewer.h b/Source/Utility/ValueTreeViewer.h index 3a84c04375..dcb630c26f 100644 --- a/Source/Utility/ValueTreeViewer.h +++ b/Source/Utility/ValueTreeViewer.h @@ -195,7 +195,7 @@ class ValueTreeNodeComponent : public Component g.setColour(sendColour); auto tagBounds = itemBounds.removeFromLeft(length).translated(4, 0).reduced(0, 5).expanded(2, 0); //g.fillRect(tagBounds.withTop(getHeight() * 0.5f)); - g.fillRoundedRectangle(tagBounds.toFloat(), Corners::defaultCornerRadius); + g.fillRoundedRectangle(tagBounds.toFloat(), Corners::defaultCornerRadius * 0.8f); Fonts::drawFittedText(g, sendSymbolText, tagBounds.translated(2,0), sendColour.contrasting()); itemBounds.translate(8,0); } @@ -208,7 +208,7 @@ class ValueTreeNodeComponent : public Component g.setColour(recColour); auto tagBounds = itemBounds.removeFromLeft(length).translated(4, 0).reduced(0, 5).expanded(2, 0); //g.fillRect(tagBounds.withBottom(getHeight() * 0.5f)); - g.fillRoundedRectangle(tagBounds.toFloat(), Corners::defaultCornerRadius); + g.fillRoundedRectangle(tagBounds.toFloat(), Corners::defaultCornerRadius * 0.8f); Fonts::drawFittedText(g, receiveSymbolText, tagBounds.translated(2,0), recColour.contrasting()); } From e0e85b3cca19614adc2b5c44e3cd298ed85c1fc3 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sat, 11 May 2024 19:23:49 +0930 Subject: [PATCH 0744/1030] use iolet visibility to control hittest & render --- Source/Iolet.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index 5d41d697a1..342fa8f6a4 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -53,7 +53,7 @@ Rectangle Iolet::getCanvasBounds() void Iolet::render(NVGcontext* nvg) { - if (getValue(presentationMode) || insideGraph || hideIolet) + if (!isVisible()) return; auto* fb = cnv->ioletBuffer; @@ -451,5 +451,6 @@ void Iolet::valueChanged(Value& v) void Iolet::setHidden(bool hidden) { hideIolet = hidden; + setVisible(!getValue(presentationMode) && !insideGraph && !hideIolet); repaint(); } From 9d0776e0f4b271d6ddebff9858d0393c4fb4c977 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 11 May 2024 16:23:10 +0200 Subject: [PATCH 0745/1030] Metal renderer fix --- Source/NVGSurface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 6a4e8a2130..e4d4bf84a3 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -294,6 +294,7 @@ void NVGSurface::render() fbHeight = scaledHeight; invalidArea = getLocalBounds(); scaleChanged = !approximatelyEqual(lastScaleFactor, pixelScale); + lastScaleFactor = pixelScale; } else if(!invalidArea.isEmpty()) { auto invalidated = invalidArea.expanded(1); @@ -369,7 +370,6 @@ void NVGSurface::render() glContext->initialiseOnThread(); nvg = nvgCreateContext(NVG_ANTIALIAS); #endif - lastScaleFactor = renderScale; invalidateAll(); From 00fa2637101c1ca710571467f081afcbe90a56cd Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 11 May 2024 21:40:29 +0200 Subject: [PATCH 0746/1030] Add legacy macOS build with lower minimum version, only Intel build, and openGL instead of Metal --- .github/workflows/cmake.yml | 72 ++++++++++++++++++++++++++++++++++++- CMakeLists.txt | 25 ++++--------- 2 files changed, 77 insertions(+), 20 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 9f6f910b27..879b324f1b 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -27,7 +27,77 @@ jobs: - name: Configure CMake working-directory: ${{github.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DBUILD_GEM_PLUGINS=1 + run: cmake $GITHUB_WORKSPACE -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + + - name: Check for Code-Signing secrets + id: secret-check + shell: bash + run: | + if [ "${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}" != '' ]; then + echo "available=true" >> $GITHUB_OUTPUT; + else + echo "available=false" >> $GITHUB_OUTPUT; + fi + + - name: Import Code-Signing Certificates + uses: figleafteam/import-codesign-certs@v2 + if: ${{ steps.secret-check.outputs.available == 'true' }} + with: + p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }} + p12-password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }} + + - name: Build + working-directory: ${{github.workspace}}/build + run: cmake --build . --config $BUILD_TYPE + + - name: Creating Installer + working-directory: ${{github.workspace}} + env: + AC_USERNAME: ${{ secrets.AC_USERNAME }} + AC_PASSWORD: ${{ secrets.AC_PASSWORD }} + run: ./.github/scripts/package-macOS.sh + + - name: Upload to server + env: + FTP_USERNAME: ${{ secrets.FTP_USERNAME }} + FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }} + GIT_HASH: ${{ github.sha }} + run: ./.github/scripts/upload-ftp.sh plugdata-macOS-Universal.pkg + + - name: Archive Artifacts + uses: actions/upload-artifact@v3 + with: + name: plugdata-macOS-Universal + path: plugdata-macOS-Universal.pkg + + - name: Release Artifacts + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + prerelease: true + draft: true + files: plugdata-macOS-Universal + + macos-legacy-build: + runs-on: macos-12 + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 0 + + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: macos + + - name: Create Build Environment + run: cmake -E make_directory ${{github.workspace}}/build + + - name: Configure CMake + working-directory: ${{github.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DNANOVG_METAL_IMPLEMENTATION=0 -DMACOS_LEGACY=1 - name: Check for Code-Signing secrets id: secret-check diff --git a/CMakeLists.txt b/CMakeLists.txt index 3bb3847699..0f15f5b41e 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,11 +4,11 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -option(RUN_CLANG_TIDY "" OFF) option(ENABLE_TESTING "" OFF) option(ENABLE_SFIZZ "" ON) option(ENABLE_GEM "" ON) option(ENABLE_ASAN "" OFF) +option(MACOS_LEGACY "" OFF) option(VERBOSE "" OFF) if(APPLE) @@ -32,16 +32,14 @@ endfunction() if(APPLE) set(CMAKE_XCODE_BUILD_SYSTEM "12" CACHE STRING "" FORCE) - set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Minimum OS X deployment version") - if(RUN_CLANG_TIDY) - set(CMAKE_OSX_ARCHITECTURES "arm64;" CACHE STRING "" FORCE) + if(MACOS_LEGACY) + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.11" CACHE STRING "Minimum OS X deployment version") + set(CMAKE_OSX_ARCHITECTURES "x86_64;" CACHE STRING "" FORCE) else() - set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE) + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") + set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE) endif() -endif() -if(RUN_CLANG_TIDY) - set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) endif() if(NOT CMAKE_BUILD_TYPE) @@ -542,17 +540,6 @@ set_target_properties(plugdata_midi PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PLUGDA set_target_properties(plugdata_midi PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PLUGDATA_PLUGINS_LOCATION}) endif() -if(RUN_CLANG_TIDY) - find_program( CLANG_TIDY_EXE NAMES "clang-tidy" DOC "Path to clang-tidy executable" ) - if(NOT CLANG_TIDY_EXE) - message(STATUS "clang-tidy not found.") - else() - message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") - set(DO_CLANG_TIDY "${CLANG_TIDY_EXE}" "-checks=clang-diagnostic-*,clang-diagnostic-unused-value,clang-analyzer-*,bugprone-*,performance-*,readability-*,-readability-magic-numbers,-readability-braces-around-statements,-readability-inconsistent-declaration-parameter-name,-readability-named-parameter --header-filter='.*'") - endif() - set_target_properties(plugdata_standalone PROPERTIES CXX_CLANG_TIDY "${DO_CLANG_TIDY}") -endif() - # Set up testing framework if(ENABLE_TESTING) From 0fbb4fc3100c08bbf1ac29d43769b30c0bbb97a8 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 11 May 2024 21:41:23 +0200 Subject: [PATCH 0747/1030] CI runner fix --- .github/workflows/cmake.yml | 8 ++++---- Source/Statusbar.cpp | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 879b324f1b..2d9f8812b0 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -132,13 +132,13 @@ jobs: FTP_USERNAME: ${{ secrets.FTP_USERNAME }} FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }} GIT_HASH: ${{ github.sha }} - run: ./.github/scripts/upload-ftp.sh plugdata-macOS-Universal.pkg + run: ./.github/scripts/upload-ftp.sh plugdata-macOS-Legacy.pkg - name: Archive Artifacts uses: actions/upload-artifact@v3 with: - name: plugdata-macOS-Universal - path: plugdata-macOS-Universal.pkg + name: plugdata-macOS-Legacy + path: plugdata-macOS-Legacy.pkg - name: Release Artifacts uses: softprops/action-gh-release@v1 @@ -146,7 +146,7 @@ jobs: with: prerelease: true draft: true - files: plugdata-macOS-Universal + files: plugdata-macOS-Legacy windows-64-build: runs-on: windows-2022 diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 6a83fd9609..503240dfcc 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -76,6 +76,7 @@ class LatencyDisplayButton : public Component, public MultiTimer, public Settabl alpha = pow(alpha, 2.2f); fading = true; if (alpha <= 0.01f) { + alpha = 0.0f; stopTimer(Animate); setVisible(false); } From 6e7b231d52afcb5befd79ae898db05c11e72600f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 11 May 2024 21:57:42 +0200 Subject: [PATCH 0748/1030] Small fix --- Source/Objects/ArrayObject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index 12c56e5aa8..0e8d3ed26a 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -1162,7 +1162,7 @@ class ArrayObject final : public ObjectBase { objectParameters.addParamSize(&sizeProperty); objectParameters.addParamCustom([_this = SafePointer(this)](){ - if(!_this) return static_cast(nullptr, nullptr); + if(!_this) return nullptr; Array> safeGraphs; for(auto* graph : _this->graphs) From 3de1d31927a989bc5be233a1415bcab50a82bf75 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 11 May 2024 22:14:45 +0200 Subject: [PATCH 0749/1030] Compilation fix --- Source/Objects/ArrayObject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index 0e8d3ed26a..5f986144d6 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -1162,7 +1162,7 @@ class ArrayObject final : public ObjectBase { objectParameters.addParamSize(&sizeProperty); objectParameters.addParamCustom([_this = SafePointer(this)](){ - if(!_this) return nullptr; + if(!_this) return static_cast(nullptr); Array> safeGraphs; for(auto* graph : _this->graphs) From 5a87f05fea9a91d61eb6761daaa6373e9718af37 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 11 May 2024 23:05:06 +0200 Subject: [PATCH 0750/1030] Fixes for nightly build upload --- .github/scripts/package-macOS.sh | 10 +++++----- .github/workflows/cmake.yml | 4 ++-- Source/NVGSurface.cpp | 1 - 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/scripts/package-macOS.sh b/.github/scripts/package-macOS.sh index 2b95052ade..2ffad7b24a 100755 --- a/.github/scripts/package-macOS.sh +++ b/.github/scripts/package-macOS.sh @@ -40,7 +40,7 @@ build_flavor() mkdir -p $TMPDIR cp -a $flavorprod $TMPDIR - + pkgbuild --analyze --root $TMPDIR ${PKG_DIR}/${PRODUCT_NAME}_${flavor}.plist plutil -replace BundleIsRelocatable -bool NO ${PKG_DIR}/${PRODUCT_NAME}_${flavor}.plist plutil -replace BundleIsVersionChecked -bool NO ${PKG_DIR}/${PRODUCT_NAME}_${flavor}.plist @@ -155,14 +155,14 @@ rm -r $PKG_DIR if [ -z "$AC_USERNAME" ]; then echo "No user name, skipping sign/notarize" # pretend that we signed the package and bail out - mv ${PRODUCT_NAME}.pkg ${PRODUCT_NAME}-MacOS-Universal.pkg + mv ${PRODUCT_NAME}.pkg ${PRODUCT_NAME}-MacOS-$1.pkg exit 0 fi # Sign installer -productsign -s "Developer ID Installer: Timothy Schoen (7SV7JPRR2L)" ${PRODUCT_NAME}.pkg ${PRODUCT_NAME}-MacOS-Universal.pkg +productsign -s "Developer ID Installer: Timothy Schoen (7SV7JPRR2L)" ${PRODUCT_NAME}.pkg ${PRODUCT_NAME}-MacOS-$1.pkg # Notarize installer xcrun notarytool store-credentials "notary_login" --apple-id ${AC_USERNAME} --password ${AC_PASSWORD} --team-id "7SV7JPRR2L" -xcrun notarytool submit ./plugdata-MacOS-Universal.pkg --keychain-profile "notary_login" --wait -xcrun stapler staple "plugdata-MacOS-Universal.pkg" +xcrun notarytool submit ./plugdata-MacOS-$1.pkg --keychain-profile "notary_login" --wait +xcrun stapler staple "plugdata-MacOS-$1.pkg" diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 2d9f8812b0..0927261426 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -55,7 +55,7 @@ jobs: env: AC_USERNAME: ${{ secrets.AC_USERNAME }} AC_PASSWORD: ${{ secrets.AC_PASSWORD }} - run: ./.github/scripts/package-macOS.sh + run: ./.github/scripts/package-macOS.sh Universal - name: Upload to server env: @@ -125,7 +125,7 @@ jobs: env: AC_USERNAME: ${{ secrets.AC_USERNAME }} AC_PASSWORD: ${{ secrets.AC_PASSWORD }} - run: ./.github/scripts/package-macOS.sh + run: ./.github/scripts/package-macOS.sh Legacy - name: Upload to server env: diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index e4d4bf84a3..afbf39db0f 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -355,7 +355,6 @@ void NVGSurface::render() mainFBO = nullptr; nvg = nullptr; - auto renderScale = getRenderScale(); #ifdef NANOVG_METAL_IMPLEMENTATION auto* peer = getPeer()->getNativeHandle(); auto* view = OSUtils::MTLCreateView(peer, 0, 0, getWidth(), getHeight()); From 12d3daa5fa6ff1513df17c1849d9d3b9415af2f4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 11 May 2024 23:28:04 +0200 Subject: [PATCH 0751/1030] Metal compilation fix --- Source/NVGSurface.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index afbf39db0f..d6aaa03b1b 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -356,6 +356,7 @@ void NVGSurface::render() nvg = nullptr; #ifdef NANOVG_METAL_IMPLEMENTATION + auto renderScale = getRenderScale(); auto* peer = getPeer()->getNativeHandle(); auto* view = OSUtils::MTLCreateView(peer, 0, 0, getWidth(), getHeight()); setView(view); From 86801f87a522957d08ce21501667386558ff604b Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sun, 12 May 2024 16:45:15 +0930 Subject: [PATCH 0752/1030] improve look of symbol label highlight symbols when they are objects [r][receive][s][send] return all symbols when using wildcard 'symbols' turn search into token and search --- Source/Sidebar/SearchPanel.h | 36 ++++++++++++++++---------- Source/Utility/ValueTreeViewer.h | 43 +++++++++++++++++++------------- 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index a3095c5db7..b384dacfc9 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -91,12 +91,12 @@ class SearchPanel : public Component, public KeyListener, public Timer input.addKeyListener(this); patchTree.addKeyListener(this); - + patchTree.onClick = [this](ValueTree& tree){ auto* ptr = reinterpret_cast(static_cast(tree.getProperty("Object"))); editor->highlightSearchTarget(ptr, true); }; - + patchTree.onSelect = [this](ValueTree& tree){ auto* ptr = reinterpret_cast(static_cast(tree.getProperty("TopLevel"))); editor->highlightSearchTarget(ptr, false); @@ -105,12 +105,13 @@ class SearchPanel : public Component, public KeyListener, public Timer addAndMakeVisible(patchTree); addAndMakeVisible(input); - input.setTooltip("Use \"send\" or \"receive\" keyword to search symbols"); + // TODO: dismiss this tooltip when the input text editor is active! + input.setTooltip("Use \"send\" or \"receive\" keyword to search symbols, \"symbols\" show all symbols"); input.setJustification(Justification::centredLeft); input.setBorder({ 1, 23, 5, 1 }); } - - + + bool keyPressed(KeyPress const& key, Component* originatingComponent) override { return false; @@ -120,7 +121,7 @@ class SearchPanel : public Component, public KeyListener, public Timer { patchTree.clearValueTree(); } - + void timerCallback() override { auto* cnv = editor->getCurrentCanvas(); @@ -131,7 +132,7 @@ class SearchPanel : public Component, public KeyListener, public Timer updateResults(); } } - + void visibilityChanged() override { if(isVisible()) @@ -142,7 +143,7 @@ class SearchPanel : public Component, public KeyListener, public Timer stopTimer(); } } - + void lookAndFeelChanged() override { input.setColour(TextEditor::backgroundColourId, Colours::transparentBlack); @@ -188,7 +189,7 @@ class SearchPanel : public Component, public KeyListener, public Timer return std::unique_ptr(settingsCalloutButton); } - + void updateResults() { auto* cnv = editor->getCurrentCanvas(); @@ -200,12 +201,12 @@ class SearchPanel : public Component, public KeyListener, public Timer cnv->pd->unlockAudioThread(); } } - + void grabFocus() { input.grabKeyboardFocus(); } - + void resized() override { auto tableBounds = getLocalBounds(); @@ -244,7 +245,7 @@ class SearchPanel : public Component, public KeyListener, public Timer pd::Patch::Ptr subpatch = new pd::Patch(objectPtr, editor->pd, false); ValueTree subpatchTree = generatePatchTree(subpatch, top); element.copyPropertiesAndChildrenFrom(subpatchTree, nullptr); - + if(auto patchPtr = subpatch->getPointer()) { if(patchPtr->gl_list) @@ -272,7 +273,7 @@ class SearchPanel : public Component, public KeyListener, public Timer name = nameWithoutArgs; } } - + element.setProperty("Name", name, nullptr); element.setProperty("RightText", positionText, nullptr); element.setProperty("Icon", canvas_isabstraction(subpatch->getPointer().get()) ? Icons::File : Icons::Object, nullptr); @@ -434,6 +435,15 @@ class SearchPanel : public Component, public KeyListener, public Timer default: { finalFormatedName = name; + if ((nameWithoutArgs == "s") || (nameWithoutArgs == "send")) { + sendSymbol = name.fromFirstOccurrenceOf(" ", false, true).upToFirstOccurrenceOf(" ", false, true); + element.setProperty("SymbolIsObject", 1, nullptr); + finalFormatedName = nameWithoutArgs; + } else if ((nameWithoutArgs == "r") || (nameWithoutArgs == "receive")) { + receiveSymbol = name.fromFirstOccurrenceOf(" ", false, true).upToFirstOccurrenceOf(" ", false, true); + element.setProperty("SymbolIsObject", 1, nullptr); + finalFormatedName = nameWithoutArgs; + } break; } } diff --git a/Source/Utility/ValueTreeViewer.h b/Source/Utility/ValueTreeViewer.h index dcb630c26f..989387c2c7 100644 --- a/Source/Utility/ValueTreeViewer.h +++ b/Source/Utility/ValueTreeViewer.h @@ -189,27 +189,27 @@ class ValueTreeNodeComponent : public Component // draw send symbol label tag if(valueTreeNode.hasProperty("SendSymbol")) { - auto sendSymbolText = "s: " + valueTreeNode.getProperty("SendSymbol").toString(); + auto sendSymbolText = (valueTreeNode.hasProperty("SymbolIsObject") ? "" : "s: ") + valueTreeNode.getProperty("SendSymbol").toString(); auto length = Font(15).getStringWidth(sendSymbolText); auto sendColour = findColour(PlugDataColour::objectSelectedOutlineColourId); - g.setColour(sendColour); + g.setColour(sendColour.withAlpha(0.15f)); auto tagBounds = itemBounds.removeFromLeft(length).translated(4, 0).reduced(0, 5).expanded(2, 0); //g.fillRect(tagBounds.withTop(getHeight() * 0.5f)); g.fillRoundedRectangle(tagBounds.toFloat(), Corners::defaultCornerRadius * 0.8f); - Fonts::drawFittedText(g, sendSymbolText, tagBounds.translated(2,0), sendColour.contrasting()); + Fonts::drawFittedText(g, sendSymbolText, tagBounds.translated(2,0), sendColour); itemBounds.translate(8,0); } // draw receive symbol label tag if(valueTreeNode.hasProperty("ReceiveSymbol")) { - auto receiveSymbolText = "r: " + valueTreeNode.getProperty("ReceiveSymbol").toString(); + auto receiveSymbolText = (valueTreeNode.hasProperty("SymbolIsObject") ? "" : "r: ") + valueTreeNode.getProperty("ReceiveSymbol").toString(); auto length = Font(15).getStringWidth(receiveSymbolText); auto recColour = findColour(PlugDataColour::objectSelectedOutlineColourId).withRotatedHue(0.5f); - g.setColour(recColour); + g.setColour(recColour.withAlpha(0.15f)); auto tagBounds = itemBounds.removeFromLeft(length).translated(4, 0).reduced(0, 5).expanded(2, 0); //g.fillRect(tagBounds.withBottom(getHeight() * 0.5f)); g.fillRoundedRectangle(tagBounds.toFloat(), Corners::defaultCornerRadius * 0.8f); - Fonts::drawFittedText(g, receiveSymbolText, tagBounds.translated(2,0), recColour.contrasting()); + Fonts::drawFittedText(g, receiveSymbolText, tagBounds.translated(2,0), recColour); } if(valueTreeNode.hasProperty("RightText")) @@ -582,18 +582,27 @@ class ValueTreeViewerComponent : public Component, public KeyListener, public Se bool searchInNode(ValueTreeNodeComponent* node) { // Check if the current node matches the filterString - bool found = false; - if (filterString.isEmpty() || - node->valueTreeNode.getProperty("Name").toString().containsIgnoreCase(filterString) || - // search over the send/receive tags - node->valueTreeNode.getProperty("SendSymbol").toString().containsIgnoreCase(filterString) || - node->valueTreeNode.getProperty("ReceiveSymbol").toString().containsIgnoreCase(filterString) || - // return all nodes that have send/receive for the patch with the keywords: "send" "receive" - (node->valueTreeNode.hasProperty("SendSymbol") && filterString.containsIgnoreCase("send")) || - (node->valueTreeNode.hasProperty("ReceiveSymbol") && filterString.containsIgnoreCase("receive")) ) - { - found = true; + int found = 0; + StringArray searchTokens; + searchTokens.addTokens(filterString, " ", ""); + for (auto& token : searchTokens) { + if (token.isEmpty() || + node->valueTreeNode.getProperty("Name").toString().containsIgnoreCase(token) || + // search over the send/receive tags + node->valueTreeNode.getProperty("SendSymbol").toString().containsIgnoreCase(token) || + node->valueTreeNode.getProperty("ReceiveSymbol").toString().containsIgnoreCase(token) || + // return all nodes that have send/receive for the patch with the keywords: "send" "receive" + (node->valueTreeNode.hasProperty("SendSymbol") && (token == "send")) || + (node->valueTreeNode.hasProperty("ReceiveSymbol") && (token == "receive")) || + // return all nodes that have send or recieve when keyword is "symbols" + ((token == "symbols") && (node->valueTreeNode.hasProperty("SendSymbol") || node->valueTreeNode.hasProperty("ReceiveSymbol"))) ) + { + found++; + } } + + // attempt at implementing an 'and' search, all search text tokens need to be true + found = searchTokens.size() == found; for (auto* child : node->nodes) { From 537d77ad1539ae8ceedcb99f00ec169744b8e567 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sun, 12 May 2024 17:17:33 +0930 Subject: [PATCH 0753/1030] quick fix for hiding right text X:Y in search --- Source/Utility/ValueTreeViewer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Utility/ValueTreeViewer.h b/Source/Utility/ValueTreeViewer.h index 989387c2c7..115c1ad451 100644 --- a/Source/Utility/ValueTreeViewer.h +++ b/Source/Utility/ValueTreeViewer.h @@ -216,7 +216,7 @@ class ValueTreeNodeComponent : public Component { auto text = valueTreeNode.getProperty("Name").toString(); auto rightText = valueTreeNode.getProperty("RightText").toString(); - if(Font(15).getStringWidth(text + rightText) < itemBounds.getWidth() - 16) { + if((itemBounds.getWidth() - Font(15).getStringWidth(rightText)) >= 8) { Fonts::drawFittedText(g, valueTreeNode.getProperty("RightText"), getLocalBounds().removeFromRight(Font(15).getStringWidth(rightText) + 4).removeFromTop(25), colour.withAlpha(0.5f), Justification::topLeft); } } From f7b496238339133035e23497d0c787cc291e985b Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sun, 12 May 2024 17:34:43 +0930 Subject: [PATCH 0754/1030] add audio "~" send / receive --- Source/Sidebar/SearchPanel.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index b384dacfc9..213a8a2e24 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -435,11 +435,11 @@ class SearchPanel : public Component, public KeyListener, public Timer default: { finalFormatedName = name; - if ((nameWithoutArgs == "s") || (nameWithoutArgs == "send")) { + if ((nameWithoutArgs == "s") || (nameWithoutArgs == "s~") || (nameWithoutArgs == "send") || (nameWithoutArgs == "send~")) { sendSymbol = name.fromFirstOccurrenceOf(" ", false, true).upToFirstOccurrenceOf(" ", false, true); element.setProperty("SymbolIsObject", 1, nullptr); finalFormatedName = nameWithoutArgs; - } else if ((nameWithoutArgs == "r") || (nameWithoutArgs == "receive")) { + } else if ((nameWithoutArgs == "r") || (nameWithoutArgs == "r~") || (nameWithoutArgs == "receive") || (nameWithoutArgs == "receive~")) { receiveSymbol = name.fromFirstOccurrenceOf(" ", false, true).upToFirstOccurrenceOf(" ", false, true); element.setProperty("SymbolIsObject", 1, nullptr); finalFormatedName = nameWithoutArgs; From 2e8a3d2bb34872acb9a9f50fb8c27e12dbd4d319 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 12 May 2024 14:46:51 +0200 Subject: [PATCH 0755/1030] Fixed potential crash when saving --- Source/Pd/Patch.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Source/Pd/Patch.cpp b/Source/Pd/Patch.cpp index 87af832c81..1b1044a96a 100644 --- a/Source/Pd/Patch.cpp +++ b/Source/Pd/Patch.cpp @@ -115,11 +115,10 @@ void Patch::savePatch(URL const& locationURL) pd::Interface::saveToFile(patch.get(), file, dir); #endif + currentFile = location; + currentURL = locationURL; instance->reloadAbstractions(location, patch.get()); } - - currentFile = location; - currentURL = locationURL; } t_glist* Patch::getRoot() From f52f48bbb124b1ecc4d60ef51a06ce26f3ce75a5 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 12 May 2024 16:04:20 +0200 Subject: [PATCH 0756/1030] Fixed broken connection message display in DAWs --- Source/PluginEditor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index a7975495a8..eff784d962 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -344,6 +344,9 @@ PluginEditor::PluginEditor(PluginProcessor& p) connectionMessageDisplay = std::make_unique(this); connectionMessageDisplay->addToDesktop(ComponentPeer::windowIsTemporary | ComponentPeer::windowIgnoresKeyPresses | ComponentPeer::windowIgnoresMouseClicks); + if(!ProjectInfo::isStandalone) { + connectionMessageDisplay->setAlwaysOnTop(true); + } // This cannot be done in MidiDeviceManager's constructor because SettingsFile is not yet initialised at that time if (ProjectInfo::isStandalone) { From 11b8aacd4c2b4227bb6d62f58e754ae780b4f6e1 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 12 May 2024 16:28:40 +0200 Subject: [PATCH 0757/1030] Fixed potential autosave crash --- Source/Utility/Autosave.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/Utility/Autosave.h b/Source/Utility/Autosave.h index 80a14320ac..b2fe6f7947 100644 --- a/Source/Utility/Autosave.h +++ b/Source/Utility/Autosave.h @@ -81,8 +81,8 @@ class Autosave : public Timer if (!getValue(autosaveEnabled)) return; - pd->enqueueFunctionAsync([this]() { - save(); + pd->enqueueFunctionAsync([_this = WeakReference(this)]() { + if(_this) _this->save(); }); } @@ -170,6 +170,7 @@ class Autosave : public Timer } friend class AutosaveHistoryComponent; + JUCE_DECLARE_WEAK_REFERENCEABLE(Autosave); }; class AutosaveHistoryComponent : public Component { From eaa8f7fe64257f7ec9684a1d9c427108d802d409 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 12 May 2024 16:29:04 +0200 Subject: [PATCH 0758/1030] More accurate intersection test when shift-dragging objects into connections --- Source/Connection.cpp | 16 +++------------- Source/Connection.h | 2 +- Source/Object.cpp | 9 +++++---- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/Source/Connection.cpp b/Source/Connection.cpp index 0c6b77ac76..3958db9f21 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -1026,12 +1026,12 @@ void Connection::updatePath() cachedIsValid = false; } -bool Connection::intersectsRectangle(Rectangle invalidatedArea) +bool Connection::intersectsRectangle(Rectangle rectToIntersect) { - if(invalidatedArea.contains(getBounds())) + if(rectToIntersect.contains(getBounds())) return true; - return clipRegion.intersectsRectangle(invalidatedArea); + return clipRegion.intersectsRectangle(rectToIntersect); } void Connection::applyBestPath() @@ -1218,16 +1218,6 @@ int Connection::findLatticePaths(PathPlan& bestPath, PathPlan& pathStack, Point< return count; } -bool Connection::intersectsObject(Object* object) const -{ - auto path = getPath(); - auto b = object->getBounds().toFloat(); - return path.intersectsLine({ b.getTopLeft(), b.getTopRight() }) - || path.intersectsLine({ b.getTopLeft(), b.getBottomLeft() }) - || path.intersectsLine({ b.getBottomRight(), b.getBottomLeft() }) - || path.intersectsLine({ b.getBottomRight(), b.getTopRight() }); -} - bool Connection::straightLineIntersectsObject(Line toCheck, Array& objects) { diff --git a/Source/Connection.h b/Source/Connection.h index b4227ca337..b741318f59 100644 --- a/Source/Connection.h +++ b/Source/Connection.h @@ -69,7 +69,7 @@ class Connection : public DrawablePath bool isSegmented() const; void setSegmented(bool segmented); - bool intersectsRectangle(Rectangle invalidatedArea); + bool intersectsRectangle(Rectangle rectToIntersect); void render(NVGcontext* nvg) override; void nvgContextDeleted(NVGcontext* nvg) override; diff --git a/Source/Object.cpp b/Source/Object.cpp index e786748891..41ed93f27d 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -1128,8 +1128,8 @@ void Object::mouseDrag(MouseEvent const& e) } // Behaviour for shift-dragging objects over - if (ds.objectSnappingInbetween) { - if (ds.connectionToSnapInbetween->intersectsObject(ds.objectSnappingInbetween)) { + if (ds.objectSnappingInbetween && !ds.objectSnappingInbetween->iolets.isEmpty()) { + if (ds.connectionToSnapInbetween->intersectsRectangle(ds.objectSnappingInbetween->iolets[0]->getCanvasBounds())) { return; } @@ -1141,10 +1141,11 @@ void Object::mouseDrag(MouseEvent const& e) if (e.mods.isShiftDown() && selection.size() == 1) { auto* object = selection.getFirst(); - if (object->numInputs && object->numOutputs) { + if (object->numInputs && object->numOutputs && !object->iolets.isEmpty()) { bool intersected = false; for (auto* connection : cnv->connections) { - if (connection->intersectsObject(object)) { + + if (connection->intersectsRectangle(object->iolets[0]->getCanvasBounds())) { object->iolets[0]->isTargeted = true; object->iolets[object->numInputs]->isTargeted = true; object->iolets[0]->repaint(); From 97485e6991c633e204f25ec5faad1c26a8391415 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 12 May 2024 16:44:36 +0200 Subject: [PATCH 0759/1030] Fixed IEM label issues --- Source/Objects/IEMHelper.h | 12 +++++++----- Source/Objects/ObjectBase.h | 14 ++++++++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Source/Objects/IEMHelper.h b/Source/Objects/IEMHelper.h index 39df6d7588..59a16f3bfa 100644 --- a/Source/Objects/IEMHelper.h +++ b/Source/Objects/IEMHelper.h @@ -231,6 +231,7 @@ class IEMHelper { setLabelPosition({ getValue(labelX), getValue(labelY) }); gui->updateLabel(); } else if (v.refersToSameSourceAs(labelHeight)) { + gui->limitValueMin(labelHeight, 0.f); setFontHeight(getValue(labelHeight)); gui->updateLabel(); } else if (v.refersToSameSourceAs(labelText)) { @@ -306,17 +307,18 @@ class IEMHelper { Rectangle getLabelBounds() { - auto objectBounds = object->getBounds().reduced(Object::margin); + auto const objectBounds = object->getBounds().reduced(Object::margin); if (auto iemgui = ptr.get()) { t_symbol const* sym = canvas_realizedollar(iemgui->x_glist, iemgui->x_lab); if (sym) { - int fontHeight = getFontHeight(); - int fontWidth = sys_fontwidth(fontHeight); + auto const labelText = getExpandedLabelText(); + int const fontHeight = getFontHeight(); + int const fontWidth = sys_fontwidth(fontHeight); int const posx = objectBounds.getX() + iemgui->x_ldx; int const posy = objectBounds.getY() + iemgui->x_ldy; - - return { posx, posy, fontWidth * (getExpandedLabelText().length() + 1), fontHeight + 2 }; + int const textWidth = fontHeight > 55 ? Font(fontHeight).getStringWidth(labelText) : fontWidth * (labelText.length() + 1); + return { posx, posy, textWidth, fontHeight + 2 }; } } diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index fb40eb553d..3071e477fe 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -74,12 +74,14 @@ class ObjectLabel : public Label, public NVGComponent, public NVGContextListener { auto componentImage = createComponentSnapshot(Rectangle(0, 0, getWidth() + 1, getHeight()), false, scale); - if(imageId && lastWidth == getWidth() && lastHeight == getHeight()) { - imageId = NVGImageRenderer::convertImage(nvg, componentImage, imageId); - } - else { - if(imageId) nvgDeleteImage(nvg, imageId); - imageId = NVGImageRenderer::convertImage(nvg, componentImage); + if(!componentImage.isNull()) { + if(imageId && lastWidth == getWidth() && lastHeight == getHeight()) { + imageId = NVGImageRenderer::convertImage(nvg, componentImage, imageId); + } + else { + if(imageId) nvgDeleteImage(nvg, imageId); + imageId = NVGImageRenderer::convertImage(nvg, componentImage); + } } } From bb25800526ca8b7664296761e9daf1b86248533b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 12 May 2024 19:29:44 +0200 Subject: [PATCH 0760/1030] Link latency value in settings and statusbar, fixed draggablenumber bug --- Source/Components/DraggableNumber.h | 6 ------ Source/Components/PropertiesPanel.h | 3 ++- Source/Dialogs/AudioSettingsPanel.h | 6 +++--- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index 23aac202ea..b1634ddc4a 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -87,12 +87,6 @@ class DraggableNumber : public Label min = minimum; } - void setMinMax(double minimum, double maximum) - { - setMinimum(minimum); - setMaximum(maximum); - } - void setLogarithmicHeight(double logHeight) { logarithmicHeight = logHeight; diff --git a/Source/Components/PropertiesPanel.h b/Source/Components/PropertiesPanel.h index 7127ffce4e..5dd99ad3f9 100644 --- a/Source/Components/PropertiesPanel.h +++ b/Source/Components/PropertiesPanel.h @@ -793,7 +793,8 @@ class PropertiesPanel : public Component { draggableNumber->getTextValue().referTo(property); draggableNumber->setFont(draggableNumber->getFont().withHeight(14)); draggableNumber->setEditableOnClick(true); - draggableNumber->setMinMax(minimum, maximum); + if(minimum != 0.0f) draggableNumber->setMinimum(minimum); + if(maximum != 0.0f) draggableNumber->setMaximum(maximum); draggableNumber->onEditorShow = [draggableNumber]() { auto* editor = draggableNumber->getCurrentTextEditor(); diff --git a/Source/Dialogs/AudioSettingsPanel.h b/Source/Dialogs/AudioSettingsPanel.h index 64063da34c..b306d30f94 100644 --- a/Source/Dialogs/AudioSettingsPanel.h +++ b/Source/Dialogs/AudioSettingsPanel.h @@ -456,7 +456,7 @@ class DAWAudioSettings : public SettingsDialogPanel , public Value::Listener { public: - explicit DAWAudioSettings(AudioProcessor* p) + explicit DAWAudioSettings(PluginProcessor* p) : processor(p) { auto settingsTree = SettingsFile::getInstance()->getValueTree(); @@ -491,11 +491,11 @@ class DAWAudioSettings : public SettingsDialogPanel void valueChanged(Value& v) override { if (v.refersToSameSourceAs(latencyValue)) { - processor->setLatencySamples(getValue(latencyValue)); + processor->performLatencyCompensationChange(getValue(latencyValue)); } } - AudioProcessor* processor; + PluginProcessor* processor; Value latencyValue; Value tailLengthValue; From e775d7c8388ea39a85ef27c9746b94ecd02e9d16 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Mon, 13 May 2024 21:24:31 +0930 Subject: [PATCH 0761/1030] use an arrow flag for symbol receive & send --- Source/Sidebar/SearchPanel.h | 47 +++++++++++++---- Source/Utility/ValueTreeViewer.h | 91 +++++++++++++++++++++++++------- 2 files changed, 107 insertions(+), 31 deletions(-) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index 213a8a2e24..1abb91ad16 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -240,6 +240,10 @@ class SearchPanel : public Component, public KeyListener, public Timer auto nameWithoutArgs = name.upToFirstOccurrenceOf(" ", false, false); auto positionText = " (" + String(x) + ":" + String(y) + ")"; + auto getFirstArgumentFromFullName = [](const String& fullName) -> String { + return fullName.fromFirstOccurrenceOf(" ", false, true).upToFirstOccurrenceOf(" ", false, true); + }; + ValueTree element("Object"); if (type == "canvas" || type == "graph") { pd::Patch::Ptr subpatch = new pd::Patch(objectPtr, editor->pd, false); @@ -273,7 +277,13 @@ class SearchPanel : public Component, public KeyListener, public Timer name = nameWithoutArgs; } } - +#ifdef SHOW_PD_SUBPATCH_SYMBOL + if (nameWithoutArgs == "pd") { + auto arg = getFirstArgumentFromFullName(name); + if (arg.isNotEmpty()) + element.setProperty("PDSymbol", nameWithoutArgs + "-" + arg, nullptr); + } +#endif element.setProperty("Name", name, nullptr); element.setProperty("RightText", positionText, nullptr); element.setProperty("Icon", canvas_isabstraction(subpatch->getPointer().get()) ? Icons::File : Icons::Object, nullptr); @@ -386,7 +396,6 @@ class SearchPanel : public Component, public KeyListener, public Timer finalFormatedName = gatomName; break; } - // ============ no send-receive symbols ============ case hash("message"): { finalFormatedName = "msg: " + name; @@ -432,17 +441,33 @@ class SearchPanel : public Component, public KeyListener, public Timer finalFormatedName = nameWithoutArgs; break; } + default: { - finalFormatedName = name; - if ((nameWithoutArgs == "s") || (nameWithoutArgs == "s~") || (nameWithoutArgs == "send") || (nameWithoutArgs == "send~")) { - sendSymbol = name.fromFirstOccurrenceOf(" ", false, true).upToFirstOccurrenceOf(" ", false, true); - element.setProperty("SymbolIsObject", 1, nullptr); - finalFormatedName = nameWithoutArgs; - } else if ((nameWithoutArgs == "r") || (nameWithoutArgs == "r~") || (nameWithoutArgs == "receive") || (nameWithoutArgs == "receive~")) { - receiveSymbol = name.fromFirstOccurrenceOf(" ", false, true).upToFirstOccurrenceOf(" ", false, true); - element.setProperty("SymbolIsObject", 1, nullptr); - finalFormatedName = nameWithoutArgs; + switch (hash(nameWithoutArgs)) { + case hash("s"): + case hash("s~"): + case hash("send"): + case hash("send~"): + case hash("throw~"): { + sendSymbol = getFirstArgumentFromFullName(name); + element.setProperty("SymbolIsObject", 1, nullptr); + finalFormatedName = nameWithoutArgs; + break; + } + case hash("r"): + case hash("r~"): + case hash("receive"): + case hash("receive~"): + case hash("catch~"): { + receiveSymbol = getFirstArgumentFromFullName(name); + element.setProperty("SymbolIsObject", 1, nullptr); + finalFormatedName = nameWithoutArgs; + break; + } + default: + finalFormatedName = name; + break; } break; } diff --git a/Source/Utility/ValueTreeViewer.h b/Source/Utility/ValueTreeViewer.h index 115c1ad451..99e987ff74 100644 --- a/Source/Utility/ValueTreeViewer.h +++ b/Source/Utility/ValueTreeViewer.h @@ -186,30 +186,81 @@ class ValueTreeNodeComponent : public Component auto nameLength = Font(15).getStringWidth(nameText); Fonts::drawFittedText(g, nameText, itemBounds.removeFromLeft(nameLength), colour); - // draw send symbol label tag - if(valueTreeNode.hasProperty("SendSymbol")) - { - auto sendSymbolText = (valueTreeNode.hasProperty("SymbolIsObject") ? "" : "s: ") + valueTreeNode.getProperty("SendSymbol").toString(); + const auto tagCornerRadius = Corners::defaultCornerRadius * 0.7f; + +#ifdef SHOW_PD_SUBPATCH_SYMBOL + if (valueTreeNode.hasProperty("PDSymbol")) { + // draw generic symbol label tag + // ╭──────────╮ + // │ symbol │ + // ╰──────────╯ + auto sendSymbolText = valueTreeNode.getProperty("PDSymbol").toString(); auto length = Font(15).getStringWidth(sendSymbolText); - auto sendColour = findColour(PlugDataColour::objectSelectedOutlineColourId); - g.setColour(sendColour.withAlpha(0.15f)); + auto sendColour = findColour(PlugDataColour::objectSelectedOutlineColourId).withRotatedHue(0.25); + g.setColour(sendColour.withAlpha(0.2f)); auto tagBounds = itemBounds.removeFromLeft(length).translated(4, 0).reduced(0, 5).expanded(2, 0); - //g.fillRect(tagBounds.withTop(getHeight() * 0.5f)); g.fillRoundedRectangle(tagBounds.toFloat(), Corners::defaultCornerRadius * 0.8f); - Fonts::drawFittedText(g, sendSymbolText, tagBounds.translated(2,0), sendColour); - itemBounds.translate(8,0); - } - // draw receive symbol label tag - if(valueTreeNode.hasProperty("ReceiveSymbol")) + Fonts::drawFittedText(g, sendSymbolText, tagBounds.translated(2, 0), sendColour); + itemBounds.translate(8, 0); + } else +#endif { - auto receiveSymbolText = (valueTreeNode.hasProperty("SymbolIsObject") ? "" : "r: ") + valueTreeNode.getProperty("ReceiveSymbol").toString(); - auto length = Font(15).getStringWidth(receiveSymbolText); - auto recColour = findColour(PlugDataColour::objectSelectedOutlineColourId).withRotatedHue(0.5f); - g.setColour(recColour.withAlpha(0.15f)); - auto tagBounds = itemBounds.removeFromLeft(length).translated(4, 0).reduced(0, 5).expanded(2, 0); - //g.fillRect(tagBounds.withBottom(getHeight() * 0.5f)); - g.fillRoundedRectangle(tagBounds.toFloat(), Corners::defaultCornerRadius * 0.8f); - Fonts::drawFittedText(g, receiveSymbolText, tagBounds.translated(2,0), recColour); + // draw receive symbol label tag + // a──b───────────╮ + // \ │ │ + // c symbol │ + // / │ │ + // e──d───────────╯ + if (valueTreeNode.hasProperty("ReceiveSymbol")) { + auto receiveSymbolText = (valueTreeNode.hasProperty("SymbolIsObject") ? "" : "r: ") + + valueTreeNode.getProperty("ReceiveSymbol").toString(); + auto length = Font(15).getStringWidth(receiveSymbolText); + auto recColour = findColour(PlugDataColour::objectSelectedOutlineColourId); + g.setColour(recColour.withAlpha(0.2f)); + auto tagBounds = itemBounds.removeFromLeft(length).translated(4, 0).reduced(0, 5).expanded(2, 0).toFloat(); + Path flag; + const Point a = tagBounds.getTopLeft().toFloat(); + const Point b = Point(tagBounds.getX() + tagBounds.getHeight() * 0.5f, tagBounds.getY()); + const Point c = Point(tagBounds.getX() + tagBounds.getHeight() * 0.5f, tagBounds.getCentreY()); + const Point d = Point(tagBounds.getX() + tagBounds.getHeight() * 0.5f, tagBounds.getBottom()); + const Point e = tagBounds.getBottomLeft().toFloat(); + flag.startNewSubPath(a); + flag.lineTo(b); + flag.lineTo(c); + flag.closeSubPath(); + flag.startNewSubPath(c); + flag.lineTo(d); + flag.lineTo(e); + flag.closeSubPath(); + flag.addRoundedRectangle(b.getX(), b.getY(), tagBounds.getWidth(), tagBounds.getHeight(), tagCornerRadius, tagCornerRadius, false, true, false, true); + g.fillPath(flag); + Fonts::drawFittedText(g, receiveSymbolText, tagBounds.translated(2 + tagBounds.getHeight() * 0.5f, 0).toNearestIntEdges(), recColour); + itemBounds.translate(16, 0); + } + // draw send symbol label tag + // ╭────────── a + // │ │ \ + // │ symbol │ b + // │ │ / + // ╰────────── c + if (valueTreeNode.hasProperty("SendSymbol")) { + auto sendSymbolText = (valueTreeNode.hasProperty("SymbolIsObject") ? "" : "s: ") + valueTreeNode.getProperty("SendSymbol").toString(); + auto length = Font(15).getStringWidth(sendSymbolText); + auto sendColour = findColour(PlugDataColour::objectSelectedOutlineColourId).withRotatedHue(0.5f); + g.setColour(sendColour.withAlpha(0.2f)); + auto tagBounds = itemBounds.removeFromLeft(length).translated(4, 0).reduced(0, 5).expanded(2, 0).toFloat(); + Path flag; + const Point a = tagBounds.getTopRight().toFloat(); + const Point b = Point(tagBounds.getRight() + (tagBounds.getHeight() * 0.5f), tagBounds.getCentreY()); + const Point c = tagBounds.getBottomRight().toFloat(); + flag.startNewSubPath(a); + flag.lineTo(b); + flag.lineTo(c); + flag.closeSubPath(); + flag.addRoundedRectangle(tagBounds.getX(), tagBounds.getY(), tagBounds.getWidth(),tagBounds.getHeight(), tagCornerRadius, tagCornerRadius, true, false, true,false); + g.fillPath(flag); + Fonts::drawFittedText(g, sendSymbolText, tagBounds.translated(2, 0).toNearestIntEdges(), sendColour); + } } if(valueTreeNode.hasProperty("RightText")) From 0027b82541c9474b74e3c036eddab220ed9369e3 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 13 May 2024 16:39:51 +0200 Subject: [PATCH 0762/1030] Simplified cached text rendering, fixed graph titles not rendering --- Source/Objects/CommentObject.h | 2 +- Source/Objects/GraphOnParent.h | 8 ++- Source/Objects/MessageObject.h | 2 +- Source/Objects/ScalarObject.h | 3 +- Source/Objects/TextObject.h | 2 +- Source/Pd/Instance.h | 2 +- Source/PluginEditor.cpp | 8 +++ Source/PluginEditor.h | 1 + .../{StringUtils.h => CachedStringWidth.h} | 49 ------------------- Source/Utility/CachedTextRender.h | 17 +++---- Source/Utility/SettingsFile.h | 2 +- 11 files changed, 31 insertions(+), 65 deletions(-) rename Source/Utility/{StringUtils.h => CachedStringWidth.h} (64%) diff --git a/Source/Objects/CommentObject.h b/Source/Objects/CommentObject.h index 6db0b713e3..1e963ee369 100644 --- a/Source/Objects/CommentObject.h +++ b/Source/Objects/CommentObject.h @@ -49,7 +49,7 @@ class CommentObject final : public ObjectBase { if (!editor) { auto textArea = border.subtractedFrom(getLocalBounds()); - textRenderer.renderText(nvg, getText(), Fonts::getDefaultFont().withHeight(15), LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::commentTextColourId), textArea, getImageScale(), getValue(sizeProperty)); + textRenderer.renderText(nvg, textArea, getImageScale()); } else { imageRenderer.renderComponentFromImage(nvg, *editor, getImageScale()); diff --git a/Source/Objects/GraphOnParent.h b/Source/Objects/GraphOnParent.h index 510fca10cb..6e7e613af4 100644 --- a/Source/Objects/GraphOnParent.h +++ b/Source/Objects/GraphOnParent.h @@ -93,9 +93,15 @@ class GraphOnParent final : public ObjectBase { void resized() override { + textRenderer.prepareLayout(getText(), Fonts::getDefaultFont().withHeight(13), LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId), getWidth(), getWidth()); updateCanvas(); updateDrawables(); } + + void lookAndFeelChanged() override + { + textRenderer.prepareLayout(getText(), Fonts::getDefaultFont().withHeight(13), LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId), getWidth(), getWidth()); + } // Called by object to make sure clicks on empty parts of the graph are passed on bool canReceiveMouseEvent(int x, int y) override @@ -217,7 +223,7 @@ class GraphOnParent final : public ObjectBase { // Strangly, the title goes below the graph content in pd if (!getValue(hideNameAndArgs) && getText() != "graph") { auto text = getText(); - textRenderer.renderText(nvg, text, Fonts::getDefaultFont().withHeight(13), LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId), Rectangle(5, 0, getWidth() - 5, 16), getImageScale(), getWidth()); + textRenderer.renderText(nvg, Rectangle(5, 0, getWidth() - 5, 16), getImageScale()); } Canvas* topLevel = cnv; diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index ed4f357da5..6916bcf615 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -191,7 +191,7 @@ class MessageObject final : public ObjectBase } else { auto text = getText(); - textRenderer.renderText(nvg, text, Fonts::getDefaultFont().withHeight(15), LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId), border.subtractedFrom(getLocalBounds()), getImageScale(), getValue(sizeProperty)); + textRenderer.renderText(nvg, border.subtractedFrom(getLocalBounds()), getImageScale()); } } diff --git a/Source/Objects/ScalarObject.h b/Source/Objects/ScalarObject.h index 2e760be17a..4ea09efc49 100644 --- a/Source/Objects/ScalarObject.h +++ b/Source/Objects/ScalarObject.h @@ -383,7 +383,8 @@ class DrawableSymbol final : public DrawableTemplate auto bounds = getBoundingBox().getBoundingBox().toNearestInt(); nvgSave(nvg); nvgTranslate(nvg, bounds.getX(), bounds.getY()); - textRenderer.renderText(nvg, getText(), getFont(), getColour(), bounds.withZeroOrigin(), scale, getWidth()); + textRenderer.prepareLayout(getText(), getFont(), getColour(), getWidth(), getWidth()); + textRenderer.renderText(nvg, bounds.withZeroOrigin(), scale); nvgRestore(nvg); } diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index ec2f600df6..050785f445 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -271,7 +271,7 @@ class TextBase : public ObjectBase // we could render at the actual scale, but that makes the transition to scolling/zooming pretty rough // Instead, rendering at 2x scale gives us pretty good sharpness overall - cachedTextRender.renderText(nvg, text, Fonts::getDefaultFont().withHeight(15), LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId), textArea, getImageScale(), getValue(sizeProperty)); + cachedTextRender.renderText(nvg, textArea, getImageScale()); } } diff --git a/Source/Pd/Instance.h b/Source/Pd/Instance.h index 00a91b1b9f..02832be3ad 100644 --- a/Source/Pd/Instance.h +++ b/Source/Pd/Instance.h @@ -13,7 +13,7 @@ extern "C" { #include #include -#include "Utility/StringUtils.h" +#include "Utility/CachedStringWidth.h" #include "Patch.h" class ObjectImplementationManager; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index eff784d962..b1687a21a3 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1885,6 +1885,14 @@ void PluginEditor::parentHierarchyChanged() grabKeyboardFocus(); } +void PluginEditor::broughtToFront() +{ + if(isShowing() || isOnDesktop()) + grabKeyboardFocus(); + + if(openedDialog) openedDialog->toFront(true); +} + void PluginEditor::commandKeyChanged(bool isHeld) { if (isHeld) { diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 5c9a0070e1..4598f8e8e2 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -88,6 +88,7 @@ class PluginEditor : public AudioProcessorEditor void resized() override; void parentSizeChanged() override; void parentHierarchyChanged() override; + void broughtToFront() override; // For dragging parent window void mouseDrag(MouseEvent const& e) override; diff --git a/Source/Utility/StringUtils.h b/Source/Utility/CachedStringWidth.h similarity index 64% rename from Source/Utility/StringUtils.h rename to Source/Utility/CachedStringWidth.h index a95505f700..84e725c3a5 100644 --- a/Source/Utility/StringUtils.h +++ b/Source/Utility/CachedStringWidth.h @@ -6,55 +6,6 @@ #pragma once -struct StringUtils { - - inline static constexpr uint64_t num_items = 1ul << (sizeof(char) * 8ul); - - std::array widths {}; - - explicit StringUtils(Font const& font) - { - for (int i = 0; i < num_items; i++) { - widths[i] = font.getStringWidth(String(std::string(1, (char)i))); - } - } - - float getStringWidth(String const& text) const - { - float totalWidth = 0.0f; - - auto* utf8 = text.toRawUTF8(); - auto numBytes = text.getNumBytesAsUTF8(); - - for (int i = 0; i < numBytes; i++) { - totalWidth += widths[(int)utf8[i]]; - } - - // In real text, letters are slightly closer together - return totalWidth * 0.95f; - } - - static float getPreciseStringWidth(String const& text, Font const& font) - { - float maxLineLength = 0; - for (auto& line : StringArray::fromLines(text)) { - maxLineLength = std::max(maxLineLength, font.getStringWidthFloat(line)); - } - - return maxLineLength; - } - - // used by console for a more optimised calculation - static int getNumLines(int width, int stringWidth) - { - // On startup, width might be zero, this is an optimisation for that case - if (width == 0) - return 0; - - return std::max(round(static_cast(stringWidth) / (width - 38.0f)), 1); - } -}; - template struct CachedStringWidth { diff --git a/Source/Utility/CachedTextRender.h b/Source/Utility/CachedTextRender.h index 282376b947..1bd50bd40a 100644 --- a/Source/Utility/CachedTextRender.h +++ b/Source/Utility/CachedTextRender.h @@ -20,12 +20,13 @@ class CachedTextRender : public NVGContextListener imageId = 0; } - void renderText(NVGcontext* nvg, String const& text, Font const& font, Colour const& colour, Rectangle const& bounds, float scale, int width) + void renderText(NVGcontext* nvg, Rectangle const& bounds, float scale) { - if(updateImage || imageId <= 0 || lastTextHash != hash(text) || scale != lastScale || colour != lastColour || lastWidth != width) + if(updateImage || imageId <= 0 || lastRenderBounds != bounds || lastScale != scale) { - layoutReady = false; - renderTextToImage(nvg, text, font, colour, Rectangle(bounds.getX(), bounds.getY(), bounds.getWidth() + 3, bounds.getHeight()), scale, width); + renderTextToImage(nvg, Rectangle(bounds.getX(), bounds.getY(), bounds.getWidth() + 3, bounds.getHeight()), scale); + lastRenderBounds = bounds; + lastScale = scale; updateImage = false; } @@ -60,11 +61,10 @@ class CachedTextRender : public NVGContextListener updateImage = true; } - layoutReady = true; return needsUpdate; } - void renderTextToImage(NVGcontext* nvg, String const& text, Font const& font, const Colour& colour, Rectangle const& bounds, float scale, int cachedWidth) + void renderTextToImage(NVGcontext* nvg, Rectangle const& bounds, float scale) { int width = std::floor(bounds.getWidth() * scale); int height = std::floor(bounds.getHeight() * scale); @@ -98,7 +98,7 @@ class CachedTextRender : public NVGContextListener } } - if(imageId && imageWidth == width && imageHeight == height && scale == lastScale) { + if(imageId && imageWidth == width && imageHeight == height) { nvgUpdateImage(nvg, imageId, pixelData); } else { @@ -106,7 +106,6 @@ class CachedTextRender : public NVGContextListener imageId = nvgCreateImageRGBA(nvg, width, height, NVG_IMAGE_PREMULTIPLIED, pixelData); imageWidth = width; imageHeight = height; - lastScale = scale; } } @@ -125,8 +124,8 @@ class CachedTextRender : public NVGContextListener Colour lastColour; int lastWidth = 0; int idealWidth = 0, idealHeight = 0; + Rectangle lastRenderBounds; TextLayout layout; - bool layoutReady = false; bool updateImage = false; }; diff --git a/Source/Utility/SettingsFile.h b/Source/Utility/SettingsFile.h index 782954bcf5..c75860f009 100644 --- a/Source/Utility/SettingsFile.h +++ b/Source/Utility/SettingsFile.h @@ -127,7 +127,7 @@ class SettingsFile : public ValueTree::Listener { "centre_sidepanel_buttons", var(true) }, { "show_all_audio_device_rates", var(false) }, { "add_object_menu_pinned", var(false) }, - { "autosave_interval", var(120) }, + { "autosave_interautosave_interval", var(120) }, { "autosave_enabled", var(1) }, { "macos_buttons", #if JUCE_MAC From 11280740924788b722d67bd01592f1b7d172ad75 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 13 May 2024 17:35:57 +0200 Subject: [PATCH 0763/1030] Fixed potential crash in [keyboard] object --- Source/Objects/KeyboardObject.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/Objects/KeyboardObject.h b/Source/Objects/KeyboardObject.h index bdc6fed064..ebbb5690ad 100644 --- a/Source/Objects/KeyboardObject.h +++ b/Source/Objects/KeyboardObject.h @@ -283,11 +283,12 @@ class KeyboardObject final : public ObjectBase sendSymbol = sndSym != "empty" ? sndSym : ""; receiveSymbol = rcvSym != "empty" ? rcvSym : ""; - MessageManager::callAsync([this] { - updateAspectRatio(); - - // Call async to make sure pd obj has updated - object->updateBounds(); + MessageManager::callAsync([_this = SafePointer(this)] { + if(_this) { + _this->updateAspectRatio(); + // Call async to make sure pd obj has updated + _this->object->updateBounds(); + } }); } } From d84766a7fad89447222e7b61ae9e1da50d68baad Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 13 May 2024 17:38:26 +0200 Subject: [PATCH 0764/1030] Async fixes --- Source/Sidebar/PaletteItem.cpp | 8 +++++--- Source/Standalone/PlugDataWindow.h | 16 +++++++++------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Source/Sidebar/PaletteItem.cpp b/Source/Sidebar/PaletteItem.cpp index b6d6e78dc3..5915544cb5 100644 --- a/Source/Sidebar/PaletteItem.cpp +++ b/Source/Sidebar/PaletteItem.cpp @@ -276,9 +276,11 @@ void PaletteItem::deleteItem() MessageManager::callAsync([this, parentTree, itemTree = this->itemTree, _paletteComp = SafePointer(paletteComp)]() mutable { parentTree.removeChild(itemTree, nullptr); auto paletteComponent = findParentComponentOfClass(); - _paletteComp->items.removeObject(this); - paletteComponent->resized(); - _paletteComp->resized(); + if(_paletteComp) { + _paletteComp->items.removeObject(this); + paletteComponent->resized(); + _paletteComp->resized(); + } }); } diff --git a/Source/Standalone/PlugDataWindow.h b/Source/Standalone/PlugDataWindow.h index 8834981a7b..58d5e21932 100644 --- a/Source/Standalone/PlugDataWindow.h +++ b/Source/Standalone/PlugDataWindow.h @@ -220,11 +220,13 @@ class StandalonePluginHolder : private AudioIODeviceCallback { { if (settings != nullptr) { // Async to give the app a chance to start up before loading the patch - MessageManager::callAsync([this]() { - MemoryOutputStream data; - Base64::convertFromBase64(data, settings->getValue("filterState")); - if (data.getDataSize() > 0) - processor->setStateInformation(data.getData(), static_cast(data.getDataSize())); + MessageManager::callAsync([_this = SafePointer(this)]() { + if(_this) { + MemoryOutputStream data; + Base64::convertFromBase64(data, settings->getValue("filterState")); + if (data.getDataSize() > 0) + processor->setStateInformation(data.getData(), static_cast(data.getDataSize())); + } }); } } @@ -654,8 +656,8 @@ class PlugDataWindow : public DocumentWindow if (auto* content = getContentComponent()) { content->resized(); content->repaint(); - MessageManager::callAsync([content] { - if (content->isShowing()) + MessageManager::callAsync([content = SafePointer(content)] { + if (content && content->isShowing()) content->grabKeyboardFocus(); }); } From 88080441d28dd68544de67ddfd4110518b7dd9b9 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Tue, 14 May 2024 02:22:20 +0930 Subject: [PATCH 0765/1030] fix compiling - to use safepointer the class needs to inherit Component VUMeter has no ability to send symbols (filter out "nosndno" also) --- Source/Objects/VUMeterObject.h | 1 - Source/Sidebar/SearchPanel.h | 2 +- Source/Standalone/PlugDataWindow.h | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/Objects/VUMeterObject.h b/Source/Objects/VUMeterObject.h index a416b8caec..c10595cd51 100644 --- a/Source/Objects/VUMeterObject.h +++ b/Source/Objects/VUMeterObject.h @@ -22,7 +22,6 @@ class VUMeterObject final : public ObjectBase { objectParameters.addParamSize(&sizeProperty); objectParameters.addParamReceiveSymbol(&iemHelper.receiveSymbol); - objectParameters.addParamSendSymbol(&iemHelper.sendSymbol, "nosndno"); iemHelper.addIemParameters(objectParameters, false, false, -1); } diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index 1abb91ad16..baef367c12 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -475,7 +475,7 @@ class SearchPanel : public Component, public KeyListener, public Timer element.setProperty("Name", finalFormatedName, nullptr); // Add send/receive tags if they exist - if (sendSymbol.isNotEmpty() && (sendSymbol != "empty")) { + if (sendSymbol.isNotEmpty() && (sendSymbol != "empty") && (sendSymbol != "nosndno")) { element.setProperty("SendSymbol", sendSymbol, nullptr); } if (receiveSymbol.isNotEmpty() && (receiveSymbol != "empty")) { diff --git a/Source/Standalone/PlugDataWindow.h b/Source/Standalone/PlugDataWindow.h index 58d5e21932..680fd9455c 100644 --- a/Source/Standalone/PlugDataWindow.h +++ b/Source/Standalone/PlugDataWindow.h @@ -74,7 +74,7 @@ class PlugDataProcessorPlayer : public AudioProcessorPlayer { MidiDeviceManager midiDeviceManager; }; -class StandalonePluginHolder : private AudioIODeviceCallback { +class StandalonePluginHolder : private AudioIODeviceCallback, public Component { public: /** Structure used for the number of inputs and outputs. */ struct PluginInOuts { @@ -220,7 +220,7 @@ class StandalonePluginHolder : private AudioIODeviceCallback { { if (settings != nullptr) { // Async to give the app a chance to start up before loading the patch - MessageManager::callAsync([_this = SafePointer(this)]() { + MessageManager::callAsync([this, _this = SafePointer(this)]() { if(_this) { MemoryOutputStream data; Base64::convertFromBase64(data, settings->getValue("filterState")); From 73837eeb84c5284165af9c3462a8a1a92019ec41 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Tue, 14 May 2024 02:39:15 +0930 Subject: [PATCH 0766/1030] fix typo --- Source/Sidebar/SearchPanel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index baef367c12..ff7151b84a 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -334,7 +334,7 @@ class SearchPanel : public Component, public KeyListener, public Timer { if (auto picObject = object.cast()) { sendSymbol = String(picObject->x_send->s_name); - sendSymbol = String(picObject->x_receive->s_name); + receiveSymbol = String(picObject->x_receive->s_name); } finalFormatedName = nameWithoutArgs; break; From 15a13f1f139d08be08ea7e04ed980bd7c8112737 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 13 May 2024 20:00:37 +0200 Subject: [PATCH 0767/1030] Fixed crash for multichannel connections with more than 8 channels --- Source/Dialogs/ConnectionMessageDisplay.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Dialogs/ConnectionMessageDisplay.h b/Source/Dialogs/ConnectionMessageDisplay.h index 12064114f4..c2f3503b35 100644 --- a/Source/Dialogs/ConnectionMessageDisplay.h +++ b/Source/Dialogs/ConnectionMessageDisplay.h @@ -57,7 +57,7 @@ class ConnectionMessageDisplay if (activeConnection.getComponent()) { mousePosition = screenPosition; isSignalDisplay = activeConnection->outlet->isSignal; - lastNumChannels = activeConnection->numSignalChannels; + lastNumChannels = std::min(activeConnection->numSignalChannels, 8); startTimer(MouseHoverDelay, mouseDelay); stopTimer(MouseHoverExitDelay); if (isSignalDisplay) { @@ -171,8 +171,8 @@ class ConnectionMessageDisplay SignalBlock block; while (sampleQueue.try_dequeue(block)) { if (i < numBlocks) { - lastNumChannels = block.numChannels; - for (int ch = 0; ch < block.numChannels; ch++) { + lastNumChannels = std::min(8, block.numChannels); + for (int ch = 0; ch < lastNumChannels; ch++) { std::copy(block.samples + ch * DEFDACBLKSIZE, block.samples + ch * DEFDACBLKSIZE + DEFDACBLKSIZE, lastSamples[ch] + (i * DEFDACBLKSIZE)); } } From 9ea1ed5d047fb04a598741ef1f3ed6b5c571e1c5 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 13 May 2024 20:43:58 +0200 Subject: [PATCH 0768/1030] Multichannel signal fixes --- Source/Connection.cpp | 2 +- Source/Dialogs/ConnectionMessageDisplay.h | 4 ++-- Source/PluginEditor.cpp | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Source/Connection.cpp b/Source/Connection.cpp index 3958db9f21..fd1d1ba8a6 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -934,7 +934,7 @@ int Connection::getSignalData(t_float* output, int maxChannels) { if (auto oc = ptr.get()) { if (auto* signal = outconnect_get_signal(oc.get())) { - auto numChannels = std::min(signal->s_nchans, maxChannels); + auto numChannels = std::min(signal->s_nchans, maxChannels-1); auto* samples = signal->s_vec; if (!samples) return 0; diff --git a/Source/Dialogs/ConnectionMessageDisplay.h b/Source/Dialogs/ConnectionMessageDisplay.h index c2f3503b35..d6f66c7134 100644 --- a/Source/Dialogs/ConnectionMessageDisplay.h +++ b/Source/Dialogs/ConnectionMessageDisplay.h @@ -57,7 +57,7 @@ class ConnectionMessageDisplay if (activeConnection.getComponent()) { mousePosition = screenPosition; isSignalDisplay = activeConnection->outlet->isSignal; - lastNumChannels = std::min(activeConnection->numSignalChannels, 8); + lastNumChannels = std::min(activeConnection->numSignalChannels, 7); startTimer(MouseHoverDelay, mouseDelay); stopTimer(MouseHoverExitDelay); if (isSignalDisplay) { @@ -171,7 +171,7 @@ class ConnectionMessageDisplay SignalBlock block; while (sampleQueue.try_dequeue(block)) { if (i < numBlocks) { - lastNumChannels = std::min(8, block.numChannels); + lastNumChannels = std::min(block.numChannels, 7); for (int ch = 0; ch < lastNumChannels; ch++) { std::copy(block.samples + ch * DEFDACBLKSIZE, block.samples + ch * DEFDACBLKSIZE + DEFDACBLKSIZE, lastSamples[ch] + (i * DEFDACBLKSIZE)); } diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index b1687a21a3..65e5e51a69 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1201,10 +1201,8 @@ void PluginEditor::getCommandInfo(CommandID const commandID, ApplicationCommandI result.setInfo("Undo", "Undo action", "General", 0); result.addDefaultKeypress(90, ModifierKeys::commandModifier); result.setActive(canUndo); - break; } - case CommandIDs::Redo: { result.setInfo("Redo", "Redo action", "General", 0); result.addDefaultKeypress(90, ModifierKeys::commandModifier | ModifierKeys::shiftModifier); From fe333692d791d47d07877830e042e611e3f4626d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 14 May 2024 00:20:39 +0200 Subject: [PATCH 0769/1030] Revert "simpler way to stop errant mouseup on iolet" This reverts commit 0a727748defa897397880db1f8f9e1608d19bc69. --- Source/Canvas.cpp | 1 + Source/Canvas.h | 2 ++ Source/Iolet.cpp | 16 +++++++++++----- Source/Iolet.h | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index ef2a91a4c8..876fc1fa3f 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -1237,6 +1237,7 @@ void Canvas::dragAndDropPaste(String const& patchString, Point mousePos, in { locked = false; presentationMode = false; + objectAdded = true; // force the valueChanged to run, and wait for them to return locked.getValueSource().sendChangeMessage(true); diff --git a/Source/Canvas.h b/Source/Canvas.h index f981a8eac9..12da0cbe52 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -251,6 +251,8 @@ class Canvas : public Component Array> drawables; + bool objectAdded = false; + private: GlobalMouseListener globalMouseListener; diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index 342fa8f6a4..5851c29172 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -149,20 +149,26 @@ void Iolet::mouseDrag(MouseEvent const& e) void Iolet::mouseDown(MouseEvent const& e) { mouseIsDown = true; + + // make sure that intentional clicks on iolet get through after a DnD action + cnv->objectAdded = false; } void Iolet::mouseUp(MouseEvent const& e) { - // make sure only intentional mouse down/up events trigger iolet events - // click dragging objects onto canvas can give us an errant mouseup if cursor is over iolet - if (!mouseIsDown) - return; - mouseIsDown = false; if (getValue(locked) || e.mods.isRightButtonDown()) return; + // If an object is addded to canvas, the mouse can potentially be over an iolet when dropped onto canvas + // This situation could cause a cable to be created when an object is added + // Disregard mouseup if this happens + if (cnv->objectAdded) { + cnv->objectAdded = false; + return; + } + // This might end up calling Canvas::synchronise, at which point we are not sure this class will survive, so we do an async call bool shiftIsDown = e.mods.isShiftDown(); diff --git a/Source/Iolet.h b/Source/Iolet.h index 60a7e56c95..802a9b2d1c 100644 --- a/Source/Iolet.h +++ b/Source/Iolet.h @@ -53,7 +53,7 @@ class Iolet : public Component Canvas* cnv; private: - bool mouseIsDown = false; + bool mouseIsDown; bool const insideGraph; bool hideIolet = false; From 05998a4cdbb2fe93bdfc8ffcbdb80d49940cbb06 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Tue, 14 May 2024 13:41:40 +0930 Subject: [PATCH 0770/1030] fix label colour updating --- Source/Objects/ArrayObject.h | 2 +- Source/Objects/AtomHelper.h | 2 +- Source/Objects/IEMHelper.h | 4 ++-- Source/Objects/ObjectBase.h | 14 +++++++++++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index 5f986144d6..6145a786af 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -1284,7 +1284,7 @@ class ArrayObject final : public ObjectBase { label->setBounds(bounds); label->setText(title, dontSendNotification); - label->setColour(Label::textColourId, LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); + label->setColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); object->cnv->addAndMakeVisible(label.get()); } diff --git a/Source/Objects/AtomHelper.h b/Source/Objects/AtomHelper.h index 867188fae2..00b0926c2a 100644 --- a/Source/Objects/AtomHelper.h +++ b/Source/Objects/AtomHelper.h @@ -296,7 +296,7 @@ class AtomHelper { textColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasBackgroundColourId).contrasting(); } - label->setColour(Label::textColourId, textColour); + label->setColour(textColour); object->cnv->addAndMakeVisible(label.get()); } else { diff --git a/Source/Objects/IEMHelper.h b/Source/Objects/IEMHelper.h index 59a16f3bfa..8ac54f595b 100644 --- a/Source/Objects/IEMHelper.h +++ b/Source/Objects/IEMHelper.h @@ -145,7 +145,7 @@ class IEMHelper { if(auto* label = gui->getLabel()) { - label->setColour(Label::textColourId, getLabelColour()); + label->setColour(getLabelColour()); } gui->repaint(); @@ -295,7 +295,7 @@ class IEMHelper { label->setBounds(bounds + offset); label->setText(text, dontSendNotification); - label->setColour(Label::textColourId, getLabelColour()); + label->setColour(getLabelColour()); label->setVisible(true); } else { diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index 3071e477fe..657c591ae8 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -30,6 +30,8 @@ class ObjectLabel : public Label, public NVGComponent, public NVGContextListener int lastWidth = 0, lastHeight = 0; float lastScale = 1.0f; NVGSurface& surface; + bool updateColour = false; + Colour lastColour; public: explicit ObjectLabel(NVGSurface& s) : NVGComponent(this), surface(s) @@ -55,13 +57,14 @@ class ObjectLabel : public Label, public NVGComponent, public NVGContextListener void renderLabel(NVGcontext* nvg, float scale) { auto textHash = hash(getText()); - if(!imageId || lastTextHash != textHash || lastScale != scale || lastWidth != getWidth() || lastHeight != getHeight()) + if(!imageId || updateColour || lastTextHash != textHash || lastScale != scale || lastWidth != getWidth() || lastHeight != getHeight()) { updateImage(nvg, scale); lastTextHash = textHash; lastScale = scale; lastWidth = getWidth(); lastHeight = getHeight(); + updateColour = false; } nvgBeginPath(nvg); @@ -69,6 +72,15 @@ class ObjectLabel : public Label, public NVGComponent, public NVGContextListener nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, getWidth() + 1, getHeight(), 0, imageId, 1.0f)); nvgFill(nvg); } + + void setColour(const Colour colour) + { + if (colour != lastColour) { + Label::setColour(Label::textColourId, colour); + lastColour = colour; + updateColour = true; + } + } void updateImage(NVGcontext* nvg, float scale) { From e7bab3b49fa6c8c83b048b30e703b830282fa124 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 14 May 2024 13:19:59 +0200 Subject: [PATCH 0771/1030] Implement latch/toggle/bang for [button] --- Resources/Documentation/ELSE/button.md | 4 +- Source/Objects/BangObject.h | 12 ++--- Source/Objects/ButtonObject.h | 67 +++++++++++++++++++++++--- 3 files changed, 68 insertions(+), 15 deletions(-) diff --git a/Resources/Documentation/ELSE/button.md b/Resources/Documentation/ELSE/button.md index 7f8a49d192..df95c628dc 100644 --- a/Resources/Documentation/ELSE/button.md +++ b/Resources/Documentation/ELSE/button.md @@ -47,9 +47,9 @@ flags: methods: - type: latch description: sets to latch mode - - type: tgl + - type: toggle description: sets to toggle mode - - type: bng + - type: bang description: sets to bang mode - type: dim description: sets horizontal and vertical size in pixels diff --git a/Source/Objects/BangObject.h b/Source/Objects/BangObject.h index 9963eac9e9..432203abb0 100644 --- a/Source/Objects/BangObject.h +++ b/Source/Objects/BangObject.h @@ -165,16 +165,14 @@ class BangObject final : public ObjectBase { lastBang = currentTime; - auto deletionChecker = SafePointer(this); Timer::callAfterDelay(holdTime, - [deletionChecker, this]() mutable { + [_this = SafePointer(this)]() mutable { // First check if this object still exists - if (!deletionChecker) - return; + if (!_this) return; - if (bangState) { - bangState = false; - repaint(); + if (_this->bangState) { + _this->bangState = false; + _this->repaint(); } }); } diff --git a/Source/Objects/ButtonObject.h b/Source/Objects/ButtonObject.h index 3c98a82492..db01b4697c 100644 --- a/Source/Objects/ButtonObject.h +++ b/Source/Objects/ButtonObject.h @@ -12,6 +12,15 @@ class ButtonObject : public ObjectBase { Value primaryColour = SynchronousValue(); Value secondaryColour = SynchronousValue(); Value sizeProperty = SynchronousValue(); + + enum Mode + { + Latch, + Toggle, + Bang + }; + + Mode mode; public: ButtonObject(pd::WeakReference obj, Object* parent) @@ -32,6 +41,18 @@ class ButtonObject : public ObjectBase { primaryColour = Colour(button->x_fgcolor[0], button->x_fgcolor[1], button->x_fgcolor[2]).toString(); secondaryColour = Colour(button->x_bgcolor[0], button->x_bgcolor[1], button->x_bgcolor[2]).toString(); sizeProperty = button->x_w; + if(button->x_mode == 0) + { + mode = Latch; + } + else if(button->x_mode == 1) + { + mode = Toggle; + } + else + { + mode = Bang; + } } repaint(); @@ -105,11 +126,38 @@ class ButtonObject : public ObjectBase { if (!e.mods.isLeftButtonDown()) return; - if (auto button = ptr.get()) { - outlet_float(button->x_obj.ob_outlet, 1); + if(mode == Latch) { + state = true; + } + else if(mode == Toggle){ + state = !state; } - state = true; + + if(mode == Bang) + { + state = true; + if (auto button = ptr.get()) { + outlet_bang(button->x_obj.ob_outlet); + } + Timer::callAfterDelay(250, + [_this = SafePointer(this)]() mutable { + // First check if this object still exists + if (!_this) return; + if (_this->state) { + _this->state = false; + _this->repaint(); + } + }); + } + else { + if (auto button = ptr.get()) { + outlet_float(button->x_obj.ob_outlet, state); + } + } + + + // Make sure we don't re-click with an accidental drag alreadyTriggered = true; @@ -119,9 +167,11 @@ class ButtonObject : public ObjectBase { void mouseUp(MouseEvent const& e) override { alreadyTriggered = false; - state = false; - if (auto button = ptr.get()) { - outlet_float(button->x_obj.ob_outlet, 0); + if(mode == Latch) { + state = false; + if (auto button = ptr.get()) { + outlet_float(button->x_obj.ob_outlet, 0); + } } repaint(); @@ -208,6 +258,11 @@ class ButtonObject : public ObjectBase { repaint(); break; } + case hash("latch"): + case hash("bang"): + case hash("toggle"): { + update(); + } default: break; } From f531ffe011e12106e6e098e98560bd2d3a5f5d89 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 14 May 2024 13:25:20 +0200 Subject: [PATCH 0772/1030] More [button] fixes --- Source/Objects/ButtonObject.h | 39 +++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/Source/Objects/ButtonObject.h b/Source/Objects/ButtonObject.h index db01b4697c..12a949f99a 100644 --- a/Source/Objects/ButtonObject.h +++ b/Source/Objects/ButtonObject.h @@ -62,10 +62,35 @@ class ButtonObject : public ObjectBase { { if (!alreadyTriggered) { - if(auto button = ptr.get()) { - outlet_float(button->x_obj.ob_outlet, 1); + if(mode == Latch) { + state = true; + } + else if(mode == Toggle){ + state = !state; + } + + if(mode == Bang) + { + state = true; + if (auto button = ptr.get()) { + outlet_bang(button->x_obj.ob_outlet); + } + Timer::callAfterDelay(250, + [_this = SafePointer(this)]() mutable { + // First check if this object still exists + if (!_this) return; + + if (_this->state) { + _this->state = false; + _this->repaint(); + } + }); + } + else { + if (auto button = ptr.get()) { + outlet_float(button->x_obj.ob_outlet, state); + } } - state = true; repaint(); alreadyTriggered = true; } @@ -74,10 +99,12 @@ class ButtonObject : public ObjectBase { { if(alreadyTriggered) { - if(auto button = ptr.get()) { - outlet_float(button->x_obj.ob_outlet, 0); + if(mode == Latch) { + state = false; + if (auto button = ptr.get()) { + outlet_float(button->x_obj.ob_outlet, 0); + } } - state = false; repaint(); alreadyTriggered = false; } From eb491a5a4f9f81fee6fe94ca14ce4c5206806761 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 14 May 2024 13:31:27 +0200 Subject: [PATCH 0773/1030] Make [button] respond to float input --- Source/Objects/ButtonObject.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Objects/ButtonObject.h b/Source/Objects/ButtonObject.h index 12a949f99a..7571fe16ef 100644 --- a/Source/Objects/ButtonObject.h +++ b/Source/Objects/ButtonObject.h @@ -285,10 +285,17 @@ class ButtonObject : public ObjectBase { repaint(); break; } + case hash("float"): + { + state = !approximatelyEqual(atoms[0].getFloat(), 0.0f); + repaint(); + break; + } case hash("latch"): case hash("bang"): case hash("toggle"): { update(); + break; } default: break; From b8e0c6eb521df7f5c4056a4e683239340250a49a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 14 May 2024 13:47:27 +0200 Subject: [PATCH 0774/1030] Fixed tab dragging bug --- Source/Objects/ObjectBase.cpp | 1 + Source/Tabbar/ResizableTabbedComponent.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/Objects/ObjectBase.cpp b/Source/Objects/ObjectBase.cpp index 7ad7b417c8..2ea6b5c6e3 100644 --- a/Source/Objects/ObjectBase.cpp +++ b/Source/Objects/ObjectBase.cpp @@ -765,6 +765,7 @@ ObjectLabel* ObjectBase::getLabel() { return label.get(); } + bool ObjectBase::isBeingEdited() { return edited; diff --git a/Source/Tabbar/ResizableTabbedComponent.cpp b/Source/Tabbar/ResizableTabbedComponent.cpp index df5a75c795..ab00ea16ec 100644 --- a/Source/Tabbar/ResizableTabbedComponent.cpp +++ b/Source/Tabbar/ResizableTabbedComponent.cpp @@ -185,8 +185,8 @@ void ResizableTabbedComponent::moveTabToNewSplit(SourceDetails const& dragSource bool shouldDelete = (sourceNumTabs - 1) == 0; bool dropZoneCentre = (activeZone == DropZones::Centre) ? true : false; auto* tabCanvas = sourceTabContent->getCanvas(sourceTabIndex); - - if (dropZoneCentre) { + + if (dropZoneCentre && tabComponent.get() != sourceTabContent) { auto tabTitle = tabCanvas->patch.getTitle(); auto newTabIdx = tabComponent->getNumTabs(); @@ -213,6 +213,10 @@ void ResizableTabbedComponent::moveTabToNewSplit(SourceDetails const& dragSource } else if (activeZone == DropZones::Left || activeZone == DropZones::Right) { createNewSplit(static_cast(activeZone), sourceTabContent->getCanvas(sourceTabIndex)); } + else + { + return; + } if (shouldDelete) { tabCanvas->editor->splitView.setFocus(this); From b59589d635b913e74a9324f288e0d1078628ff3b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 14 May 2024 15:44:29 +0200 Subject: [PATCH 0775/1030] Fixed problems in plugin version when DAW sends in small block sizes (due to looping, automation, etc.) --- Source/Dialogs/AudioSettingsPanel.h | 4 ++-- Source/Pd/Instance.cpp | 2 +- Source/Pd/Instance.h | 2 +- Source/PluginProcessor.cpp | 22 +++++++++------------- Source/Statusbar.cpp | 2 +- Source/Utility/AudioMidiFifo.h | 15 +++++++++++++++ 6 files changed, 29 insertions(+), 18 deletions(-) diff --git a/Source/Dialogs/AudioSettingsPanel.h b/Source/Dialogs/AudioSettingsPanel.h index b306d30f94..a470333073 100644 --- a/Source/Dialogs/AudioSettingsPanel.h +++ b/Source/Dialogs/AudioSettingsPanel.h @@ -465,8 +465,8 @@ class DAWAudioSettings : public SettingsDialogPanel tailLengthValue.referTo(proc->tailLength); latencyValue.addListener(this); - - latencyValue = proc->getLatencySamples(); + + latencyValue = proc->getLatencySamples() - pd::Instance::getBlockSize(); latencyNumberBox = new PropertiesPanel::EditableComponent("Latency (samples)", latencyValue); tailLengthNumberBox = new PropertiesPanel::EditableComponent("Tail length (seconds)", tailLengthValue); diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 8e49626ba8..690c24ed99 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -326,7 +326,7 @@ void Instance::initialisePd(String& pdlua_version) libpd_set_verbose(0); } -int Instance::getBlockSize() const +int Instance::getBlockSize() { return libpd_blocksize(); } diff --git a/Source/Pd/Instance.h b/Source/Pd/Instance.h index 02832be3ad..96c722f8fb 100644 --- a/Source/Pd/Instance.h +++ b/Source/Pd/Instance.h @@ -167,7 +167,7 @@ class Instance { void startDSP(); void releaseDSP(); void performDSP(float const* inputs, float* outputs); - int getBlockSize() const; + static int getBlockSize(); void sendNoteOn(int channel, int pitch, int velocity) const; void sendControlChange(int channel, int controller, int value) const; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 9cb58bf34b..2bdf8b46e2 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -137,7 +137,7 @@ PluginProcessor::PluginProcessor() midiBufferIn.ensureSize(2048); midiBufferOut.ensureSize(2048); midiBufferInternalSynth.ensureSize(2048); - + atoms_playhead.reserve(3); atoms_playhead.resize(1); @@ -497,6 +497,7 @@ void PluginProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) if (variableBlockSize) { inputFifo = std::make_unique(maxChannels, std::max(pdBlockSize, samplesPerBlock) * 3); outputFifo = std::make_unique(maxChannels, std::max(pdBlockSize, samplesPerBlock) * 3); + outputFifo->writeSilence(Instance::getBlockSize()); } midiByteIndex = 0; @@ -819,13 +820,8 @@ void PluginProcessor::processVariable(dsp::AudioBlock buffer, MidiBuffer& outputFifo->writeAudioAndMidi(audioBufferOut, midiBufferOut); } - - // When the amount of samples availabble is larger than (2 * pdBlockSize) - buffer.getNumSamples(), we know for sure that we'll have enough samples to process the next block as well - auto numAvailable = outputFifo->getNumSamplesAvailable(); - auto enough = std::max((2 * pdBlockSize) - static_cast(buffer.getNumSamples()), static_cast(buffer.getNumSamples())); - if (numAvailable >= enough) { - outputFifo->readAudioAndMidi(buffer, midiMessages); - } + + outputFifo->readAudioAndMidi(buffer, midiMessages); } void PluginProcessor::sendPlayhead() @@ -1045,7 +1041,7 @@ void PluginProcessor::getStateInformation(MemoryBlock& destData) } unlockAudioThread(); - ostream.writeInt(getLatencySamples()); + ostream.writeInt(getLatencySamples() - Instance::getBlockSize()); ostream.writeInt(oversampling); ostream.writeFloat(getValue(tailLength)); @@ -1055,7 +1051,7 @@ void PluginProcessor::getStateInformation(MemoryBlock& destData) // In the future, we're gonna load everything from xml, to make it easier to add new properties // By putting this here, we can prepare for making this change without breaking existing DAW saves xml.setAttribute("Oversampling", oversampling); - xml.setAttribute("Latency", getLatencySamples()); + xml.setAttribute("Latency", getLatencySamples() - Instance::getBlockSize()); xml.setAttribute("TailLength", getValue(tailLength)); xml.setAttribute("Legacy", false); @@ -1207,12 +1203,12 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) auto versionString = String("0.6.1"); // latest version that didn't have version inside the daw state if (!xmlState->hasAttribute("Legacy") || xmlState->getBoolAttribute("Legacy")) { - setLatencySamples(legacyLatency); + setLatencySamples(legacyLatency + Instance::getBlockSize()); setOversampling(legacyOversampling); tailLength = legacyTail; } else { setOversampling(xmlState->getDoubleAttribute("Oversampling")); - setLatencySamples(xmlState->getDoubleAttribute("Latency")); + setLatencySamples(xmlState->getDoubleAttribute("Latency") + Instance::getBlockSize()); tailLength = xmlState->getDoubleAttribute("TailLength"); } @@ -1674,7 +1670,7 @@ void PluginProcessor::handleAsyncUpdate() editor->statusbar->setLatencyDisplay(customLatencySamples); } - setLatencySamples(customLatencySamples); + setLatencySamples(customLatencySamples + Instance::getBlockSize()); } // set custom plugin latency diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 503240dfcc..031c2093f9 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -908,7 +908,7 @@ Statusbar::Statusbar(PluginProcessor* processor) alignmentButton.setTooltip(String("Alignment tools")); - setLatencyDisplay(pd->getLatencySamples()); + setLatencyDisplay(pd->getLatencySamples() - pd::Instance::getBlockSize()); setSize(getWidth(), statusbarHeight); } diff --git a/Source/Utility/AudioMidiFifo.h b/Source/Utility/AudioMidiFifo.h index e80b384f5a..774d582c11 100644 --- a/Source/Utility/AudioMidiFifo.h +++ b/Source/Utility/AudioMidiFifo.h @@ -74,6 +74,21 @@ class AudioMidiFifo { fifo.finishedRead(size1 + size2); } + void writeSilence (int numSamples) + { + jassert (getNumSamplesFree() >= numSamples); + + int start1, size1, start2, size2; + fifo.prepareToWrite (numSamples, start1, size1, start2, size2); + + if (size1 > 0) + audioBuffer.clear (start1, size1); + if (size2 > 0) + audioBuffer.clear (start2, size2); + + fifo.finishedWrite (size1 + size2); + } + void writeAudioAndMidi(juce::AudioBuffer const& audioSrc, juce::MidiBuffer const& midiSrc) { jassert(getNumSamplesFree() >= audioSrc.getNumSamples()); From e6c993a5b0ebcac5605c3a095774eac680e92238 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 14 May 2024 17:11:37 +0200 Subject: [PATCH 0776/1030] Latency display fixes --- Source/Statusbar.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 031c2093f9..3d8a7acae2 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -76,7 +76,7 @@ class LatencyDisplayButton : public Component, public MultiTimer, public Settabl alpha = pow(alpha, 2.2f); fading = true; if (alpha <= 0.01f) { - alpha = 0.0f; + alpha = 0.0f; stopTimer(Animate); setVisible(false); } @@ -98,7 +98,7 @@ class LatencyDisplayButton : public Component, public MultiTimer, public Settabl { currentLatencyValue = value; updateValue(); - if (value == 64) { + if (value == 0) { startTimer(Timeout, 1000 / 3.0f); } else { stopTimer(Timeout); @@ -765,8 +765,6 @@ Statusbar::Statusbar(PluginProcessor* processor) pd->statusbarSource->addListener(cpuMeter.get()); pd->statusbarSource->addListener(this); - setWantsKeyboardFocus(true); - oversampleSelector->setTooltip("Set oversampling"); oversampleSelector->setColour(ComboBox::outlineColourId, Colours::transparentBlack); @@ -1000,9 +998,11 @@ void Statusbar::resized() void Statusbar::setLatencyDisplay(int value) { - if (currentLatency != value) { - currentLatency = value; - latencyDisplayButton->setLatencyValue(value); + if(!ProjectInfo::isStandalone) { + if (currentLatency != value) { + currentLatency = value; + latencyDisplayButton->setLatencyValue(value); + } } } From 7f88c657a67cdebccc09f36df3eb71e1e4b25d46 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 14 May 2024 17:11:45 +0200 Subject: [PATCH 0777/1030] Allow space key in plugin mode --- Libraries/JUCE | 2 +- Source/PluginEditor.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index a08a665568..efaf343b91 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit a08a665568a340941d03ab9f9ea8fe1a34165339 +Subproject commit efaf343b91728c7c6720d81b6684a9081ca5794a diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 65e5e51a69..41731045de 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1228,7 +1228,7 @@ void PluginEditor::getCommandInfo(CommandID const commandID, ApplicationCommandI break; } case CommandIDs::PanDragKey: { - result.setInfo("Pan drag key", "Pan drag key", "View", 0); + result.setInfo("Pan drag key", "Pan drag key", "View", ApplicationCommandInfo::dontTriggerAlertSound); result.addDefaultKeypress(KeyPress::spaceKey, ModifierKeys::noModifiers); result.setActive(hasCanvas && !isDragging && !pluginMode); break; @@ -1874,7 +1874,7 @@ bool PluginEditor::keyPressed(KeyPress const& key) // Claim tab keys on canvas to prevent cycling selection // The user might want to catch the tab key with an object, this behaviour just gets in the way // We do still want to allow tab cycling on other components, so if canvas doesn't have focus, don't grab the tab key - return getCurrentCanvas()->hasKeyboardFocus(true) || key.getKeyCode() != KeyPress::tabKey; + return getCurrentCanvas()->hasKeyboardFocus(true) || (key.getKeyCode() != KeyPress::tabKey && key.getKeyCode() != KeyPress::spaceKey); } void PluginEditor::parentHierarchyChanged() From f1fbd524e83d1498c84b81e988110ad1b9cf9966 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 14 May 2024 17:12:09 +0200 Subject: [PATCH 0778/1030] Small fix --- Libraries/JUCE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index efaf343b91..494eef1f33 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit efaf343b91728c7c6720d81b6684a9081ca5794a +Subproject commit 494eef1f331079cbd33377a5d24bfd3c05d9e14f From a8afc6d9a55d69d7607ce6c40005f98652ae0ac3 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 14 May 2024 17:21:45 +0200 Subject: [PATCH 0779/1030] More fixes to allow space key to be used for DAW stop/play in pluginmode --- Libraries/JUCE | 2 +- Source/PluginEditor.cpp | 2 ++ Source/Statusbar.cpp | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index 494eef1f33..92db47b276 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit 494eef1f331079cbd33377a5d24bfd3c05d9e14f +Subproject commit 92db47b276f789b240b920669bb197ff46d29fa7 diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 41731045de..b46b14e56c 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1871,6 +1871,8 @@ void PluginEditor::enablePluginMode(Canvas* cnv) // it would be annoying to hear the bloop sound for every key that isn't a valid command bool PluginEditor::keyPressed(KeyPress const& key) { + if(!getCurrentCanvas()) return false; + // Claim tab keys on canvas to prevent cycling selection // The user might want to catch the tab key with an object, this behaviour just gets in the way // We do still want to allow tab cycling on other components, so if canvas doesn't have focus, don't grab the tab key diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 3d8a7acae2..b29169c39e 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -74,6 +74,8 @@ class LatencyDisplayButton : public Component, public MultiTimer, public Settabl alpha = pow(alpha, 1.0f / 2.2f); alpha -= 0.02f; alpha = pow(alpha, 2.2f); + alpha = std::clamp(alpha, 0.0f, 1.0f); + alpha = std::isfinite(alpha) ? alpha : 0.0f; fading = true; if (alpha <= 0.01f) { alpha = 0.0f; From 793fa2c59a23ebbf46cae27e2f16ebda775848da Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 14 May 2024 17:48:00 +0200 Subject: [PATCH 0780/1030] Fixed [keyboard] toggle mode issue --- Source/Objects/KeyboardObject.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Objects/KeyboardObject.h b/Source/Objects/KeyboardObject.h index ebbb5690ad..c9820832d3 100644 --- a/Source/Objects/KeyboardObject.h +++ b/Source/Objects/KeyboardObject.h @@ -276,7 +276,7 @@ class KeyboardObject final : public ObjectBase octaves.setValue(obj->x_octaves); toggleMode.setValue(obj->x_toggle_mode); sizeProperty.setValue(obj->x_height); - + auto sndSym = obj->x_snd_set ? String::fromUTF8(obj->x_snd_raw->s_name) : getBinbufSymbol(7); auto rcvSym = obj->x_rcv_set ? String::fromUTF8(obj->x_rcv_raw->s_name) : getBinbufSymbol(8); @@ -291,6 +291,8 @@ class KeyboardObject final : public ObjectBase } }); } + + keyboard.setToggleMode(getValue(toggleMode)); } void render(NVGcontext* nvg) override From cc485c4195b57cff0a0bc621247ab4c0be6abba0 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 14 May 2024 18:52:32 +0200 Subject: [PATCH 0781/1030] Gem fixes --- Libraries/Gem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Gem b/Libraries/Gem index 63dd1c6ffd..dacd06992e 160000 --- a/Libraries/Gem +++ b/Libraries/Gem @@ -1 +1 @@ -Subproject commit 63dd1c6ffd69dba33bcfec7a8774a39e51fc6897 +Subproject commit dacd06992ec5e8533187322fdd61810ac922123b From c54bd38e84f44994766ee3ad86f16795f7234d71 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 15 May 2024 21:30:44 +0930 Subject: [PATCH 0782/1030] add vu scale as an additional label --- Source/Object.cpp | 13 +++- Source/Objects/ArrayObject.h | 18 ++--- Source/Objects/AtomHelper.h | 18 ++--- Source/Objects/BangObject.h | 2 +- Source/Objects/CanvasObject.h | 2 +- Source/Objects/FloatAtomObject.h | 2 +- Source/Objects/IEMHelper.h | 41 +++++----- Source/Objects/ListObject.h | 2 +- Source/Objects/NumberObject.h | 4 +- Source/Objects/ObjectBase.cpp | 4 +- Source/Objects/ObjectBase.h | 120 +++++++++++++++++++++++++++++- Source/Objects/RadioObject.h | 2 +- Source/Objects/SliderObject.h | 2 +- Source/Objects/SymbolAtomObject.h | 2 +- Source/Objects/ToggleObject.h | 2 +- Source/Objects/VUMeterObject.h | 47 ++++++------ 16 files changed, 211 insertions(+), 70 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index 41ed93f27d..5cf97d170f 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -1373,10 +1373,21 @@ void Object::renderLabel(NVGcontext* nvg) if(auto* label = gui->getLabel()) { nvgSave(nvg); - nvgTranslate(nvg, label->getX(), label->getY()); + auto posOnCanvas = cnv->getLocalPoint(gui->labels.get(), label->getPosition()); + nvgTranslate(nvg, posOnCanvas.getX(), posOnCanvas.getY()); label->renderLabel(nvg, cnv->getRenderScale() * 2.0f); nvgRestore(nvg); } + if(auto* vu = gui->getVU()) + { + if (vu->isVisible()) { + nvgSave(nvg); + auto posOnCanvas = cnv->getLocalPoint(gui->labels.get(), vu->getPosition()); + nvgTranslate(nvg, posOnCanvas.getX(), posOnCanvas.getY()); + vu->render(nvg); + nvgRestore(nvg); + } + } } } diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index 6145a786af..3764b5d1ee 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -1272,24 +1272,24 @@ class ArrayObject final : public ObjectBase { } if (title.isNotEmpty()) { - if (!label) { - label = std::make_unique(cnv->editor->nvgSurface); + if (!labels) { + labels = std::make_unique(cnv->editor->nvgSurface); } - auto bounds = object->getBounds().reduced(Object::margin).removeFromTop(fontHeight + 2); + auto bounds = object->getBounds().reduced(Object::margin).removeFromTop(fontHeight + 2).withWidth(Font(fontHeight).getStringWidth(title)); bounds.translate(2, -(fontHeight + 2)); - label->setFont(Font(fontHeight)); - label->setBounds(bounds); - label->setText(title, dontSendNotification); + labels->getObjectLabel()->setFont(Font(fontHeight)); + labels->setLabelBounds(bounds); + labels->getObjectLabel()->setText(title, dontSendNotification); - label->setColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); + labels->setColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId)); - object->cnv->addAndMakeVisible(label.get()); + object->cnv->addAndMakeVisible(labels.get()); } else { - label.reset(nullptr); + labels.reset(nullptr); } } diff --git a/Source/Objects/AtomHelper.h b/Source/Objects/AtomHelper.h index 00b0926c2a..b274f53efe 100644 --- a/Source/Objects/AtomHelper.h +++ b/Source/Objects/AtomHelper.h @@ -271,7 +271,7 @@ class AtomHelper { } } - void updateLabel(std::unique_ptr& label) + void updateLabel(std::unique_ptr& labels) { int idx = std::clamp(fontSize.getValue(), 1, 7); @@ -281,26 +281,26 @@ class AtomHelper { String const text = getExpandedLabelText(); if (text.isNotEmpty()) { - if (!label) { - label = std::make_unique(cnv->editor->nvgSurface); + if (!labels) { + labels = std::make_unique(cnv->editor->nvgSurface); } auto bounds = getLabelBounds(); - label->setBounds(bounds); - label->setFont(Font(fontHeight)); - label->setText(text, dontSendNotification); + labels->setLabelBounds(bounds); + labels->getObjectLabel()->setFont(Font(fontHeight)); + labels->getObjectLabel()->setText(text, dontSendNotification); auto textColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId); if (std::abs(textColour.getBrightness() - LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasBackgroundColourId).getBrightness()) < 0.3f) { textColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasBackgroundColourId).contrasting(); } - label->setColour(textColour); + labels->setColour(textColour); - object->cnv->addAndMakeVisible(label.get()); + object->cnv->addAndMakeVisible(labels.get()); } else { - label.reset(nullptr); + labels.reset(nullptr); } } diff --git a/Source/Objects/BangObject.h b/Source/Objects/BangObject.h index 432203abb0..766dcbca6b 100644 --- a/Source/Objects/BangObject.h +++ b/Source/Objects/BangObject.h @@ -55,7 +55,7 @@ class BangObject final : public ObjectBase { void updateLabel() override { - iemHelper.updateLabel(label); + iemHelper.updateLabel(labels); } Rectangle getPdBounds() override diff --git a/Source/Objects/CanvasObject.h b/Source/Objects/CanvasObject.h index b0e497ef12..5cc6a677cc 100644 --- a/Source/Objects/CanvasObject.h +++ b/Source/Objects/CanvasObject.h @@ -45,7 +45,7 @@ class CanvasObject final : public ObjectBase { void updateLabel() override { - iemHelper.updateLabel(label); + iemHelper.updateLabel(labels); } void receiveObjectMessage(hash32 symbol, pd::Atom const atoms[8], int numAtoms) override diff --git a/Source/Objects/FloatAtomObject.h b/Source/Objects/FloatAtomObject.h index c5f4339a6d..c8b06a151c 100644 --- a/Source/Objects/FloatAtomObject.h +++ b/Source/Objects/FloatAtomObject.h @@ -204,7 +204,7 @@ class FloatAtomObject final : public ObjectBase { void updateLabel() override { - atomHelper.updateLabel(label); + atomHelper.updateLabel(labels); } Rectangle getPdBounds() override diff --git a/Source/Objects/IEMHelper.h b/Source/Objects/IEMHelper.h index 8ac54f595b..068e4fba05 100644 --- a/Source/Objects/IEMHelper.h +++ b/Source/Objects/IEMHelper.h @@ -4,6 +4,8 @@ // WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ +#pragma once + static int srl_is_valid(t_symbol const* s) { return (s != nullptr && s != gensym("")); @@ -277,31 +279,36 @@ class IEMHelper { } } - void updateLabel(std::unique_ptr& label, Point offset = {0, 0}) + void updateLabel(std::unique_ptr& labels, Point offset = {0, 0}) { String const text = labelText.toString(); - if (text.isNotEmpty()) { - if (!label) { - label = std::make_unique(cnv->editor->nvgSurface); - object->cnv->addChildComponent(label.get()); + if (text.isNotEmpty() || (gui->showVU())) { + if (!labels) { + labels = std::make_unique(cnv->editor->nvgSurface); + object->cnv->addChildComponent(labels.get()); + if (gui->showVU()) + labels->setObjectToTrack(object); } - auto bounds = getLabelBounds(); - - bounds.translate(0, bounds.getHeight() / -2.0f); - - label->setFont(Font(bounds.getHeight())); - label->setBounds(bounds + offset); - label->setText(text, dontSendNotification); + if (text.isNotEmpty()) { + auto bounds = getLabelBounds(); - label->setColour(getLabelColour()); + bounds.translate(0, bounds.getHeight() / -2.0f); - label->setVisible(true); + labels->getObjectLabel()->setFont(Font(bounds.getHeight())); + labels->setLabelBounds(bounds + offset); + labels->getObjectLabel()->setText(text, dontSendNotification); + } else { + // updating side VU label only, by sending empty rectangle + labels->setLabelBounds(Rectangle()); + } + labels->setColour(getLabelColour()); + labels->setVisible(true); } else { - if (label) - label->setVisible(false); - label.reset(nullptr); + if (labels) + labels->setVisible(false); + labels.reset(nullptr); } } diff --git a/Source/Objects/ListObject.h b/Source/Objects/ListObject.h index 00784e81e9..b65bc8c8f9 100644 --- a/Source/Objects/ListObject.h +++ b/Source/Objects/ListObject.h @@ -149,7 +149,7 @@ class ListObject final : public ObjectBase { void updateLabel() override { - atomHelper.updateLabel(label); + atomHelper.updateLabel(labels); } bool hideInlets() override diff --git a/Source/Objects/NumberObject.h b/Source/Objects/NumberObject.h index e897c5524f..9a41337173 100644 --- a/Source/Objects/NumberObject.h +++ b/Source/Objects/NumberObject.h @@ -5,6 +5,8 @@ */ #include "Components/DraggableNumber.h" +#include "ObjectBase.h" +#include "IEMHelper.h" class NumberObject final : public ObjectBase { @@ -109,7 +111,7 @@ class NumberObject final : public ObjectBase { void updateLabel() override { - iemHelper.updateLabel(label); + iemHelper.updateLabel(labels); } Rectangle getPdBounds() override diff --git a/Source/Objects/ObjectBase.cpp b/Source/Objects/ObjectBase.cpp index 2ea6b5c6e3..353c57f144 100644 --- a/Source/Objects/ObjectBase.cpp +++ b/Source/Objects/ObjectBase.cpp @@ -763,7 +763,9 @@ void ObjectBase::setParameterExcludingListener(Value& parameter, var const& valu ObjectLabel* ObjectBase::getLabel() { - return label.get(); + if (labels) + return labels->getObjectLabel(); + return nullptr; } bool ObjectBase::isBeingEdited() diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index 657c591ae8..cf56cf652f 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -13,6 +13,8 @@ #include "Utility/SynchronousValue.h" #include "Utility/NVGComponent.h" #include "Utility/CachedTextRender.h" +#include "Object.h" +#include "Canvas.h" class PluginProcessor; class Canvas; @@ -73,7 +75,7 @@ class ObjectLabel : public Label, public NVGComponent, public NVGContextListener nvgFill(nvg); } - void setColour(const Colour colour) + void setColour(const Colour& colour) { if (colour != lastColour) { Label::setColour(Label::textColourId, colour); @@ -100,6 +102,115 @@ class ObjectLabel : public Label, public NVGComponent, public NVGContextListener private: }; +class VUScale : public Component, public NVGComponent, public NVGContextListener +{ + NVGSurface& surface; + Colour textColour; + StringArray scale = {"+12", "+6", "+2", "-0dB", "-2", "-6", "-12", "-20", "-30", "-50", "-99"}; + StringArray scaleDecim = {"+12", "", "", "-0dB", "", "", "-12", "", "", "", "-99"}; +public: + VUScale(NVGSurface& s) : NVGComponent(this), surface(s) + { + surface.addNVGContextListener(this); + } + + ~VUScale() + { + surface.removeNVGContextListener(this); + } + + void setColour(const Colour& colour) + { + textColour = colour; + repaint(); + } + + void render(NVGcontext * nvg) override + { + nvgFontSize(nvg, 8); + nvgFontFace(nvg, "Inter-Regular"); + nvgTextAlign(nvg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); + nvgFillColor(nvg, convertColour(textColour)); + auto scaleToUse = getHeight() < 80 ? scaleDecim : scale; + for (int i = 0; i < scale.size(); i++) { + auto posY = ((getHeight() - 20) * (i / 10.0f)) + 10; + // align the "-" and "+" text element centre + nvgTextAlign(nvg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); + nvgText(nvg, 2, posY, scaleToUse[i].substring(0,1).toRawUTF8(), nullptr); + // align the number text element left + nvgTextAlign(nvg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); + nvgText(nvg, 5, posY, scaleToUse[i].substring(1).toRawUTF8(), nullptr); + } + } +}; + +class ObjectLabels : public Component +{ +public: + ObjectLabels(NVGSurface& s) : objectLabel(s), vuScale(s) + { + addAndMakeVisible(objectLabel); + addAndMakeVisible(vuScale); + + setInterceptsMouseClicks(false, false); + } + + ~ObjectLabels() + { + } + + ObjectLabel* getObjectLabel() + { + return &objectLabel; + } + + VUScale* getVUObject() + { + return &vuScale; + } + + void setColour(const Colour& colour) + { + objectLabel.setColour(colour); + vuScale.setColour(colour); + } + + void setObjectToTrack(Object* object) + { + obj = object; + } + + void setLabelBounds(Rectangle bounds) + { + labelBounds = bounds; + if (obj) + vuScaleBounds = Rectangle(obj->getBounds().getTopRight().x - 3, obj->getBounds().getTopRight().y, 20, obj->getBounds().getHeight()); + auto allBounds = bounds.getUnion(vuScaleBounds); + setBounds(allBounds); + // force resize to run, so position updates even when union size doesn't change + resized(); + } + + void resized() override + { + if (obj) { + auto lb = getLocalArea(obj->cnv, labelBounds); + auto vb = getLocalArea(obj->cnv, vuScaleBounds); + objectLabel.setBounds(lb); + vuScale.setBounds(vb); + } else { + objectLabel.setBounds(getLocalBounds()); + } + } +private: + Object* obj = nullptr; + + Rectangle labelBounds; + Rectangle vuScaleBounds; + ObjectLabel objectLabel; + VUScale vuScale; +}; + class ObjectBase : public Component , public pd::MessageListener , public Value::Listener @@ -226,6 +337,9 @@ class ObjectBase : public Component virtual ObjectLabel* getLabel(); + virtual VUScale* getVU() { return nullptr; }; + virtual bool showVU() { return false; }; + // Should return current object text if applicable // Currently only used to subsitute arguments in tooltips // TODO: does that even work? @@ -298,6 +412,8 @@ class ObjectBase : public Component Canvas* cnv; PluginProcessor* pd; + std::unique_ptr labels; + protected: PropertyUndoListener propertyUndoListener; @@ -307,7 +423,7 @@ class ObjectBase : public Component virtual std::unique_ptr createConstrainer(); - std::unique_ptr label; + static inline constexpr int maxSize = 1000000; static inline std::atomic edited = false; std::unique_ptr constrainer; diff --git a/Source/Objects/RadioObject.h b/Source/Objects/RadioObject.h index 24acf80cb4..11e18913fa 100644 --- a/Source/Objects/RadioObject.h +++ b/Source/Objects/RadioObject.h @@ -67,7 +67,7 @@ class RadioObject final : public ObjectBase { void updateLabel() override { - iemHelper.updateLabel(label); + iemHelper.updateLabel(labels); } void setPdBounds(Rectangle b) override diff --git a/Source/Objects/SliderObject.h b/Source/Objects/SliderObject.h index 58932cc507..d239226678 100644 --- a/Source/Objects/SliderObject.h +++ b/Source/Objects/SliderObject.h @@ -216,7 +216,7 @@ class SliderObject : public ObjectBase { void updateLabel() override { - iemHelper.updateLabel(label, isVertical ? Point(0, 2) : Point(2, 0)); + iemHelper.updateLabel(labels, isVertical ? Point(0, 2) : Point(2, 0)); } Rectangle getPdBounds() override diff --git a/Source/Objects/SymbolAtomObject.h b/Source/Objects/SymbolAtomObject.h index b6c82e6163..f1f609debb 100644 --- a/Source/Objects/SymbolAtomObject.h +++ b/Source/Objects/SymbolAtomObject.h @@ -185,7 +185,7 @@ class SymbolAtomObject final : public ObjectBase void updateLabel() override { - atomHelper.updateLabel(label); + atomHelper.updateLabel(labels); } void valueChanged(Value& v) override diff --git a/Source/Objects/ToggleObject.h b/Source/Objects/ToggleObject.h index 06dbaff378..c9979721af 100644 --- a/Source/Objects/ToggleObject.h +++ b/Source/Objects/ToggleObject.h @@ -41,7 +41,7 @@ class ToggleObject final : public ObjectBase { void updateLabel() override { - iemHelper.updateLabel(label); + iemHelper.updateLabel(labels); } Rectangle getPdBounds() override diff --git a/Source/Objects/VUMeterObject.h b/Source/Objects/VUMeterObject.h index c10595cd51..fe1743e8a2 100644 --- a/Source/Objects/VUMeterObject.h +++ b/Source/Objects/VUMeterObject.h @@ -8,6 +8,7 @@ class VUMeterObject final : public ObjectBase { IEMHelper iemHelper; Value sizeProperty = SynchronousValue(); + Value showScale = SynchronousValue(); public: VUMeterObject(pd::WeakReference ptr, Object* object) @@ -22,7 +23,12 @@ class VUMeterObject final : public ObjectBase { objectParameters.addParamSize(&sizeProperty); objectParameters.addParamReceiveSymbol(&iemHelper.receiveSymbol); + objectParameters.addParamBool("Show scale", ParameterCategory::cAppearance, &showScale, {"No", "Yes"}, 1); iemHelper.addIemParameters(objectParameters, false, false, -1); + + updateLabel(); + showScale = ptr.get()->x_scale; + valueChanged(showScale); } void updateSizeProperty() override @@ -46,7 +52,19 @@ class VUMeterObject final : public ObjectBase { void updateLabel() override { - iemHelper.updateLabel(label); + iemHelper.updateLabel(labels); + } + + VUScale* getVU() override + { + if (labels) + return labels->getVUObject(); + return nullptr; + } + + bool showVU() override + { + return true; } void valueChanged(Value& v) override @@ -65,6 +83,12 @@ class VUMeterObject final : public ObjectBase { } object->updateBounds(); + } else if (v.refersToSameSourceAs(showScale)) { + if (auto vu = ptr.get()) { + auto showVU = getValue(showScale); + vu->x_scale = showVU; + getVU()->setVisible(showVU); + } } else { iemHelper.valueChanged(v); } @@ -141,27 +165,6 @@ class VUMeterObject final : public ObjectBase { nvgBeginPath(nvg); nvgRoundedRect(nvg, outerBorderWidth, outerBorderWidth + ((totalBlocks - numBlocks2) * blockHeight) + blockRectSpacing, blockWidth, blockRectHeight / 2.0f, blockCornerSize); nvgFill(nvg); - - // Get text value with 2 and 0 decimals - // Prevent going past -100 for size reasons - String textValue = String(std::max(values[1], -96.0f), 2); - - if (width > nvgTextBounds(nvg, 0, 0, textValue.toRawUTF8(), nullptr, nullptr)) { - // Check noscale flag, otherwise display next to slider - nvgFontSize(nvg, 11); - nvgFontFace(nvg, "Inter-Regular"); - nvgTextAlign(nvg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); - nvgFillColor(nvg, nvgRGBAf(1, 1, 1, 1)); // White color - nvgText(nvg, width * 0.5f, height - 20, textValue.toRawUTF8(), nullptr); - } else { - // Fallback for smaller sizes - textValue = String(std::max(values[1], -96.0f), 0); - nvgFontSize(nvg, 11); - nvgFontFace(nvg, "Inter-Regular"); - nvgTextAlign(nvg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); - nvgFillColor(nvg, nvgRGBAf(1, 1, 1, 1)); // White color - nvgText(nvg, width * 0.5f, height - 20, textValue.toRawUTF8(), nullptr); - } } void receiveObjectMessage(hash32 symbol, pd::Atom const atoms[8], int numAtoms) override From 91aea22e6e249866edb60468e361e2aa98f08450 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 16 May 2024 01:14:43 +0930 Subject: [PATCH 0783/1030] make sure connection layer is not visible when in plugin mode If the user entered plugin mode from edit mode, then the JUCE connections could still be active, even if NVG wasn't rendering them, this forces them to be off. However there could be a better way to do this? Setting the connections to visible when leaving plugin mode looks ok, as the canvas then resets anyway --- Source/PluginMode.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/PluginMode.h b/Source/PluginMode.h index 296866f43c..fc5a6be028 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -110,6 +110,8 @@ class PluginMode : public Component, public NVGComponent { addAndMakeVisible(titleBar); setWidthAndHeight(1.0f); + + cnv->connectionLayer.setVisible(false); } ~PluginMode() override = default; @@ -200,6 +202,8 @@ class PluginMode : public Component, public NVGComponent { editor->parentSizeChanged(); editor->resized(); + cnv->connectionLayer.setVisible(true); + #if JUCE_LINUX editor->sendLookAndFeelChange(); // TODO: this is just a hacky way to make sure all framebuffers area cleared. could be cleaner. editor->nvgSurface.detachContext(); From 48546fe6ef566a1f7fe2c1bf043d154c6f95966f Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 16 May 2024 01:22:24 +0930 Subject: [PATCH 0784/1030] Change click to drop behaviour to release object on mouse up This way we don't need to worry about iolets getting triggered on the mouse up, as the DnD action still has the mouse event --- Source/Canvas.cpp | 1 - Source/Canvas.h | 2 -- Source/Components/ObjectDragAndDrop.h | 2 +- Source/Iolet.cpp | 11 ----------- 4 files changed, 1 insertion(+), 15 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 876fc1fa3f..ef2a91a4c8 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -1237,7 +1237,6 @@ void Canvas::dragAndDropPaste(String const& patchString, Point mousePos, in { locked = false; presentationMode = false; - objectAdded = true; // force the valueChanged to run, and wait for them to return locked.getValueSource().sendChangeMessage(true); diff --git a/Source/Canvas.h b/Source/Canvas.h index 12da0cbe52..f981a8eac9 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -251,8 +251,6 @@ class Canvas : public Component Array> drawables; - bool objectAdded = false; - private: GlobalMouseListener globalMouseListener; diff --git a/Source/Components/ObjectDragAndDrop.h b/Source/Components/ObjectDragAndDrop.h index 6368cf55f2..537eaec537 100644 --- a/Source/Components/ObjectDragAndDrop.h +++ b/Source/Components/ObjectDragAndDrop.h @@ -194,7 +194,7 @@ class ObjectClickAndDrop : public Component setCentrePosition(mousePosition); } - void mouseDown(MouseEvent const& e) override + void mouseUp(MouseEvent const& e) override { // This is nicer, but also makes sure that getComponentAt doesn't return this object setVisible(false); diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index 5851c29172..1fb8b05b4c 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -149,9 +149,6 @@ void Iolet::mouseDrag(MouseEvent const& e) void Iolet::mouseDown(MouseEvent const& e) { mouseIsDown = true; - - // make sure that intentional clicks on iolet get through after a DnD action - cnv->objectAdded = false; } void Iolet::mouseUp(MouseEvent const& e) @@ -161,14 +158,6 @@ void Iolet::mouseUp(MouseEvent const& e) if (getValue(locked) || e.mods.isRightButtonDown()) return; - // If an object is addded to canvas, the mouse can potentially be over an iolet when dropped onto canvas - // This situation could cause a cable to be created when an object is added - // Disregard mouseup if this happens - if (cnv->objectAdded) { - cnv->objectAdded = false; - return; - } - // This might end up calling Canvas::synchronise, at which point we are not sure this class will survive, so we do an async call bool shiftIsDown = e.mods.isShiftDown(); From 0489defc672fd7a8c68186e69458eb9657e6334e Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sun, 19 May 2024 00:17:22 +0930 Subject: [PATCH 0785/1030] put connection order number infront of cable layer fix offline object rendering for silhouettes --- Source/Canvas.cpp | 18 +++++- Source/Canvas.h | 1 + Source/Connection.cpp | 53 ++++++++--------- Source/Connection.h | 2 + Source/Utility/OfflineObjectRenderer.cpp | 74 +++++++++++++++++++++++- 5 files changed, 118 insertions(+), 30 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index ef2a91a4c8..6d181d2c15 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -514,14 +514,29 @@ void Canvas::renderAllObjects(NVGcontext* nvg, Rectangle area) } void Canvas::renderAllConnections(NVGcontext* nvg, Rectangle area) { + if (! connectionLayer.isVisible()) + return; + + Array connectionsToDraw; + for(auto* connection : connections) { nvgSave(nvg); - if(connection->intersectsRectangle(area) && connection->isVisible()) { + if(connection->intersectsRectangle(area)) { connection->render(nvg); + if (showConnectionOrder) + connectionsToDraw.add(connection); } nvgRestore(nvg); } + if (!connectionsToDraw.isEmpty()){ + for (auto* connection : connectionsToDraw) + { + nvgSave(nvg); + connection->renderConnectionOrder(nvg); + nvgRestore(nvg); + } + } } void Canvas::propertyChanged(String const& name, var const& value) @@ -575,6 +590,7 @@ void Canvas::updateOverlays() showBorder = overlayState & Border; showOrigin = overlayState & Origin; + showConnectionOrder = overlayState & Order; connectionsBehind = overlayState & Behind; orderConnections(); diff --git a/Source/Canvas.h b/Source/Canvas.h index f981a8eac9..81dfce618b 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -205,6 +205,7 @@ class Canvas : public Component bool showOrigin = false; bool showBorder = false; + bool showConnectionOrder = false; bool connectionsBehind = true; bool isScrolling = false; diff --git a/Source/Connection.cpp b/Source/Connection.cpp index fd1d1ba8a6..509eb08bcc 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -156,7 +156,7 @@ void Connection::lookAndFeelChanged() void Connection::render(NVGcontext* nvg) { - auto connectionColour = baseColour; + connectionColour = baseColour; if (isSelected() || isHovering) { if(outlet->isSignal) { @@ -286,7 +286,7 @@ void Connection::render(NVGcontext* nvg) const float arrowWidth = 8.0f; const float arrowLength = 12.0f; - auto renderArrow = [this, nvg, arrowLength, arrowWidth, connectionColour](Path& path, float connectionLength){ + auto renderArrow = [this, nvg, arrowLength, arrowWidth](Path& path, float connectionLength){ // get the center point of the connection path const auto arrowCenter = connectionLength * 0.5f; @@ -336,32 +336,30 @@ void Connection::render(NVGcontext* nvg) } } } +} - // draw connection index number - if (showConnectionOrder) { - if ((cableType == DataCable) && (getNumberOfConnections() > 1)) { - auto connectionPath = getPath(); - connectionPath.applyTransform(AffineTransform::translation(-getX(), -getY())); - auto pos = cnv->getLocalPoint(this, connectionPath.getPointAlongPath(jmax(connectionPath.getLength() - 8.5f * 3, 9.5f))); - - // circle background - nvgBeginPath(nvg); - nvgStrokeColor(nvg, outlineColour); - nvgFillColor(nvg, connectionColour); - const auto radius = 7.0f; - const auto diameter = radius * 2.0f; - const auto circleTopLeft = pos - Point(radius, radius); - nvgRoundedRect(nvg, circleTopLeft.getX(), circleTopLeft.getY(), diameter, diameter, radius); - nvgStrokeWidth(nvg, 1.0f); - nvgFill(nvg); - nvgStroke(nvg); - - // connection index number - nvgFillColor(nvg, textColour); - nvgFontSize(nvg, 9.0f); - nvgTextAlign(nvg, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER); - nvgText(nvg, pos.getX(), pos.getY(), String(getMultiConnectNumber()).toUTF8(), nullptr); - } +void Connection::renderConnectionOrder(NVGcontext* nvg) +{ + if ((cableType == DataCable) && (getNumberOfConnections() > 1)) { + auto connectionPath = getPath(); + connectionPath.applyTransform(AffineTransform::translation(-getX(), -getY())); + auto pos = cnv->getLocalPoint(this, connectionPath.getPointAlongPath(jmax(connectionPath.getLength() - 8.5f * 3, 9.5f))); + // circle background + nvgBeginPath(nvg); + nvgStrokeColor(nvg, outlineColour); + nvgFillColor(nvg, connectionColour); + const auto radius = 7.0f; + const auto diameter = radius * 2.0f; + const auto circleTopLeft = pos - Point(radius, radius); + nvgRoundedRect(nvg, circleTopLeft.getX(), circleTopLeft.getY(), diameter, diameter, radius); + nvgStrokeWidth(nvg, 1.0f); + nvgFill(nvg); + nvgStroke(nvg); + // connection index number + nvgFillColor(nvg, textColour); + nvgFontSize(nvg, 9.0f); + nvgTextAlign(nvg, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER); + nvgText(nvg, pos.getX(), pos.getY(), String(getMultiConnectNumber()).toUTF8(), nullptr); } } @@ -514,7 +512,6 @@ void Connection::updateOverlays(int overlay) return; showDirection = overlay & Overlay::Direction; - showConnectionOrder = overlay & Overlay::Order; showActivity = overlay & Overlay::ConnectionActivity; repaint(); } diff --git a/Source/Connection.h b/Source/Connection.h index b741318f59..9db6b9540b 100644 --- a/Source/Connection.h +++ b/Source/Connection.h @@ -72,6 +72,7 @@ class Connection : public DrawablePath bool intersectsRectangle(Rectangle rectToIntersect); void render(NVGcontext* nvg) override; + void renderConnectionOrder(NVGcontext* nvg); void nvgContextDeleted(NVGcontext* nvg) override; void updatePath(); @@ -162,6 +163,7 @@ class Connection : public DrawablePath NVGcolor shadowColour; NVGcolor outlineColour; NVGcolor gemColour; + NVGcolor connectionColour; NVGcolor textColour; diff --git a/Source/Utility/OfflineObjectRenderer.cpp b/Source/Utility/OfflineObjectRenderer.cpp index c8106375c4..cb780fe151 100644 --- a/Source/Utility/OfflineObjectRenderer.cpp +++ b/Source/Utility/OfflineObjectRenderer.cpp @@ -11,6 +11,8 @@ #include "Pd/Interface.h" #include "Pd/Patch.h" +#include "Objects/AllGuis.h" +#include OfflineObjectRenderer::OfflineObjectRenderer(pd::Instance* instance) : pd(instance) @@ -89,9 +91,79 @@ ImageWithOffset OfflineObjectRenderer::patchToTempImage(String const& patch, flo auto object = offlineCnv->gl_list; while (object) { pd::Interface::getObjectBounds(offlineCnv, object, &obj_x, &obj_y, &obj_w, &obj_h); + auto* objectPtr = pd::Interface::checkObject(object); + + char *objectText; + int len; + pd::Interface::getObjectText(objectPtr, &objectText, &len); + const auto objectTextString = String::fromUTF8(objectText, len); + + String type = String::fromUTF8(object->g_pd->c_name->s_name); + const auto objectNameHash = hash(type); + + std::cout << "object type: " << type << std::endl; + + switch(objectNameHash){ + case hash("bng"): + case hash("hsl"): + case hash("vsl"): + case hash("slider"): + case hash("tgl"): + case hash("nbx"): + case hash("numbox~"): + case hash("vradio"): + case hash("hradio"): + case hash("vu"): + case hash("canvas"): + case hash("pic"): + case hash("keyboard"): + case hash("scope~"): + case hash("function"): + case hash("note"): + case hash("knob"): + case hash("gatom"): + case hash("button"): + case hash("graph"): + case hash("bicoeff"): + case hash("messbox"): + case hash("pad"): + { + break; + } + case hash("cnv"): { + auto cnvObject = ((t_my_canvas*)object); + obj_w = cnvObject->x_vis_w; + obj_h = cnvObject->x_vis_h; + break; + } + case hash("message"): + case hash("comment"): + case hash("text"): { + StringArray lines; + lines.addTokens(objectTextString, ";", ""); + auto charWidth = objectPtr->te_width; + // charWidth of 0 == auto width, which means we need to calculate the width manually :facepalm: + obj_w = jmax(Font(16).getStringWidth(objectTextString), charWidth * 7); + obj_h = jmax(20, 20 * lines.size()); + + if ((objectNameHash == hash("message")) || (((t_fake_text_define *) object)->x_textbuf.b_ob.te_type == T_OBJECT)) { + obj_w = 45; + } + break; + } + default: + { + obj_h = 20; + if (objectPtr->te_width == 0) { + std::cout << "object text is: " << objectTextString << std::endl; + obj_w = Font(16).getStringWidth(objectTextString); + } + } + break; + } + auto maxIolets = jmax(pd::Interface::numOutlets(objectPtr), pd::Interface::numInlets(objectPtr)); - // ALEX TODO: fix this heuristic, it doesn't work well for everything auto maxSize = jmax(maxIolets * 18, obj_w); rect.setBounds(obj_x, obj_y, maxSize, obj_h); From d1c6d758417876225330762936b1eec22e050a49 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sun, 19 May 2024 18:55:43 +0930 Subject: [PATCH 0786/1030] fix object silhouette (for now - should be using static functions in objects) fix subpatch text object not resizing char width from parameters --- Source/Objects/CanvasObject.h | 8 +- Source/Objects/SubpatchObject.h | 7 +- Source/Utility/OfflineObjectRenderer.cpp | 119 ++++++++++++++++++----- 3 files changed, 109 insertions(+), 25 deletions(-) diff --git a/Source/Objects/CanvasObject.h b/Source/Objects/CanvasObject.h index 5cc6a677cc..5545cad2cf 100644 --- a/Source/Objects/CanvasObject.h +++ b/Source/Objects/CanvasObject.h @@ -95,6 +95,11 @@ class CanvasObject final : public ObjectBase { } } + static Rectangle getPDSize(t_my_canvas* cnvObj) + { + return Rectangle(0, 0, cnvObj->x_vis_w + 1, cnvObj->x_vis_h + 1); + } + Rectangle getPdBounds() override { if (auto canvas = ptr.get()) { @@ -104,8 +109,9 @@ class CanvasObject final : public ObjectBase { int x = 0, y = 0, w = 0, h = 0; pd::Interface::getObjectBounds(patch, canvas.cast(), &x, &y, &w, &h); + const auto pdSize = getPDSize(ptr.get().get()); - return Rectangle(x, y, ptr.get()->x_vis_w + 1, ptr.get()->x_vis_h + 1); + return Rectangle(x, y, pdSize.getWidth(), pdSize.getHeight()); } return {}; diff --git a/Source/Objects/SubpatchObject.h b/Source/Objects/SubpatchObject.h index bb3f5daa39..7f7ee0c22f 100644 --- a/Source/Objects/SubpatchObject.h +++ b/Source/Objects/SubpatchObject.h @@ -87,7 +87,10 @@ class SubpatchObject final : public TextBase { void valueChanged(Value& v) override { - if (v.refersToSameSourceAs(isGraphChild)) { + if (v.refersToSameSourceAs(sizeProperty)){ + // forward the value change to the text object + TextBase::valueChanged(v); + } else if (v.refersToSameSourceAs(isGraphChild)) { int isGraph = getValue(isGraphChild); if (auto glist = ptr.get()) { canvas_setgraph(glist.get(), isGraph + 2 * glist->gl_hidetext, 0); @@ -139,7 +142,7 @@ class SubpatchObject final : public TextBase { bool showParametersWhenSelected() override { - return true; + return cnv->isGraph; } static void checkHvccCompatibility(String const& objectText, pd::Patch::Ptr patch, String const& prefix = "") diff --git a/Source/Utility/OfflineObjectRenderer.cpp b/Source/Utility/OfflineObjectRenderer.cpp index cb780fe151..884a1de4cd 100644 --- a/Source/Utility/OfflineObjectRenderer.cpp +++ b/Source/Utility/OfflineObjectRenderer.cpp @@ -14,6 +14,11 @@ #include "Objects/AllGuis.h" #include +#include "Objects/ObjectBase.h" +#include "PluginProcessor.h" +#include "Objects/IEMHelper.h" +#include "Objects/CanvasObject.h" + OfflineObjectRenderer::OfflineObjectRenderer(pd::Instance* instance) : pd(instance) { @@ -92,6 +97,11 @@ ImageWithOffset OfflineObjectRenderer::patchToTempImage(String const& patch, flo while (object) { pd::Interface::getObjectBounds(offlineCnv, object, &obj_x, &obj_y, &obj_w, &obj_h); + obj_w = obj_w + 1; + obj_h = obj_h + 1; + + auto const padding = 5; + auto* objectPtr = pd::Interface::checkObject(object); char *objectText; @@ -99,12 +109,10 @@ ImageWithOffset OfflineObjectRenderer::patchToTempImage(String const& patch, flo pd::Interface::getObjectText(objectPtr, &objectText, &len); const auto objectTextString = String::fromUTF8(objectText, len); - String type = String::fromUTF8(object->g_pd->c_name->s_name); - const auto objectNameHash = hash(type); - - std::cout << "object type: " << type << std::endl; + String type = String::fromUTF8(pd::Interface::getObjectClassName(&object->g_pd)); + const auto typeHash = hash(type); - switch(objectNameHash){ + switch(typeHash){ case hash("bng"): case hash("hsl"): case hash("vsl"): @@ -115,12 +123,10 @@ ImageWithOffset OfflineObjectRenderer::patchToTempImage(String const& patch, flo case hash("vradio"): case hash("hradio"): case hash("vu"): - case hash("canvas"): case hash("pic"): case hash("keyboard"): case hash("scope~"): case hash("function"): - case hash("note"): case hash("knob"): case hash("gatom"): case hash("button"): @@ -129,12 +135,76 @@ ImageWithOffset OfflineObjectRenderer::patchToTempImage(String const& patch, flo case hash("messbox"): case hash("pad"): { + // use the bounds obtained from pd::Interface::getObjectBounds() + break; + } + case hash("note"): + { + auto noteObject = (t_fake_note*)object; + + // if note object isn't init, initialize because text buf will be empty + if (noteObject->x_init == 0) { + auto notePtr = (t_pd*)object; + (*notePtr)->c_wb->w_visfn((t_gobj*)noteObject, offlineCnv, 1); + } + obj_w = noteObject->x_max_pixwidth; + auto const noteText = String::fromUTF8(noteObject->x_buf, noteObject->x_bufsize).trim().replace("\\,", ",").replace("\\;", ";"); + auto const fontName = String::fromUTF8(noteObject->x_fontname->s_name); + auto const fontSize = noteObject->x_fontsize; + + Font fontMetrics; + + if (fontName.isEmpty() || fontName == "Inter") { + fontMetrics = Fonts::getVariableFont().withStyle(Font::plain).withHeight(fontSize); + } + else { + fontMetrics = Font(fontName, fontSize, Font::plain); + } + + int lineLength = 0; + int lineChars = 0; + int lineCount = 1; + int charIndex = 0; + + const auto objReducedPadding = obj_w - 3; + + while (charIndex < noteText.length()){ + auto charLength = fontMetrics.getStringWidth(noteText.substring(charIndex, charIndex + 1)); + lineLength += charLength; + lineChars++; + if (lineLength > objReducedPadding) { + if (lineChars == 1) { + charIndex++; + } + lineCount++; + lineLength = 0; + lineChars = 0; + } else { + charIndex++; + } + } + obj_h = lineCount * fontMetrics.getHeight(); + break; + } + case hash("canvas"): + { + if (((t_canvas*)object)->gl_isgraph == 1) { + break; + } + StringArray lines; + lines.addTokens(objectTextString, ";", ""); + auto charWidth = objectPtr->te_width; + obj_w = jmax(Font(16).getStringWidth(objectTextString), charWidth * 7); + obj_h = jmax(20, 20 * lines.size()); break; } case hash("cnv"): { - auto cnvObject = ((t_my_canvas*)object); - obj_w = cnvObject->x_vis_w; - obj_h = cnvObject->x_vis_h; + // example of how we could get the size from the objects themselves via static function + // in which case we would get the whole bounds + // this way all object dimension calculations would be in the same place + auto const cnvBounds = CanvasObject::getPDSize((t_my_canvas*)object); + obj_w = cnvBounds.getWidth(); + obj_h = cnvBounds.getHeight(); break; } case hash("message"): @@ -143,12 +213,16 @@ ImageWithOffset OfflineObjectRenderer::patchToTempImage(String const& patch, flo StringArray lines; lines.addTokens(objectTextString, ";", ""); auto charWidth = objectPtr->te_width; - // charWidth of 0 == auto width, which means we need to calculate the width manually :facepalm: - obj_w = jmax(Font(16).getStringWidth(objectTextString), charWidth * 7); - obj_h = jmax(20, 20 * lines.size()); - - if ((objectNameHash == hash("message")) || (((t_fake_text_define *) object)->x_textbuf.b_ob.te_type == T_OBJECT)) { - obj_w = 45; + // charWidth of 0 == auto width, which means we need to calculate the width manually + obj_w = jmax(Font(15).getStringWidth(objectTextString), charWidth * 7) + padding; + obj_h = 20 * jmax(1, lines.size()); + + if ((typeHash == hash("message")) || (((t_fake_text_define *) object)->x_textbuf.b_ob.te_type == T_OBJECT)) { + if (objectPtr->te_width == 0) { + obj_w = jmax(42, Font(15).getStringWidth(objectTextString) + padding); + } else { + obj_w = (objectPtr->te_width * 7) + padding; + } } break; } @@ -156,16 +230,17 @@ ImageWithOffset OfflineObjectRenderer::patchToTempImage(String const& patch, flo { obj_h = 20; if (objectPtr->te_width == 0) { - std::cout << "object text is: " << objectTextString << std::endl; - obj_w = Font(16).getStringWidth(objectTextString); + obj_w = Font(15).getStringWidth(objectTextString) + padding; + } else { + obj_w = (objectPtr->te_width * 7); } + auto maxIolets = jmax(pd::Interface::numOutlets(objectPtr), pd::Interface::numInlets(objectPtr)); + obj_w = jmax((maxIolets * 18) + padding, obj_w); + break; } - break; } - auto maxIolets = jmax(pd::Interface::numOutlets(objectPtr), pd::Interface::numInlets(objectPtr)); - auto maxSize = jmax(maxIolets * 18, obj_w); - rect.setBounds(obj_x, obj_y, maxSize, obj_h); + rect.setBounds(obj_x, obj_y, obj_w, obj_h); // put the object bounds into the rect list, and also calculate the total size of all objects objectRects.add(rect); From d93c75af0d5bddb7f035b1fb5cd062621b62a348 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Mon, 20 May 2024 20:23:26 +0930 Subject: [PATCH 0787/1030] fix alignment tools being off-by 2px when applying alignment --- Source/Pd/Patch.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Pd/Patch.cpp b/Source/Pd/Patch.cpp index 1b1044a96a..38a88ea501 100644 --- a/Source/Pd/Patch.cpp +++ b/Source/Pd/Patch.cpp @@ -526,7 +526,9 @@ void Patch::moveObjects(std::vector const& objects, int dx, int dy) void Patch::moveObjectTo(t_gobj* object, int x, int y) { if (auto patch = ptr.get()) { - pd::Interface::moveObject(patch.get(), object, x + 1544, y + 1544); // FIXME: why do we have to offset by 1544? + // Originally this was +1544, but caused issues with alignment tools being off-by xy +2px. + // FIXME: why do we have to do this at all? + pd::Interface::moveObject(patch.get(), object, x + 1542, y + 1542); } } From c56664131cdfb9464acfa59d4cff0527f0ed59dc Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sun, 19 May 2024 20:22:40 +0930 Subject: [PATCH 0788/1030] show if a text-object is a subpatch by placing a dot at the left --- Source/Objects/SubpatchObject.h | 2 ++ Source/Objects/TextObject.h | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/Source/Objects/SubpatchObject.h b/Source/Objects/SubpatchObject.h index bb3f5daa39..c94ea769e0 100644 --- a/Source/Objects/SubpatchObject.h +++ b/Source/Objects/SubpatchObject.h @@ -22,6 +22,8 @@ class SubpatchObject final : public TextBase { checkHvccCompatibility(getText(), subpatch.get()); } + setIsSubpatch(); + objectParameters.addParamBool("Is graph", cGeneral, &isGraphChild, { "No", "Yes" }); // There is a possibility that a donecanvasdialog message is sent inbetween the initialisation in pd and the initialisation of the plugdata object, making it possible to miss this message. This especially tends to happen if the messagebox is connected to a loadbang. diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index 050785f445..c24d875b34 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -180,6 +180,7 @@ class TextBase : public ObjectBase NVGcolor outlineColour; NVGcolor ioletAreaColour; + bool isSubpatch = false; public: TextBase(pd::WeakReference obj, Object* parent, bool valid = true) : ObjectBase(obj, parent) @@ -197,6 +198,11 @@ class TextBase : public ObjectBase ~TextBase() override = default; + void setIsSubpatch() + { + isSubpatch = true; + } + void update() override { if (auto obj = ptr.get()) { @@ -273,6 +279,12 @@ class TextBase : public ObjectBase // Instead, rendering at 2x scale gives us pretty good sharpness overall cachedTextRender.renderText(nvg, textArea, getImageScale()); } + if (isSubpatch){ + nvgBeginPath(nvg); + nvgCircle(nvg, 3, getHeight() * 0.5, 2); + nvgFillColor(nvg, convertColour(object->findColour(PlugDataColour::guiObjectInternalOutlineColour))); + nvgFill(nvg); + } } // Override to cancel default behaviour From e2d56e24e6670b45648ec8b12709f1a15163664e Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 23 May 2024 02:21:35 +0930 Subject: [PATCH 0789/1030] revert testing if connection is visible in render --- Source/Canvas.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 6d181d2c15..cafab62ced 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -522,7 +522,7 @@ void Canvas::renderAllConnections(NVGcontext* nvg, Rectangle area) for(auto* connection : connections) { nvgSave(nvg); - if(connection->intersectsRectangle(area)) { + if(connection->intersectsRectangle(area) && connection->isVisible()) { connection->render(nvg); if (showConnectionOrder) connectionsToDraw.add(connection); From 25b677d33794a434e6861d1807bc672b66c1a304 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 23 May 2024 01:28:01 +0200 Subject: [PATCH 0790/1030] Small cleanup --- Libraries/JUCE | 2 +- Source/Iolet.cpp | 2 ++ Source/Objects/Gem.h | 1 + Source/Objects/SubpatchObject.h | 13 ++++++++++--- Source/Objects/TextObject.h | 12 ------------ 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index 92db47b276..391015a7bd 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit 92db47b276f789b240b920669bb197ff46d29fa7 +Subproject commit 391015a7bd458827a0c380109d2887ba891851d6 diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index 1fb8b05b4c..54c59bc5cd 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -21,6 +21,8 @@ using namespace juce::gl; Iolet::Iolet(Object* parent, bool inlet) : NVGComponent(this) , object(parent) + , isSignal(false) + , isGemState(false) , insideGraph(parent->cnv->isGraph) { isInlet = inlet; diff --git a/Source/Objects/Gem.h b/Source/Objects/Gem.h index 51844eb983..72e50a1274 100644 --- a/Source/Objects/Gem.h +++ b/Source/Objects/Gem.h @@ -267,6 +267,7 @@ void destroyGemWindow(WindowInfo& info) { if(auto* window = info.getWindow()) { GemCallOnMessageThread([window, &info](){ window->removeFromDesktop(); + window->openGLContext.detach(); info.window.erase(window->instance); info.context.erase(window->instance); gemJUCEWindow[window->instance].reset(nullptr); diff --git a/Source/Objects/SubpatchObject.h b/Source/Objects/SubpatchObject.h index 32d7e2f209..13db07bb60 100644 --- a/Source/Objects/SubpatchObject.h +++ b/Source/Objects/SubpatchObject.h @@ -22,8 +22,6 @@ class SubpatchObject final : public TextBase { checkHvccCompatibility(getText(), subpatch.get()); } - setIsSubpatch(); - objectParameters.addParamBool("Is graph", cGeneral, &isGraphChild, { "No", "Yes" }); // There is a possibility that a donecanvasdialog message is sent inbetween the initialisation in pd and the initialisation of the plugdata object, making it possible to miss this message. This especially tends to happen if the messagebox is connected to a loadbang. @@ -41,7 +39,16 @@ class SubpatchObject final : public TextBase { object->hvccMode.removeListener(this); closeOpenedSubpatchers(); } - + void render(NVGcontext* nvg) override + { + TextBase::render(nvg); + + nvgBeginPath(nvg); + nvgCircle(nvg, 4, getHeight() * 0.5, 2); + nvgFillColor(nvg, convertColour(object->findColour(PlugDataColour::guiObjectInternalOutlineColour))); + nvgFill(nvg); + } + void update() override { isGraphChild = static_cast(subpatch->getPointer()->gl_isgraph); diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index c24d875b34..050785f445 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -180,7 +180,6 @@ class TextBase : public ObjectBase NVGcolor outlineColour; NVGcolor ioletAreaColour; - bool isSubpatch = false; public: TextBase(pd::WeakReference obj, Object* parent, bool valid = true) : ObjectBase(obj, parent) @@ -198,11 +197,6 @@ class TextBase : public ObjectBase ~TextBase() override = default; - void setIsSubpatch() - { - isSubpatch = true; - } - void update() override { if (auto obj = ptr.get()) { @@ -279,12 +273,6 @@ class TextBase : public ObjectBase // Instead, rendering at 2x scale gives us pretty good sharpness overall cachedTextRender.renderText(nvg, textArea, getImageScale()); } - if (isSubpatch){ - nvgBeginPath(nvg); - nvgCircle(nvg, 3, getHeight() * 0.5, 2); - nvgFillColor(nvg, convertColour(object->findColour(PlugDataColour::guiObjectInternalOutlineColour))); - nvgFill(nvg); - } } // Override to cancel default behaviour From b24625626d6f60ab93efbd2735dccb881082f91a Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 23 May 2024 15:00:03 +0930 Subject: [PATCH 0791/1030] quick fix for dragging tab onto another split centre --- Source/Tabbar/ResizableTabbedComponent.cpp | 61 ++++++++++++++-------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/Source/Tabbar/ResizableTabbedComponent.cpp b/Source/Tabbar/ResizableTabbedComponent.cpp index ab00ea16ec..6c2af70220 100644 --- a/Source/Tabbar/ResizableTabbedComponent.cpp +++ b/Source/Tabbar/ResizableTabbedComponent.cpp @@ -55,29 +55,44 @@ void ResizableTabbedComponent::itemDropped(SourceDetails const& dragSourceDetail if (dynamic_cast(dragSourceDetails.sourceComponent.get())) { switch (activeZone) { - case DropZones::Right: - splitMode = Split::SplitMode::Horizontal; - moveTabToNewSplit(dragSourceDetails); - break; - case DropZones::Left: - splitMode = Split::SplitMode::Horizontal; - moveTabToNewSplit(dragSourceDetails); - break; - case DropZones::Top: - moveTabToNewSplit(dragSourceDetails); - // splitMode = Split::SplitMode::Vertical; - break; - case DropZones::Bottom: - moveTabToNewSplit(dragSourceDetails); - // splitMode = Split::SplitMode::Vertical; - break; - case DropZones::TabBar: - splitMode = Split::SplitMode::None; - break; - case DropZones::Centre: - moveTabToNewSplit(dragSourceDetails); - break; - } + case DropZones::Right: + splitMode = Split::SplitMode::Horizontal; + moveTabToNewSplit(dragSourceDetails); + break; + case DropZones::Left: + splitMode = Split::SplitMode::Horizontal; + moveTabToNewSplit(dragSourceDetails); + break; + case DropZones::Top: + moveTabToNewSplit(dragSourceDetails); + // splitMode = Split::SplitMode::Vertical; + break; + case DropZones::Bottom: + moveTabToNewSplit(dragSourceDetails); + // splitMode = Split::SplitMode::Vertical; + break; + case DropZones::TabBar: + splitMode = Split::SplitMode::None; + break; + case DropZones::Centre: + { + auto *sourceTabButton = dynamic_cast(dragSourceDetails.sourceComponent.get()); + auto tabCanvas = sourceTabButton->getTabComponent()->getCurrentCanvas(); + + if (sourceTabButton->getTabComponent()->getNumTabs() < 2) { + tabCanvas->editor->splitView.setFocus(this); + tabCanvas->editor->splitView.removeSplit(sourceTabButton->getTabComponent()); + tabCanvas->editor->splitView.resized(); + for (auto *split: editor->splitView.splits) { + split->setBoundsWithFactors(getParentComponent()->getLocalBounds()); + } + } + moveToSplit(this, tabCanvas); + break; + } + default: + break; + } } else if (dynamic_cast(dragSourceDetails.sourceComponent.get())) { if (!tabComponent) return; From 80287d385474c200a9534aa95d7250004c7d1432 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 24 May 2024 02:09:24 +0930 Subject: [PATCH 0792/1030] make canvas active when centre dragged onto another split --- Source/Tabbar/ResizableTabbedComponent.cpp | 1 + Source/Tabbar/Tabbar.cpp | 7 +++++++ Source/Tabbar/Tabbar.h | 1 + 3 files changed, 9 insertions(+) diff --git a/Source/Tabbar/ResizableTabbedComponent.cpp b/Source/Tabbar/ResizableTabbedComponent.cpp index 6c2af70220..8e6e28a027 100644 --- a/Source/Tabbar/ResizableTabbedComponent.cpp +++ b/Source/Tabbar/ResizableTabbedComponent.cpp @@ -88,6 +88,7 @@ void ResizableTabbedComponent::itemDropped(SourceDetails const& dragSourceDetail } } moveToSplit(this, tabCanvas); + getTabComponent()->setCanvasActive(tabCanvas); break; } default: diff --git a/Source/Tabbar/Tabbar.cpp b/Source/Tabbar/Tabbar.cpp index 90b8bccbbd..ed30ab083d 100644 --- a/Source/Tabbar/Tabbar.cpp +++ b/Source/Tabbar/Tabbar.cpp @@ -484,6 +484,13 @@ void TabComponent::setCurrentTabIndex(int idx) } } +void TabComponent::setCanvasActive(Canvas* cnv) +{ + auto cnvIdx = getIndexOfCanvas(cnv); + if (cnvIdx != -1) + setCurrentTabIndex(cnvIdx); +} + int TabComponent::getNumVisibleTabs() { int numVisibleTabs = 0; diff --git a/Source/Tabbar/Tabbar.h b/Source/Tabbar/Tabbar.h index c6d867c400..fb558249b5 100644 --- a/Source/Tabbar/Tabbar.h +++ b/Source/Tabbar/Tabbar.h @@ -74,6 +74,7 @@ class TabComponent : public Component Component* getCurrentContentComponent() const noexcept { return panelComponent.get(); } int getCurrentTabIndex(); void setCurrentTabIndex(int idx); + void setCanvasActive(Canvas* cnv); int getNumTabs() const noexcept { return tabs->getNumTabs(); } int getNumVisibleTabs(); void removeTab(int idx); From 5fc9d915ecfaab4c65a6b67fc98a8604a80595a7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 26 May 2024 21:00:08 +0200 Subject: [PATCH 0793/1030] UX improvements: new welcome screen, cleaned up statusbar controls, less outlines, improved sidebar look --- Resources/Fonts/IconFont.ttf | Bin 61160 -> 61232 bytes Source/Canvas.cpp | 28 +- Source/Canvas.h | 2 + Source/CanvasViewport.h | 3 - Source/Components/Buttons.h | 1 + Source/Constants.h | 10 +- Source/Dialogs/AlignmentTools.h | 4 +- Source/Dialogs/AudioOutputSettings.h | 187 ++++++++++++++ Source/Dialogs/Dialogs.cpp | 96 ++++++- Source/Dialogs/MainMenu.h | 141 ---------- Source/Dialogs/OverlayDisplaySettings.h | 74 ++++-- Source/Dialogs/SnapSettings.h | 63 +++-- Source/NVGSurface.cpp | 9 +- Source/PluginEditor.cpp | 139 ++-------- Source/PluginEditor.h | 6 +- Source/Sidebar/Palettes.h | 44 ++-- Source/Sidebar/Sidebar.cpp | 31 +-- Source/Sidebar/Sidebar.h | 3 +- Source/Statusbar.cpp | 312 +++++++---------------- Source/Statusbar.h | 16 +- Source/Tabbar/Tabbar.cpp | 305 ++++++++++++++-------- Source/Utility/Autosave.h | 38 ++- Source/Utility/OfflineObjectRenderer.cpp | 167 ++++++++++++ Source/Utility/OfflineObjectRenderer.h | 2 + 24 files changed, 957 insertions(+), 724 deletions(-) create mode 100644 Source/Dialogs/AudioOutputSettings.h diff --git a/Resources/Fonts/IconFont.ttf b/Resources/Fonts/IconFont.ttf index b85af7ebc0476ddc52a7d9959be9650c0e42e602..948f388dec2c1de412b8f451a00a14080f8af95a 100644 GIT binary patch delta 950 zcmaEHmwCfIW(fvH1_lORh6V;^h5$FW5Z^_@?&=H-??om`*!jKZ0m^Zt=TxTYKjTRR zGA$Sw9=yp&O-xbycdP>_cL#{gGJpa+?>K;J8AX76m5khyiik_Ap91+i7#Nsca`KZC zFDzv-o47E(0ZcsY1i@xI~n;_Kr3!SBZ3Bfuu0BM>CeC$LN4gJ7886u~P( zlY}k^{Snp>E)hN@A|^6Thznd2crC~zXj*VgNLr{}m{ho2gkHqI$bV5W zQD35!qAQ};M8ApAiJ20s6Q>n-EZ!vkRDw;yu0)H(dr4c8^^zZ@bfl`K8;E31$kNEp z$(fUDkb5TYQvSRGrGm1}795kA*hBw|v9D&mzxfiEFmpXKgW|sg4pr731~G;xh9m|C zVG}cBMI$j$K|MxgB|aum5phK&HB&})GjnkHW9`71*}sG=0`lOYks>X z)$nh6LH&G|UBXTQhE^-BnYl#y0-c08LY+)G*jaca1sy}!MSxP)E3FIzoP1Dv;$W^G`dxIxMW3fnoCoKGO%xz-#~j D-hdO` delta 877 zcmW+!ZD>4!DT`i0A& zbHg|`y5W!FN9jcNN9l@4C?%0mIuIr@f(Df+W0YEp78D%)AzEs+w>#Mj59fWK_jz7k zIPcuD;>BZy9Z&#(LU0BsKw#g#P}`N4r)L3J%bvG4No%)B51$zPX29}kY?7ot09K}> z$9wvPKV%*0EfIL5gwQ<1E&wPqsmr57@v-(?@=sFF1EAPDIMUa1YjT}FKg~s65C=Up zI`UQg`Df`j0K9A^im(6Qf$Y)z?0K|X=YIp7m<$5o$zK(j{QFdz_-QL9AHxh9ASDKL zo+Uv%Xj8gkSNa;wI!^FU-Pq)s;m1UI|s>0QvI#|6~(_fRU_1CU`n5*lpyY0%h_?{#>uH70g6W_(fK(nquYVt;n`T$8Wq!5-tDRCBO-rg^hvSIeoE^^fD9 z=no_hN(VPvyISwGdD~W{wZN`JXAY%;`rygWeW8}a#fR6!o9#^d;*rG;U&n(Exii%H zb7$r#K00~~ccHGU-TLmCh&?jZWA0h#J=e$d-R$o_PM^@2zZi_cFZU0Q4QYn14bP68 z8j(kXX&=5wt3umWRZ_W<&M_uMf&%h(7|F^oAh$uWPL0K8Fe9E5Dh1I%u?<$6RWu_N zC$hXtX%k#%H)Zg6-Gn#i`$XpvJziK%|G;21Mi3!2f;3ILdDaE#clW6jE7EkGliB(f zs}RDNe7_1pnrK9x6gui->tabChanged(); } + + editor->statusbar->updateZoomLevel(); + editor->repaint(); // Make sure everything it up to date } int Canvas::getTabIndex() @@ -1812,7 +1815,7 @@ void Canvas::alignObjects(Align alignment) } break; } - case Align::VCenter: { + case Align::VCentre: { auto centrePos = selectedBounds.getCentreX(); for (auto* object : objects) { auto objectBounds = object->getBounds(); @@ -1835,7 +1838,7 @@ void Canvas::alignObjects(Align alignment) } break; } - case Align::HCenter: { + case Align::HCentre: { auto centerPos = selectedBounds.getCentreY(); for (auto* object : objects) { auto objectBounds = object->getBounds(); @@ -1912,6 +1915,7 @@ void Canvas::valueChanged(Value& v) { // Update zoom if (v.refersToSameSourceAs(zoomScale)) { + editor->statusbar->updateZoomLevel(); hideSuggestions(); } else if (v.refersToSameSourceAs(patchWidth)) { // limit canvas width to smallest object (11px) @@ -2205,3 +2209,23 @@ void Canvas::resized() connectionLayer.setBounds(getLocalBounds()); objectLayer.setBounds(getLocalBounds()); } + +String Canvas::generateSilhouetteSVG() +{ + String svgContent; + + auto regionOfInterest = Rectangle(); + for (auto* object : objects) { + regionOfInterest = regionOfInterest.getUnion(object->getBounds().reduced(Object::margin)); + } + + for (auto* object : objects) + { + auto rect = object->getBounds().reduced(Object::margin) - regionOfInterest.getPosition(); + svgContent += String::formatted( + "\n", + rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight(), Corners::objectCornerRadius, Corners::objectCornerRadius); + } + + return "\n" + svgContent + ""; +} diff --git a/Source/Canvas.h b/Source/Canvas.h index 81dfce618b..2024a8c4bf 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -136,6 +136,8 @@ class Canvas : public Component void jumpToOrigin(); void zoomToFitAll(); + String generateSilhouetteSVG(); + float getRenderScale() const; bool autoscroll(MouseEvent const& e); diff --git a/Source/CanvasViewport.h b/Source/CanvasViewport.h index 43ef48692a..8b0e25a509 100644 --- a/Source/CanvasViewport.h +++ b/Source/CanvasViewport.h @@ -386,9 +386,6 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent // This is needed to make sure the viewport the current canvas bounds to the lastVisibleArea variable // Without this, future calls to getViewPosition() will give wrong results resized(); - - editor->setZoomLabelLevel(newScaleFactor); - cnv->zoomScale = newScaleFactor; } diff --git a/Source/Components/Buttons.h b/Source/Components/Buttons.h index cee3b3f48a..ca52bbffa9 100644 --- a/Source/Components/Buttons.h +++ b/Source/Components/Buttons.h @@ -144,6 +144,7 @@ class SettingsToolbarButton : public TextButton { } }; +// TODO: only used for Heavy toolchain now. rename, and move it over there class WelcomePanelButton : public Component { public: diff --git a/Source/Constants.h b/Source/Constants.h index 7e418cd652..3ec173cbef 100644 --- a/Source/Constants.h +++ b/Source/Constants.h @@ -78,7 +78,7 @@ struct Icons { inline static String const SnapCenters = "$"; inline static String const ExportState = "^"; inline static String const Trash = "~"; - inline static String const Fullscreen = "&"; + inline static String const CanvasSettings = "&"; inline static String const Eyedropper = "@"; inline static String const Debug = "?"; @@ -104,11 +104,11 @@ struct Icons { inline static String const AlignLeft = "4"; inline static String const AlignRight = "5"; - inline static String const AlignVCentre = "6"; + inline static String const AlignHCentre = "6"; inline static String const AlignHDistribute = "/"; inline static String const AlignTop = "7"; inline static String const AlignBottom = "8"; - inline static String const AlignHCentre = "9"; + inline static String const AlignVCentre = "9"; inline static String const AlignVDistribute = "*"; @@ -414,10 +414,10 @@ enum Overlay { enum Align { Left = 0, Right, - HCenter, + HCentre, HDistribute, Top, Bottom, - VCenter, + VCentre, VDistribute }; diff --git a/Source/Dialogs/AlignmentTools.h b/Source/Dialogs/AlignmentTools.h index 6f70ac8fb1..f322715856 100644 --- a/Source/Dialogs/AlignmentTools.h +++ b/Source/Dialogs/AlignmentTools.h @@ -112,7 +112,7 @@ class AlignmentTools : public Component { alignButtons[AlignButton::VCentre]->onClick = [this]() { auto cnv = pluginEditor->getCurrentCanvas(); if (cnv) - cnv->alignObjects(Align::VCenter); + cnv->alignObjects(Align::VCentre); }; alignButtons[AlignButton::Top]->onClick = [this]() { @@ -130,7 +130,7 @@ class AlignmentTools : public Component { alignButtons[AlignButton::HCentre]->onClick = [this]() { auto cnv = pluginEditor->getCurrentCanvas(); if (cnv) - cnv->alignObjects(Align::HCenter); + cnv->alignObjects(Align::HCentre); }; alignButtons[AlignButton::HDistribute]->onClick = [this]() { diff --git a/Source/Dialogs/AudioOutputSettings.h b/Source/Dialogs/AudioOutputSettings.h new file mode 100644 index 0000000000..ebd022ecd1 --- /dev/null +++ b/Source/Dialogs/AudioOutputSettings.h @@ -0,0 +1,187 @@ +#pragma once + +#include + +#include +#include "Constants.h" +#include "LookAndFeel.h" +#include "Components/DraggableNumber.h" +#include "PluginEditor.h" + +class OversampleSettings : public Component { +public: + std::function onChange = [](int) {}; + + explicit OversampleSettings(int currentSelection) + { + one.setConnectedEdges(Button::ConnectedOnRight); + two.setConnectedEdges(Button::ConnectedOnLeft | Button::ConnectedOnRight); + four.setConnectedEdges(Button::ConnectedOnLeft | Button::ConnectedOnRight); + eight.setConnectedEdges(Button::ConnectedOnLeft); + + auto buttons = Array { &one, &two, &four, &eight }; + + int i = 0; + for (auto* button : buttons) { + button->setRadioGroupId(hash("oversampling_selector")); + button->setClickingTogglesState(true); + button->onClick = [this, i]() { + onChange(i); + }; + + button->setColour(TextButton::textColourOffId, findColour(PlugDataColour::popupMenuTextColourId)); + button->setColour(TextButton::textColourOnId, findColour(PlugDataColour::popupMenuActiveTextColourId)); + button->setColour(TextButton::buttonColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.04f)); + button->setColour(TextButton::buttonOnColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.075f)); + button->setColour(ComboBox::outlineColourId, Colours::transparentBlack); + + addAndMakeVisible(button); + i++; + } + + buttons[currentSelection]->setToggleState(true, dontSendNotification); + + setSize(180, 50); + } + +private: + void resized() override + { + auto b = getLocalBounds().reduced(4, 4); + auto buttonWidth = b.getWidth() / 4; + + one.setBounds(b.removeFromLeft(buttonWidth)); + two.setBounds(b.removeFromLeft(buttonWidth).expanded(1, 0)); + four.setBounds(b.removeFromLeft(buttonWidth).expanded(1, 0)); + eight.setBounds(b.removeFromLeft(buttonWidth).expanded(1, 0)); + } + + TextButton one = TextButton("1x"); + TextButton two = TextButton("2x"); + TextButton four = TextButton("4x"); + TextButton eight = TextButton("8x"); +}; + +class AudioOutputSettings : public Component { + + class LimiterEnableButton : public Component + , public SettableTooltipClient { + + String icon; + String text; + bool state; + bool buttonHover = false; + + public: + std::function onClick; + + LimiterEnableButton(AudioOutputSettings* parent, String const& iconText, String text, bool initState) + : icon(iconText) + , text(text) + , state(initState) + { + } + + void paint(Graphics& g) override + { + auto iconColour = state ? findColour(PlugDataColour::toolbarActiveColourId) : findColour(PlugDataColour::toolbarTextColourId); + auto textColour = findColour(PlugDataColour::toolbarTextColourId); + + if (isMouseOver()) { + iconColour = iconColour.contrasting(0.3f); + textColour = textColour.contrasting(0.3f); + } + + Fonts::drawIcon(g, icon, Rectangle(0, 0, 30, getHeight()), iconColour, 14); + Fonts::drawText(g, text, Rectangle(30, 0, getWidth(), getHeight()), textColour, 14); + } + + + void mouseEnter(MouseEvent const& e) override + { + buttonHover = true; + repaint(); + } + + void mouseExit(MouseEvent const& e) override + { + buttonHover = false; + repaint(); + } + + void mouseDown(MouseEvent const& e) override + { + state = !state; + onClick(state); + repaint(); + } + }; + +public: + AudioOutputSettings(PluginProcessor* pd) : oversampleSettings(SettingsFile::getInstance()->getValueTree().getProperty("Oversampling")) + { + enableLimiterButton = std::make_unique(this, Icons::Protection, "Enable limiter", SettingsFile::getInstance()->getProperty("protected")); + enableLimiterButton->onClick = [pd](bool state){ + pd->setProtectedMode(state); + SettingsFile::getInstance()->setProperty("protected", state); + }; + addAndMakeVisible(*enableLimiterButton); + addAndMakeVisible(oversampleSettings); + oversampleSettings.onChange = [pd](int value){ + pd->setOversampling(value); + }; + + setSize(160, 125); + } + + ~AudioOutputSettings() + { + isShowing = false; + } + + void resized() override + { + auto bounds = getLocalBounds().reduced(4.0f).withTrimmedTop(24); + + enableLimiterButton->setBounds(bounds.removeFromTop(32)); + + bounds.removeFromTop(32); + oversampleSettings.setBounds(bounds.removeFromTop(28)); + } + + void paint(Graphics& g) override + { + g.setColour(findColour(PlugDataColour::popupMenuTextColourId)); + g.setFont(Fonts::getBoldFont().withHeight(15)); + g.drawText("Limiter", 0, 0, getWidth(), 24, Justification::centred); + + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); + g.drawLine(4, 24, getWidth() - 8, 24); + + + g.setColour(findColour(PlugDataColour::popupMenuTextColourId)); + g.setFont(Fonts::getBoldFont().withHeight(15)); + g.drawText("Oversampling", 0, 56, getWidth(), 24, Justification::centred); + + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); + g.drawLine(4, 84, getWidth() - 8, 84); + } + + static void show(PluginEditor* editor, Rectangle bounds) + { + if (isShowing) + return; + + isShowing = true; + + auto audioOutputSettings = std::make_unique(editor->pd); + editor->showCalloutBox(std::move(audioOutputSettings), bounds); + } + +private: + static inline bool isShowing = false; + + std::unique_ptr enableLimiterButton; + OversampleSettings oversampleSettings; + +}; diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index aa7cc41e30..2a864bf614 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -562,7 +562,16 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent Forward, Backward, ToBack, - Properties + Properties, + + AlignLeft, + AlignHCentre, + AlignRight, + AlignHDistribute, + AlignTop, + AlignVCentre, + AlignBottom, + AlignVDistribute }; // Create popup menu PopupMenu popupMenu; @@ -607,6 +616,67 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent orderMenu.addItem(Backward, "Move backward", object != nullptr && !locked); orderMenu.addItem(ToBack, "To Back", object != nullptr && !locked); popupMenu.addSubMenu("Order", orderMenu, !locked); + + class AlignmentMenuItem : public PopupMenu::CustomComponent { + + String menuItemIcon; + String menuItemText; + + public: + bool isActive = true; + + AlignmentMenuItem(String icon, String text) + : menuItemIcon(std::move(icon)) + , menuItemText(std::move(text)) + { + } + + void getIdealSize(int& idealWidth, int& idealHeight) override + { + idealWidth = 170; + idealHeight = 24; + } + + void paint(Graphics& g) override + { + auto r = getLocalBounds(); + + auto colour = findColour(PopupMenu::textColourId).withMultipliedAlpha(isActive ? 1.0f : 0.5f); + if (isItemHighlighted() && isActive) { + g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId)); + + PlugDataLook::fillSmoothedRectangle(g, r.toFloat().reduced(0, 1), Corners::defaultCornerRadius); + colour = findColour(PlugDataColour::popupMenuActiveTextColourId); + } + g.setColour(colour); + + r.reduce(jmin(5, r.getWidth() / 20), 0); + + auto maxFontHeight = (float)r.getHeight() / 1.3f; + auto iconArea = r.removeFromLeft(roundToInt(maxFontHeight)).withSizeKeepingCentre(maxFontHeight, maxFontHeight); + + if (menuItemIcon.isNotEmpty()) { + Fonts::drawIcon(g, menuItemIcon, iconArea.translated(3.0f, 0.0f), colour, std::min(15.0f, maxFontHeight), true); + } + r.removeFromLeft(roundToInt(maxFontHeight * 0.5f)); + + int fontHeight = std::min(17.0f, maxFontHeight); + r.removeFromRight(3); + Fonts::drawFittedText(g, menuItemText, r, colour, fontHeight); + } + }; + + PopupMenu alignMenu; + alignMenu.addCustomItem(AlignLeft, std::make_unique(Icons::AlignLeft, "Left"), nullptr, "Left"); + alignMenu.addCustomItem(AlignHCentre, std::make_unique(Icons::AlignHCentre, "Centre horizontally"), nullptr, "Centre horizontally"); + alignMenu.addCustomItem(AlignRight, std::make_unique(Icons::AlignRight, "Right"), nullptr, "Right"); + alignMenu.addCustomItem(AlignHDistribute, std::make_unique(Icons::AlignHDistribute, "Distribute horizonally"), nullptr, "Distribute horizonally"); + alignMenu.addSeparator(); + alignMenu.addCustomItem(AlignTop, std::make_unique(Icons::AlignTop, "Top"), nullptr, "Top"); + alignMenu.addCustomItem(AlignVCentre, std::make_unique(Icons::AlignVCentre, "Centre verticaly"), nullptr, "Centre verticaly"); + alignMenu.addCustomItem(AlignBottom, std::make_unique(Icons::AlignBottom, "Bottom"), nullptr, "Bottom"); + alignMenu.addCustomItem(AlignVDistribute, std::make_unique(Icons::AlignVDistribute, "Distribute vertically"), nullptr, "Distribute vertically"); + popupMenu.addSubMenu("Align", alignMenu, !locked); popupMenu.addSeparator(); popupMenu.addItem(Properties, "Properties", (originalComponent == cnv || (object && !params.getParameters().isEmpty())) && !locked); @@ -732,6 +802,30 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent case Reference: Dialogs::showObjectReferenceDialog(&editor->openedDialog, editor, object->getType()); break; + case AlignLeft: + cnv->alignObjects(Align::Left); + break; + case AlignHCentre: + cnv->alignObjects(Align::HCentre); + break; + case AlignRight: + cnv->alignObjects(Align::Right); + break; + case AlignHDistribute: + cnv->alignObjects(Align::HDistribute); + break; + case AlignTop: + cnv->alignObjects(Align::Top); + break; + case AlignVCentre: + cnv->alignObjects(Align::VCentre); + break; + case AlignBottom: + cnv->alignObjects(Align::Bottom); + break; + case AlignVDistribute: + cnv->alignObjects(Align::VDistribute); + break; default: break; } diff --git a/Source/Dialogs/MainMenu.h b/Source/Dialogs/MainMenu.h index 900d5919fb..d6d2378cb0 100644 --- a/Source/Dialogs/MainMenu.h +++ b/Source/Dialogs/MainMenu.h @@ -15,16 +15,12 @@ class MainMenu : public PopupMenu { explicit MainMenu(PluginEditor* editor) : settingsTree(SettingsFile::getInstance()->getValueTree()) , themeSelector(settingsTree) - , zoomSelector(editor) { addCustomItem(1, themeSelector, 70, 45, false); - addCustomItem(2, zoomSelector, 70, 30, false); addSeparator(); addCustomItem(getMenuItemID(MenuItem::NewPatch), std::unique_ptr(menuItems[getMenuItemIndex(MenuItem::NewPatch)]), nullptr, "New patch"); - addSeparator(); - addCustomItem(getMenuItemID(MenuItem::OpenPatch), std::unique_ptr(menuItems[getMenuItemIndex(MenuItem::OpenPatch)]), nullptr, "Open patch"); auto recentlyOpened = new PopupMenu(); @@ -102,136 +98,12 @@ class MainMenu : public PopupMenu { bool hvccModeEnabled = settingsTree.hasProperty("hvcc_mode") && static_cast(settingsTree.getProperty("hvcc_mode")); bool hasCanvas = editor->getCurrentCanvas() != nullptr; - zoomSelector.setEnabled(hasCanvas); menuItems[getMenuItemIndex(MenuItem::Save)]->isActive = hasCanvas; menuItems[getMenuItemIndex(MenuItem::SaveAs)]->isActive = hasCanvas; menuItems[getMenuItemIndex(MenuItem::CompiledMode)]->isTicked = hvccModeEnabled; } - class ZoomSelector : public Component { - TextButton zoomIn; - TextButton zoomOut; - TextButton zoomReset; - - Value zoomValue; - - PluginEditor* _editor; - - float const minZoom = 0.2f; - float const maxZoom = 3.0f; - - public: - explicit ZoomSelector(PluginEditor* editor) - : _editor(editor) - { - auto cnv = _editor->getCurrentCanvas(); - auto buttonText = String("100.0%"); - if (cnv) - buttonText = String(getValue(cnv->zoomScale) * 100.0f, 1) + "%"; - - zoomIn.setButtonText("+"); - zoomReset.setButtonText(buttonText); - zoomOut.setButtonText("-"); - - for (auto* button : Array { &zoomIn, &zoomReset, &zoomOut }) { - button->setColour(TextButton::textColourOffId, findColour(PlugDataColour::popupMenuTextColourId)); - button->setColour(TextButton::textColourOnId, findColour(PlugDataColour::popupMenuActiveTextColourId)); - button->setColour(TextButton::buttonColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.035f)); - button->setColour(TextButton::buttonOnColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.075f)); - button->setColour(ComboBox::outlineColourId, Colours::transparentBlack); - addAndMakeVisible(button); - } - - zoomIn.setConnectedEdges(Button::ConnectedOnLeft); - zoomOut.setConnectedEdges(Button::ConnectedOnRight); - zoomReset.setConnectedEdges(12); - - zoomIn.onClick = [this]() { - applyZoom(ZoomIn); - }; - zoomOut.onClick = [this]() { - applyZoom(ZoomOut); - }; - zoomReset.onClick = [this]() { - applyZoom(Reset); - }; - } - - enum ZoomType { ZoomIn, - ZoomOut, - Reset }; - - void lookAndFeelChanged() override - { - for (auto* button : Array { &zoomIn, &zoomReset, &zoomOut }) { - button->setColour(TextButton::textColourOffId, findColour(PlugDataColour::popupMenuTextColourId)); - button->setColour(TextButton::textColourOnId, findColour(PlugDataColour::popupMenuActiveTextColourId)); - button->setColour(TextButton::buttonColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.035f)); - button->setColour(TextButton::buttonOnColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.075f)); - button->setColour(ComboBox::outlineColourId, Colours::transparentBlack); - } - } - - void applyZoom(ZoomType zoomEventType) - { - auto cnv = _editor->getCurrentCanvas(); - - if (!cnv) - return; - - auto scale = getValue(cnv->zoomScale); - - // Apply limits - switch (zoomEventType) { - case ZoomIn: - scale = std::clamp(scale + 0.1f, minZoom, maxZoom); - break; - case ZoomOut: - scale = std::clamp(scale - 0.1f, minZoom, maxZoom); - break; - default: - scale = 1.0f; - break; - } - - // Round in case we zoomed with scrolling - scale = static_cast(static_cast(round(scale * 10.))) / 10.; - - // Get the current viewport position in canvas coordinates - auto oldViewportPosition = cnv->getLocalPoint(cnv->viewport.get(), cnv->viewport->getViewArea().withZeroOrigin().toFloat().getCentre()); - - // Apply transform and make sure viewport bounds get updated - cnv->setTransform(AffineTransform::scale(scale)); - cnv->viewport->resized(); - - // After zooming, get the new viewport position in canvas coordinates - auto newViewportPosition = cnv->getLocalPoint(cnv->viewport.get(), cnv->viewport->getViewArea().withZeroOrigin().toFloat().getCentre()); - - // Calculate offset to keep the center point of the viewport the same as before this zoom action - auto offset = newViewportPosition - oldViewportPosition; - - // Set the new canvas position - // Alex: there is an accumulated error when zooming in/out - // possibly we should save the canvas position as an additional Point ? - cnv->setTopLeftPosition((cnv->getPosition().toFloat() + offset).roundToInt()); - - cnv->zoomScale = scale; - - zoomReset.setButtonText(String(scale * 100.0f, 1) + "%"); - } - - void resized() override - { - auto bounds = getLocalBounds().reduced(8, 4); - int buttonWidth = (getWidth() - 8) / 3; - - zoomOut.setBounds(bounds.removeFromLeft(buttonWidth).expanded(1, 0)); - zoomReset.setBounds(bounds.removeFromLeft(buttonWidth).expanded(1, 0)); - zoomIn.setBounds(bounds.removeFromLeft(buttonWidth).expanded(1, 0)); - } - }; - class IconMenuItem : public PopupMenu::CustomComponent { String menuItemIcon; @@ -258,13 +130,6 @@ class MainMenu : public PopupMenu { idealHeight = 24; } -#if JUCE_IOS // On iOS, the mouseUp event arrives after the menu has already been dismissed... - void mouseDown(MouseEvent const& e) override - { - triggerMenuItem(); - } -#endif - void paint(Graphics& g) override { auto r = getLocalBounds(); @@ -318,11 +183,6 @@ class MainMenu : public PopupMenu { r.removeFromRight(3); Fonts::drawFittedText(g, menuItemText, r, colour, fontHeight); - - /* - if (shortcutKeyText.isNotEmpty()) { - Fonts::drawText(g, shortcutKeyText, r.translated(-2, 0), findColour(PopupMenu::textColourId), f2.getHeight() * 0.75f, Justification::centredRight); - } */ } }; @@ -451,5 +311,4 @@ class MainMenu : public PopupMenu { ValueTree settingsTree; ThemeSelector themeSelector; - ZoomSelector zoomSelector; }; diff --git a/Source/Dialogs/OverlayDisplaySettings.h b/Source/Dialogs/OverlayDisplaySettings.h index a4cb2591aa..60e07e00f8 100644 --- a/Source/Dialogs/OverlayDisplaySettings.h +++ b/Source/Dialogs/OverlayDisplaySettings.h @@ -7,6 +7,7 @@ #include "Constants.h" #include "PluginEditor.h" #include "LookAndFeel.h" +#include "Components/PropertiesPanel.h" class OverlayDisplaySettings : public Component { public: @@ -16,10 +17,9 @@ class OverlayDisplaySettings : public Component { enum ButtonType { Edit = 0, Lock, - Run, Alt }; - OwnedArray buttons { new SmallIconButton("edit"), new SmallIconButton("lock"), new SmallIconButton("run"), new SmallIconButton("alt") }; + OwnedArray buttons { new SmallIconButton("edit"), new SmallIconButton("lock"), new SmallIconButton("alt") }; Label textLabel; String groupName; @@ -53,14 +53,12 @@ class OverlayDisplaySettings : public Component { buttons[Edit]->setButtonText(Icons::Edit); buttons[Lock]->setButtonText(Icons::Lock); - buttons[Run]->setButtonText(Icons::Presentation); buttons[Alt]->setButtonText(Icons::Eye); auto lowerCaseToolTip = toolTip.toLowerCase(); buttons[Edit]->setTooltip("Show " + lowerCaseToolTip + " in edit mode"); - buttons[Lock]->setTooltip("Show " + lowerCaseToolTip + " in run mode"); - buttons[Run]->setTooltip("Show " + lowerCaseToolTip + " in presentation mode"); + buttons[Lock]->setTooltip("Show " + lowerCaseToolTip + " in lock mode"); buttons[Alt]->setTooltip("Show " + lowerCaseToolTip + " when overlay button is active"); textLabel.setText(groupName, dontSendNotification); @@ -70,12 +68,10 @@ class OverlayDisplaySettings : public Component { auto editState = static_cast(settings.getProperty("edit")); auto lockState = static_cast(settings.getProperty("lock")); - auto runState = static_cast(settings.getProperty("run")); auto altState = static_cast(settings.getProperty("alt")); buttons[Edit]->setToggleState(static_cast(editState & group), dontSendNotification); buttons[Lock]->setToggleState(static_cast(lockState & group), dontSendNotification); - buttons[Run]->setToggleState(static_cast(runState & group), dontSendNotification); buttons[Alt]->setToggleState(static_cast(altState & group), dontSendNotification); setSize(200, 30); @@ -109,8 +105,6 @@ class OverlayDisplaySettings : public Component { bounds.translate(25, 0); buttons[Lock]->setBounds(bounds); bounds.translate(25, 0); - buttons[Run]->setBounds(bounds); - bounds.translate(25, 0); buttons[Alt]->setBounds(bounds); bounds.translate(25, 0); } @@ -145,6 +139,11 @@ class OverlayDisplaySettings : public Component { connection.add(new OverlaySelector(overlayTree, Order, "order", "Order", "Trigger order of multiple outlets")); connection.add(new OverlaySelector(overlayTree, Behind, "behind", "Behind", "Connection cables behind objects")); + debugModeValue.referTo(SettingsFile::getInstance()->getPropertyAsValue("debug_connections")); + connectionDebugToggle.reset(new PropertiesPanel::BoolComponent("Debug", debugModeValue, { "No", "Yes" })); + connectionDebugToggle->setTooltip("Enable connection debugging tooltips"); + addAndMakeVisible(*connectionDebugToggle); + groups.add(&canvas); groups.add(&object); groups.add(&connection); @@ -154,41 +153,57 @@ class OverlayDisplaySettings : public Component { addAndMakeVisible(item); } } - setSize(200, 505); + setSize(335, 200); + } void resized() override { - auto bounds = getLocalBounds().reduced(4, 0); + auto bounds = getLocalBounds().reduced(4, 0).withTrimmedTop(24); auto const labelHeight = 26; auto const itemHeight = 28; - auto const spacing = 2; - canvasLabel.setBounds(bounds.removeFromTop(labelHeight)); + auto leftSide = bounds.removeFromLeft(bounds.proportionOfWidth(0.5f)).withTrimmedRight(4); + auto rightSide = bounds.withTrimmedLeft(4); + + canvasLabel.setBounds(leftSide.removeFromTop(labelHeight)); for (auto& item : canvas) { - item->setBounds(bounds.removeFromTop(itemHeight)); + item->setBounds(leftSide.removeFromTop(itemHeight)); } - bounds.removeFromTop(spacing); - objectLabel.setBounds(bounds.removeFromTop(labelHeight)); + leftSide.removeFromTop(2); + objectLabel.setBounds(leftSide.removeFromTop(labelHeight)); for (auto& item : object) { - item->setBounds(bounds.removeFromTop(itemHeight)); + item->setBounds(leftSide.removeFromTop(itemHeight)); } - bounds.removeFromTop(spacing); - connectionLabel.setBounds(bounds.removeFromTop(labelHeight)); + connectionLabel.setBounds(rightSide.removeFromTop(labelHeight)); for (auto& item : connection) { - item->setBounds(bounds.removeFromTop(itemHeight)); + item->setBounds(rightSide.removeFromTop(itemHeight)); } - setSize(200, bounds.getY() + 5); + + connectionDebugToggle->setBounds(rightSide.removeFromTop(itemHeight)); } void paint(Graphics& g) override { + g.setColour(findColour(PlugDataColour::popupMenuTextColourId)); + g.setFont(Fonts::getBoldFont().withHeight(15)); + g.drawText("Overlays", 0, 0, getWidth(), 24, Justification::centred); + + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); + g.drawLine(4, 24, getWidth() - 8, 24); + for (auto& group : groups) { auto groupBounds = group->getFirst()->getBounds().getUnion(group->getLast()->getBounds()); - + + bool isConnectionGroup = group == &connection; + if(isConnectionGroup) + { + groupBounds = groupBounds.withTrimmedBottom(-28); + } + // draw background rectangle g.setColour(findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.035f)); g.fillRoundedRectangle(groupBounds.toFloat(), Corners::largeCornerRadius); @@ -199,7 +214,7 @@ class OverlayDisplaySettings : public Component { // draw lines between items for (auto& item : *group){ - if ((group->size() >= 2) && (item != group->getLast())) + if ((group->size() >= 2) && ((item != group->getLast()) || isConnectionGroup)) g.drawHorizontalLine(item->getBottom(), groupBounds.getX(), groupBounds.getRight()); } } @@ -230,15 +245,18 @@ class OverlayDisplaySettings : public Component { AllOff = 0, EditDisplay, LockDisplay, - RunDisplay, AltDisplay }; - Array*> groups; + Array*> groups; - OwnedArray canvas; - OwnedArray object; - OwnedArray connection; + OwnedArray canvas; + OwnedArray object; + OwnedArray connection; + + Value debugModeValue; + std::unique_ptr connectionDebugToggle; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OverlayDisplaySettings) }; diff --git a/Source/Dialogs/SnapSettings.h b/Source/Dialogs/SnapSettings.h index a1f51ed328..b28828e3d8 100644 --- a/Source/Dialogs/SnapSettings.h +++ b/Source/Dialogs/SnapSettings.h @@ -59,7 +59,18 @@ class SnapSettings : public Component { EdgesBit = 2, CentersBit = 4 }; + + static void show(PluginEditor* editor, Rectangle bounds) + { + if (isShowing) + return; + isShowing = true; + + auto snapSettings = std::make_unique(); + editor->showCalloutBox(std::move(snapSettings), bounds); + } + class SnapSelector : public Component , public Value::Listener , public SettableTooltipClient { @@ -89,8 +100,6 @@ class SnapSettings : public Component { snapValue.referTo(SettingsFile::getInstance()->getPropertyAsValue(property)); snapValue.addListener(this); valueChanged(snapValue); - - setSize(110, 30); } void paint(Graphics& g) override @@ -163,6 +172,14 @@ class SnapSettings : public Component { SnapSettings() { + snapLabel.setText("Snap", dontSendNotification); + snapLabel.setFont(Fonts::getSemiBoldFont().withHeight(14)); + addAndMakeVisible(snapLabel); + + gridSizeLabel.setText("Grid Size", dontSendNotification); + gridSizeLabel.setFont(Fonts::getSemiBoldFont().withHeight(14)); + addAndMakeVisible(gridSizeLabel); + for (auto* group : buttonGroups) { addAndMakeVisible(group); group->addMouseListener(this, true); @@ -173,25 +190,30 @@ class SnapSettings : public Component { buttonGroups[SnapItem::Centers]->setTooltip("Snap to centers of objects"); addAndMakeVisible(gridSlider.get()); - - setSize(110, 500); + setSize(140, 182); } - + + ~SnapSettings() + { + isShowing = false; + } + void resized() override { - auto bounds = getLocalBounds(); + auto bounds = getLocalBounds().withTrimmedTop(24); + snapLabel.setBounds(bounds.removeFromTop(24)); buttonGroups[SnapItem::Edges]->setBounds(bounds.removeFromTop(26)); buttonGroups[SnapItem::Centers]->setBounds(bounds.removeFromTop(26)); buttonGroups[SnapItem::Grid]->setBounds(bounds.removeFromTop(26)); + + gridSizeLabel.setBounds(bounds.removeFromTop(24)); gridSlider->setBounds(bounds.removeFromTop(34)); - setSize(110, bounds.getY()); } void mouseUp(MouseEvent const& e) override { for (auto* group : buttonGroups) { group->dragToggledInteraction = false; - // button.button.setState(Button::ButtonState::buttonNormal); group->repaint(); } } @@ -205,21 +227,15 @@ class SnapSettings : public Component { } } } - - static void show(PluginEditor* editor, Rectangle bounds) - { - if (isShowing) - return; - - isShowing = true; - - auto snapSettings = std::make_unique(); - editor->showCalloutBox(std::move(snapSettings), bounds); - } - - ~SnapSettings() override + + void paint(Graphics& g) override { - isShowing = false; + g.setColour(findColour(PlugDataColour::popupMenuTextColourId)); + g.setFont(Fonts::getBoldFont().withHeight(15)); + g.drawText("Grid", 0, 0, getWidth(), 24, Justification::centred); + + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); + g.drawLine(4, 24, getWidth() - 8, 24); } enum MouseInteraction { @@ -230,8 +246,9 @@ class SnapSettings : public Component { MouseInteraction mouseInteraction = ToggledButtonOff; private: + static inline bool isShowing = false; - + Label snapLabel, gridSizeLabel; std::unique_ptr gridSlider = std::make_unique(); OwnedArray buttonGroups = { diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index d6aaa03b1b..9b70821743 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -19,6 +19,7 @@ using namespace juce::gl; #include "PluginEditor.h" #include "PluginMode.h" #include "Canvas.h" +#include "Sidebar/Sidebar.h" // meh... #define ENABLE_FPS_COUNT 0 @@ -408,14 +409,6 @@ void NVGSurface::render() if(!editor->pluginMode) { editor->splitView.render(nvg); // Render split view outlines and tab dnd areas - if(auto* zoomLabel = reinterpret_cast(editor->zoomLabel.get())) // If we don't cast through the first inherited class, this is UB - { - auto zoomLabelPos = getLocalPoint(zoomLabel, Point(0, 0)); - nvgSave(nvg); - nvgTranslate(nvg, zoomLabelPos.x, zoomLabelPos.y); - dynamic_cast(zoomLabel)->render(nvg); // Render zoom notifier at the bottom left - nvgRestore(nvg); - } } #if ENABLE_FPS_COUNT diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index b46b14e56c..f6895c965c 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -39,113 +39,6 @@ using namespace juce::gl; #include -class ZoomLabel : public TextButton - , public MultiTimer, public NVGComponent -{ - float animationAlpha = 0.0f; - float targetAlpha = 0.0f; - float increment = 0.0f; - - int const fadeTimer = 0; - int const expireTimer = 1; - - bool visible = false; - int initRun = 2; - -public: - ZoomLabel() : NVGComponent(this) - { - setInterceptsMouseClicks(false, false); - } - - void setZoomLevel(float value) - { - if (initRun > 0) { - initRun--; - return; - } - - setButtonText(String(value * 100, 1) + "%"); - fadeIn(); - } - - void fadeIn() - { - targetAlpha = 1.0f; - increment = 0.015f; - visible = true; - startTimer(fadeTimer, 1.0f / 60.0f); - } - - void fadeOut() - { - targetAlpha = 0.0f; - increment = -0.015f; - startTimer(fadeTimer, 1.0f / 60.0f); - } - - void timerCallback(int timerId) override - { - if(timerId == fadeTimer) { - - if((increment > 0.0f && animationAlpha >= targetAlpha) || (increment < 0.0f && animationAlpha <= targetAlpha)) - { - animationAlpha = targetAlpha; - visible = targetAlpha != 0.0f; - if(visible) - { - startTimer(expireTimer, 1500); - } - stopTimer(fadeTimer); - } - else { - animationAlpha += increment; - } - - findParentComponentOfClass()->nvgSurface.triggerRepaint(); - } - else { - fadeOut(); - stopTimer(expireTimer); - } - } - - void render(NVGcontext* nvg) override - { - if(visible) { - auto text = getButtonText(); - auto bg = findNVGColour(PlugDataColour::toolbarBackgroundColourId); - auto outline = findNVGColour(PlugDataColour::outlineColourId); - auto textColour = findNVGColour(PlugDataColour::toolbarTextColourId); - - nvgGlobalAlpha(nvg, std::clamp(animationAlpha, 0.0f, 1.0f)); - - nvgBeginPath(nvg); - nvgRoundedRect(nvg, 0, 0, getWidth(), getHeight(), Corners::defaultCornerRadius); - nvgFillColor(nvg, bg); - nvgFill(nvg); - nvgStrokeColor(nvg, outline); - nvgStrokeWidth(nvg, 1.0f); - nvgStroke(nvg); - - nvgFillColor(nvg, textColour); - nvgFontSize(nvg, 11.5f); - nvgFontFace(nvg, "Inter-Regular"); - nvgTextAlign(nvg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); - nvgText(nvg, getWidth() / 2.0f, getHeight() / 2.0f, text.toRawUTF8(), nullptr); - - nvgGlobalAlpha(nvg, 1.0f); // Reset alpha to 1.0 for other elements - } - } - -private: - int getTimerInterval() const - { - // Adjust this interval for smoother or faster animation - return 1000 / 60; // 60 FPS - } -}; - PluginEditor::PluginEditor(PluginProcessor& p) : AudioProcessorEditor(&p) @@ -155,7 +48,6 @@ PluginEditor::PluginEditor(PluginProcessor& p) , openedDialog(nullptr) , pluginMode(nullptr) , splitView(this) - , zoomLabel(std::make_unique()) , offlineRenderer(&p) , nvgSurface(this) , pluginConstrainer(*getConstrainer()) @@ -330,8 +222,6 @@ PluginEditor::PluginEditor(PluginProcessor& p) addModifierKeyListener(statusbar.get()); - addChildComponent(*zoomLabel); - addAndMakeVisible(&callOutSafeArea); callOutSafeArea.setAlwaysOnTop(true); callOutSafeArea.setInterceptsMouseClicks(false, true); @@ -393,11 +283,6 @@ SplitView* PluginEditor::getSplitView() return &splitView; } -void PluginEditor::setZoomLabelLevel(float value) -{ - zoomLabel->setZoomLevel(value); -} - void PluginEditor::setUseBorderResizer(bool shouldUse) { if (shouldUse) { @@ -447,9 +332,13 @@ void PluginEditor::paint(Graphics& g) } else { g.fillAll(baseColour); } - - g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); - g.drawLine(29.0f, toolbarHeight - 0.5f, static_cast(getWidth() - 29.5f), toolbarHeight - 0.5f, 1.0f); + + // Draw lines in case tabbar is not visible. Otherwise the sidebar outlines will stop too soon + if(!getCurrentCanvas()) { + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); + g.drawLine(palettes->isExpanded() ? palettes->getRight() : 29.5f, toolbarHeight, palettes->isExpanded() ? palettes->getRight() : 29.5f, toolbarHeight + 30); + g.drawLine(sidebar->getX(), toolbarHeight, sidebar->getX(), toolbarHeight + 30); + } } // Paint file drop outline @@ -544,8 +433,6 @@ void PluginEditor::resized() offset += 22; #endif - zoomLabel->setBounds(paletteWidth + 6, getHeight() - Statusbar::statusbarHeight - 32, 55, 23); - int buttonDisctance = 56; mainMenuButton.setBounds(offset, 0, toolbarHeight, toolbarHeight); undoButton.setBounds(buttonDisctance + offset, 0, toolbarHeight, toolbarHeight); @@ -558,8 +445,8 @@ void PluginEditor::resized() runButton.setBounds(startX + toolbarHeight - 1, 1, toolbarHeight, toolbarHeight - 2); presentButton.setBounds(startX + (2 * toolbarHeight) - 2, 1, toolbarHeight, toolbarHeight - 2); - auto windowControlsOffset = (useNonNativeTitlebar && !useLeftButtons) ? 150.0f : 60.0f; - + auto windowControlsOffset = (useNonNativeTitlebar && !useLeftButtons) ? 140.0f : 50.0f; + if (borderResizer && ProjectInfo::isStandalone) { borderResizer->setBounds(getLocalBounds()); } else if (cornerResizer) { @@ -573,6 +460,8 @@ void PluginEditor::resized() pd->lastUIWidth = getWidth(); pd->lastUIHeight = getHeight(); + + repaint(); // Some outlines are dependent on whether or not the sidebars are expanded, or whether or not a patch is opened } void PluginEditor::parentSizeChanged() @@ -1081,7 +970,7 @@ void PluginEditor::handleAsyncUpdate() presentButton.setEnabled(true); statusbar->centreButton.setEnabled(true); - statusbar->fitAllButton.setEnabled(true); + statusbar->zoomComboButton.setEnabled(true); addObjectMenuButton.setEnabled(true); } else { @@ -1093,7 +982,7 @@ void PluginEditor::handleAsyncUpdate() presentButton.setEnabled(false); statusbar->centreButton.setEnabled(false); - statusbar->fitAllButton.setEnabled(false); + statusbar->zoomComboButton.setEnabled(false); undoButton.setEnabled(false); redoButton.setEnabled(false); @@ -1103,6 +992,8 @@ void PluginEditor::handleAsyncUpdate() void PluginEditor::updateCommandStatus() { + statusbar->updateZoomLevel(); + // Make sure patches update their undo/redo state information soon pd->updatePatchUndoRedoState(); AsyncUpdater::triggerAsyncUpdate(); diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 4598f8e8e2..27c588a583 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -55,7 +55,6 @@ class CalloutArea : public Component, public Timer class ConnectionMessageDisplay; class Sidebar; class Statusbar; -class ZoomLabel; class Dialog; class Canvas; class TabComponent; @@ -144,7 +143,6 @@ class PluginEditor : public AudioProcessorEditor void enablePluginMode(Canvas* cnv); void commandKeyChanged(bool isHeld) override; - void setZoomLabelLevel(float value); void setUseBorderResizer(bool shouldUse); void showTouchSelectionHelper(bool shouldBeShown); @@ -176,8 +174,6 @@ class PluginEditor : public AudioProcessorEditor std::unique_ptr palettes; - std::unique_ptr zoomLabel; - OfflineObjectRenderer offlineRenderer; NVGSurface nvgSurface; @@ -202,7 +198,7 @@ class PluginEditor : public AudioProcessorEditor // Used by standalone to handle dragging the window WindowDragger windowDragger; - int const toolbarHeight = ProjectInfo::isStandalone ? 40 : 35; + int const toolbarHeight = ProjectInfo::isStandalone ? 38 : 35; MainToolbarButton mainMenuButton, undoButton, redoButton, addObjectMenuButton, pluginModeButton; ToolbarRadioButton editButton, runButton, presentButton; diff --git a/Source/Sidebar/Palettes.h b/Source/Sidebar/Palettes.h index f91c9042d7..8d164900d7 100644 --- a/Source/Sidebar/Palettes.h +++ b/Source/Sidebar/Palettes.h @@ -401,23 +401,10 @@ class PaletteSelector : public TextButton { void paint(Graphics& g) override { - auto backgroundColour = findColour(PlugDataColour::toolbarBackgroundColourId); - auto* editor = findParentComponentOfClass(); - if (ProjectInfo::isStandalone && editor && !editor->isActiveWindow()) { - backgroundColour = backgroundColour.brighter(backgroundColour.getBrightness() / 2.5f); - } - - g.setColour(backgroundColour); - g.fillRect(getLocalBounds().toFloat().withTrimmedTop(0.5f)); - - if (getToggleState()) { - g.setColour(findColour(PlugDataColour::toolbarActiveColourId).brighter(isMouseOver() ? 0.3f : 0.0f)); - g.fillRect(getLocalBounds().toFloat().withTrimmedTop(0.5f).removeFromRight(4)); - } else if (isMouseOver()) { + if (getToggleState() || isMouseOver()) { g.setColour(findColour(PlugDataColour::toolbarHoverColourId)); - g.fillRect(getLocalBounds().toFloat().withTrimmedTop(0.5f).removeFromRight(4)); + g.fillRoundedRectangle(getLocalBounds().toFloat().reduced(4.0f, 4.0f), Corners::defaultCornerRadius); } - g.saveState(); auto midX = static_cast(getWidth()) * 0.5f; @@ -748,24 +735,25 @@ class Palettes : public Component { if (view) { g.setColour(findColour(PlugDataColour::sidebarBackgroundColourId)); - g.fillRect(getLocalBounds().toFloat().withTrimmedTop(0.5f)); - } - - auto backgroundColour = findColour(PlugDataColour::toolbarBackgroundColourId); - if (ProjectInfo::isStandalone && !editor->isActiveWindow()) { - backgroundColour = backgroundColour.brighter(backgroundColour.getBrightness() / 2.5f); + g.fillRect(getLocalBounds().toFloat().withTrimmedTop(29.5f)); + g.fillRect(getLocalBounds().toFloat().removeFromLeft(30).withTrimmedTop(29.5f)); } - g.setColour(backgroundColour); - g.fillRect(getLocalBounds().toFloat().removeFromLeft(30).withTrimmedTop(0.5f)); } void paintOverChildren(Graphics& g) override { g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); - g.drawLine(29.5f, 0.0f, 29.5f, getHeight()); - - if (view) { - g.drawLine(getWidth() - 0.5f, 0.0f, getWidth() - 0.5f, getHeight()); + if(view) { + auto hasTabbar = editor->getCurrentCanvas() != nullptr; + auto lineHeight = hasTabbar ? 30.f : 0.0f; + g.drawLine(0, lineHeight, getWidth(), lineHeight); + g.drawLine(getWidth() - 0.5f, 29.5f, getWidth() - 0.5f, getHeight()); + + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId).withAlpha(0.5f)); + g.drawLine(29.5f, 29.5f, 29.5f, getHeight()); + } + else { + g.drawLine(29.5f, 29.5f, 29.5f, getHeight()); } } @@ -876,7 +864,7 @@ class Palettes : public Component Viewport paletteViewport; Component paletteBar; - SmallIconButton addButton = SmallIconButton(Icons::Add); + MainToolbarButton addButton = MainToolbarButton(Icons::Add); OwnedArray paletteSelectors; diff --git a/Source/Sidebar/Sidebar.cpp b/Source/Sidebar/Sidebar.cpp index 59424a473d..33e615dbfc 100644 --- a/Source/Sidebar/Sidebar.cpp +++ b/Source/Sidebar/Sidebar.cpp @@ -107,21 +107,10 @@ Sidebar::~Sidebar() void Sidebar::paint(Graphics& g) { - // Sidebar - g.setColour(findColour(PlugDataColour::sidebarBackgroundColourId)); - g.fillRect(0, 30, getWidth(), getHeight()); - - auto toolbarColour = findColour(PlugDataColour::toolbarBackgroundColourId); - if (ProjectInfo::isStandalone && !editor->isActiveWindow()) { - toolbarColour = toolbarColour.brighter(toolbarColour.getBrightness() / 2.5f); - } - - // Background for buttons - g.setColour(toolbarColour); - g.fillRect(getWidth() - 30, 0, 30, getHeight()); - g.fillRect(0, 0, getWidth() - 30, 30); - if (!sidebarHidden) { + g.setColour(findColour(PlugDataColour::sidebarBackgroundColourId)); + g.fillRect(0, 30, getWidth(), getHeight()); + if (inspector->isVisible()) { Fonts::drawStyledText(g, "Inspector: " + inspector->getTitle(), Rectangle(0, 0, getWidth() - 30, 30), findColour(PlugDataColour::toolbarTextColourId), RegularBoldened, 15, Justification::centred); } else { @@ -133,9 +122,13 @@ void Sidebar::paint(Graphics& g) void Sidebar::paintOverChildren(Graphics& g) { g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); - g.drawLine(getWidth() - 30, 0, getWidth() - 30, getHeight()); - g.drawLine(0.5f, 0, 0.5f, getHeight() + 0.5f); - g.drawLine(0, 30, getWidth() - 30, 30); + g.drawLine(0.5f, 30, 0.5f, getHeight() + 0.5f); + if(!sidebarHidden) { + g.drawLine(0, 30, getWidth(), 30); + + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId).withAlpha(0.5f)); + g.drawLine(getWidth() - 30, 30, getWidth() - 30, getHeight() + 0.5f); + } } void Sidebar::propertyChanged(String const& name, var const& value) @@ -162,7 +155,7 @@ void Sidebar::resized() buttonBarBounds.removeFromTop(8); searchButton.setBounds(buttonBarBounds.removeFromTop(30)); - auto panelTitleBarBounds = bounds.removeFromTop(30); + auto panelTitleBarBounds = bounds.removeFromTop(30).withTrimmedRight(-30); if (extraSettingsButton) extraSettingsButton->setBounds(panelTitleBarBounds.removeFromRight(30)); @@ -290,6 +283,7 @@ void Sidebar::showSidebar(bool show) lastWidth = getWidth(); int newWidth = 30; setBounds(getParentWidth() - newWidth, getY(), newWidth, getHeight()); + if(extraSettingsButton) extraSettingsButton->setVisible(false); } else { int newWidth = lastWidth; setBounds(getParentWidth() - newWidth, getY(), newWidth, getHeight()); @@ -297,6 +291,7 @@ void Sidebar::showSidebar(bool show) if (inspector->isVisible()) { inspector->showParameters(); } + if(extraSettingsButton) extraSettingsButton->setVisible(true); } editor->resized(); diff --git a/Source/Sidebar/Sidebar.h b/Source/Sidebar/Sidebar.h index 3e409660e5..c47928196a 100644 --- a/Source/Sidebar/Sidebar.h +++ b/Source/Sidebar/Sidebar.h @@ -9,6 +9,7 @@ #include "Components/Buttons.h" #include "Objects/ObjectParameters.h" #include "Utility/SettingsFile.h" +#include "Utility/NVGComponent.h" class Console; class Inspector; @@ -78,7 +79,7 @@ class SidebarSelectorButton : public TextButton { class PluginEditor; class Sidebar : public Component - , public SettingsFileListener { + , public SettingsFileListener{ public: explicit Sidebar(PluginProcessor* instance, PluginEditor* parent); diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index b29169c39e..68c8982098 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -17,8 +17,12 @@ #include "Canvas.h" #include "Connection.h" +#include "Sidebar/Sidebar.h" +#include "Sidebar/Palettes.h" + #include "Dialogs/OverlayDisplaySettings.h" #include "Dialogs/SnapSettings.h" +#include "Dialogs/AudioOutputSettings.h" #include "Dialogs/AlignmentTools.h" #include "Components/ArrowPopupMenu.h" @@ -161,117 +165,6 @@ class LatencyDisplayButton : public Component, public MultiTimer, public Settabl JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LatencyDisplayButton); }; -class OversampleSelector : public TextButton { - - class OversampleSettingsPopup : public Component { - public: - std::function onChange = [](int) {}; - std::function onClose = []() {}; - - explicit OversampleSettingsPopup(int currentSelection) - { - title.setText("Oversampling factor", dontSendNotification); - title.setFont(Fonts::getBoldFont().withHeight(14.0f)); - title.setJustificationType(Justification::centred); - addAndMakeVisible(title); - - one.setConnectedEdges(ConnectedOnRight); - two.setConnectedEdges(ConnectedOnLeft | ConnectedOnRight); - four.setConnectedEdges(ConnectedOnLeft | ConnectedOnRight); - eight.setConnectedEdges(ConnectedOnLeft); - - auto buttons = Array { &one, &two, &four, &eight }; - - int i = 0; - for (auto* button : buttons) { - button->setRadioGroupId(hash("oversampling_selector")); - button->setClickingTogglesState(true); - button->onClick = [this, i]() { - onChange(i); - }; - - button->setColour(TextButton::textColourOffId, findColour(PlugDataColour::popupMenuTextColourId)); - button->setColour(TextButton::textColourOnId, findColour(PlugDataColour::popupMenuActiveTextColourId)); - button->setColour(TextButton::buttonColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.04f)); - button->setColour(TextButton::buttonOnColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.075f)); - button->setColour(ComboBox::outlineColourId, Colours::transparentBlack); - - addAndMakeVisible(button); - i++; - } - - buttons[currentSelection]->setToggleState(true, dontSendNotification); - - setSize(180, 50); - } - - ~OversampleSettingsPopup() override - { - onClose(); - } - - private: - void resized() override - { - auto b = getLocalBounds().reduced(4, 4); - auto titleBounds = b.removeFromTop(22); - - title.setBounds(titleBounds.translated(0, -2)); - - auto buttonWidth = b.getWidth() / 4; - - one.setBounds(b.removeFromLeft(buttonWidth)); - two.setBounds(b.removeFromLeft(buttonWidth).expanded(1, 0)); - four.setBounds(b.removeFromLeft(buttonWidth).expanded(1, 0)); - eight.setBounds(b.removeFromLeft(buttonWidth).expanded(1, 0)); - } - - Label title; - TextButton one = TextButton("1x"); - TextButton two = TextButton("2x"); - TextButton four = TextButton("4x"); - TextButton eight = TextButton("8x"); - }; - -public: - explicit OversampleSelector(PluginProcessor* pd) - { - onClick = [this, pd]() { - auto selection = log2(getButtonText().upToLastOccurrenceOf("x", false, false).getIntValue()); - - auto oversampleSettings = std::make_unique(selection); - - oversampleSettings->onChange = [this, pd](int result) { - setButtonText(String(1 << result) + "x"); - pd->setOversampling(result); - }; - oversampleSettings->onClose = [this]() { - repaint(); - }; - auto* editor = findParentComponentOfClass(); - editor->showCalloutBox(std::move(oversampleSettings), getScreenBounds()); - }; - } - -private: - void lookAndFeelChanged() override - { - repaint(); - } - - void paint(Graphics& g) override - { - auto buttonText = getButtonText(); - if (buttonText == "1x") { - g.setColour(isMouseOverOrDragging() ? findColour(PlugDataColour::toolbarTextColourId).brighter(0.8f) : findColour(PlugDataColour::toolbarTextColourId)); - } else { - g.setColour(isMouseOverOrDragging() ? findColour(PlugDataColour::toolbarActiveColourId).brighter(0.8f) : findColour(PlugDataColour::toolbarActiveColourId)); - } - - g.setFont(Fonts::getCurrentFont().withHeight(14.0f)); - g.drawText(buttonText, getLocalBounds(), Justification::centred); - } -}; class VolumeSlider : public Slider { public: @@ -760,18 +653,12 @@ Statusbar::Statusbar(PluginProcessor* processor) cpuMeter = std::make_unique(); midiBlinker = std::make_unique(); volumeSlider = std::make_unique(); - oversampleSelector = std::make_unique(processor); pd->statusbarSource->addListener(levelMeter.get()); pd->statusbarSource->addListener(midiBlinker.get()); pd->statusbarSource->addListener(cpuMeter.get()); pd->statusbarSource->addListener(this); - oversampleSelector->setTooltip("Set oversampling"); - oversampleSelector->setColour(ComboBox::outlineColourId, Colours::transparentBlack); - - oversampleSelector->setButtonText(String(1 << pd->oversampling) + "x"); - addAndMakeVisible(*oversampleSelector); latencyDisplayButton = std::make_unique(); addChildComponent(latencyDisplayButton.get()); @@ -780,23 +667,7 @@ Statusbar::Statusbar(PluginProcessor* processor) }; powerButton.setButtonText(Icons::Power); - protectButton.setButtonText(Icons::Protection); centreButton.setButtonText(Icons::Centre); - fitAllButton.setButtonText(Icons::FitAll); - debugButton.setButtonText(Icons::Debug); - - debugButton.setTooltip("Enable/disable debugging tooltips"); - debugButton.setClickingTogglesState(true); - debugButton.getToggleStateValue().referTo(SettingsFile::getInstance()->getPropertyAsValue("debug_connections")); - debugButton.onClick = [this]() { - set_plugdata_debugging_enabled(debugButton.getToggleState()); - // Recreate the DSP graph with the new optimisations - pd->lockAudioThread(); - canvas_update_dsp(); - pd->unlockAudioThread(); - }; - set_plugdata_debugging_enabled(debugButton.getToggleState()); - addAndMakeVisible(debugButton); powerButton.setTooltip("Enable/disable DSP"); powerButton.setClickingTogglesState(true); @@ -816,16 +687,7 @@ Statusbar::Statusbar(PluginProcessor* processor) addAndMakeVisible(centreButton); - fitAllButton.setTooltip("Zoom to fit all"); - fitAllButton.onClick = [this]() { - auto* editor = findParentComponentOfClass(); - if (auto* cnv = editor->getCurrentCanvas()) { - cnv->zoomToFitAll(); - } - }; - - addAndMakeVisible(fitAllButton); - + /* protectButton.setTooltip("Clip output signal and filter non-finite values"); protectButton.setClickingTogglesState(true); protectButton.setToggleState(SettingsFile::getInstance()->getProperty("protected"), dontSendNotification); @@ -833,8 +695,7 @@ Statusbar::Statusbar(PluginProcessor* processor) int state = protectButton.getToggleState(); pd->setProtectedMode(state); SettingsFile::getInstance()->setProperty("protected", state); - }; - addAndMakeVisible(protectButton); + }; */ volumeSlider->setRange(0.0f, 1.0f); volumeSlider->setValue(0.8f); @@ -855,59 +716,76 @@ Statusbar::Statusbar(PluginProcessor* processor) levelMeter->toBehind(volumeSlider.get()); - overlayButton.setButtonText(Icons::Eye); - overlaySettingsButton.setButtonText(Icons::ThinDown); + zoomComboButton.setButtonText(Icons::ThinDown); - overlaySettingsButton.onClick = [this]() { - auto* editor = findParentComponentOfClass(); - OverlayDisplaySettings::show(editor, overlaySettingsButton.getScreenBounds()); + zoomComboButton.onClick = [this]() { + PopupMenu zoomMenu; + auto zoomOptions = StringArray{"25%", "50%", "75%", "100%", "125%", "150%", "175%", "200%", "250%", "300%"}; + for(auto zoomOption : zoomOptions) + { + auto scale = zoomOption.upToFirstOccurrenceOf("%", false, false).getIntValue() / 100.0f; + zoomMenu.addItem(zoomOption, [this, scale](){ + auto* editor = findParentComponentOfClass(); + if (auto* cnv = editor->getCurrentCanvas()) { + cnv->zoomScale.setValue(scale); + cnv->setTransform(AffineTransform().scaled(scale)); + cnv->viewport->resized(); + } + }); + } + + zoomMenu.addSeparator(); + zoomMenu.addItem("Zoom to fit content", [this](){ + auto* editor = findParentComponentOfClass(); + if (auto* cnv = editor->getCurrentCanvas()) { + cnv->zoomToFitAll(); + } + }); + zoomMenu.showMenuAsync(PopupMenu::Options().withMinimumWidth(150).withMaximumNumColumns(1).withTargetComponent(&zoomComboButton)); }; snapEnableButton.setButtonText(Icons::Magnet); snapSettingsButton.setButtonText(Icons::ThinDown); + audioSettingsButton.setButtonText(Icons::ThinDown); + audioSettingsButton.onClick = [this]() { + auto* editor = findParentComponentOfClass(); + AudioOutputSettings::show(editor, audioSettingsButton.getScreenBounds()); + }; + snapEnableButton.getToggleStateValue().referTo(SettingsFile::getInstance()->getPropertyAsValue("grid_enabled")); - + snapEnableButton.setClickingTogglesState(true); + addAndMakeVisible(snapEnableButton); + snapSettingsButton.onClick = [this]() { auto* editor = findParentComponentOfClass(); SnapSettings::show(editor, snapSettingsButton.getScreenBounds()); }; + addAndMakeVisible(snapSettingsButton); + + addAndMakeVisible(zoomComboButton); - alignmentButton.setButtonText(Icons::AlignLeft); - alignmentButton.onClick = [this]() { - auto* editor = findParentComponentOfClass(); - AlignmentTools::show(editor, alignmentButton.getScreenBounds()); - }; - + overlayButton.getToggleStateValue().referTo(SettingsFile::getInstance()->getValueTree().getChildWithName("Overlays").getPropertyAsValue("alt_mode", nullptr)); + overlayButton.setTooltip(String("Show overlays")); + overlayButton.setButtonText(Icons::Eye); overlayButton.setClickingTogglesState(true); - overlaySettingsButton.setClickingTogglesState(false); - + overlaySettingsButton.setButtonText(Icons::ThinDown); addAndMakeVisible(overlayButton); + + overlaySettingsButton.onClick = [this]() { + auto* editor = findParentComponentOfClass(); + OverlayDisplaySettings::show(editor, overlaySettingsButton.getScreenBounds()); + }; addAndMakeVisible(overlaySettingsButton); - overlayButton.setConnectedEdges(Button::ConnectedOnRight); - overlaySettingsButton.setConnectedEdges(Button::ConnectedOnLeft); - overlayButton.getToggleStateValue().referTo(SettingsFile::getInstance()->getValueTree().getChildWithName("Overlays").getPropertyAsValue("alt_mode", nullptr)); - overlayButton.setTooltip(String("Show overlays")); - overlaySettingsButton.setTooltip(String("Overlay settings")); - - snapEnableButton.setClickingTogglesState(true); - snapSettingsButton.setClickingTogglesState(false); - - addAndMakeVisible(snapEnableButton); - addAndMakeVisible(snapSettingsButton); + zoomComboButton.setTooltip(String("Select zoom")); - snapEnableButton.setConnectedEdges(Button::ConnectedOnRight); - snapSettingsButton.setConnectedEdges(Button::ConnectedOnLeft); - - snapEnableButton.setTooltip(String("Enable snapping")); + addAndMakeVisible(audioSettingsButton); + + audioSettingsButton.setTooltip(String("Audio settings")); snapSettingsButton.setTooltip(String("Snap settings")); - - addAndMakeVisible(alignmentButton); - - alignmentButton.setTooltip(String("Alignment tools")); - + setLatencyDisplay(pd->getLatencySamples() - pd::Instance::getBlockSize()); setSize(getWidth(), statusbarHeight); @@ -921,17 +799,33 @@ Statusbar::~Statusbar() pd->statusbarSource->removeListener(this); } +void Statusbar::updateZoomLevel() +{ + auto* editor = findParentComponentOfClass(); + if(auto* cnv = editor->getCurrentCanvas()) + { + currentZoomLevel = getValue(cnv->zoomScale) * 100; + } + else { + currentZoomLevel = 100.0f; + } + repaint(); +} + void Statusbar::paint(Graphics& g) { + g.setColour(findColour(PlugDataColour::toolbarTextColourId)); + g.drawText(String(int(currentZoomLevel)) + "%", 10, 0, 44, getHeight(), Justification::centredLeft); + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); - g.drawLine(29.f, 0.5f, static_cast(getWidth() - 29.5f), 0.5f); - + + auto* editor = findParentComponentOfClass(); + auto start = !editor->palettes->isExpanded() ? 29.5f : 0.0f; + auto end = editor->sidebar->isHidden() ? 29.5f : 0.0f; + g.drawLine(start, 0.5f, static_cast(getWidth()) - end, 0.5f); + g.drawLine(firstSeparatorPosition, 6.0f, firstSeparatorPosition, getHeight() - 6.0f); g.drawLine(secondSeparatorPosition, 6.0f, secondSeparatorPosition, getHeight() - 6.0f); - g.drawLine(thirdSeparatorPosition, 6.0f, thirdSeparatorPosition, getHeight() - 6.0f); - if (getWidth() > 500) { - g.drawLine(fourthSeparatorPosition, 6.0f, fourthSeparatorPosition, getHeight() - 6.0f); - } } void Statusbar::resized() @@ -943,58 +837,50 @@ void Statusbar::resized() return inverse ? getWidth() - pos : result; }; - auto spacing = getHeight() + 4; + auto spacing = getHeight(); + position(34); // Space for zoom label + zoomComboButton.setBounds(position(8) - 11, 0, getHeight(), getHeight()); + + firstSeparatorPosition = position(4) + 3.5f; // fifth seperator + // Some newer iPhone models have a very large corner radius #if JUCE_IOS position(22); #endif centreButton.setBounds(position(spacing), 0, getHeight(), getHeight()); - fitAllButton.setBounds(position(spacing), 0, getHeight(), getHeight()); - - firstSeparatorPosition = position(7) + 3.5f; // Second seperator - overlayButton.setBounds(position(spacing), 0, getHeight(), getHeight()); - overlaySettingsButton.setBounds(overlayButton.getBounds().translated(getHeight() - 3, 0).withTrimmedRight(8)); - position(10); - - snapEnableButton.setBounds(position(spacing), 0, getHeight(), getHeight()); - snapSettingsButton.setBounds(snapEnableButton.getBounds().translated(getHeight() - 3, 0).withTrimmedRight(8)); - position(10); - - alignmentButton.setBounds(position(spacing), 0, getHeight(), getHeight()); - debugButton.setBounds(position(spacing), 0, getHeight(), getHeight()); + secondSeparatorPosition = position(4) + 1.f; // Second seperator + snapEnableButton.setBounds(position(14), 0, getHeight(), getHeight()); + snapSettingsButton.setBounds(position(spacing - 4), 0, getHeight(), getHeight()); + + overlayButton.setBounds(position(14), 0, getHeight(), getHeight()); + overlaySettingsButton.setBounds(position(spacing - 4), 0, getHeight(), getHeight()); + pos = 4; // reset position for elements on the right #if JUCE_IOS position(22, true); #endif - protectButton.setBounds(position(getHeight(), true), 0, getHeight(), getHeight()); - - powerButton.setBounds(position(getHeight(), true), 0, getHeight(), getHeight()); + audioSettingsButton.setBounds(position(24, true), 0, getHeight(), getHeight()); + powerButton.setBounds(position(spacing, true) + 16, 0, getHeight(), getHeight()); // TODO: combine these both into one - int levelMeterPosition = position(110, true); + int levelMeterPosition = position(95, true); levelMeter->setBounds(levelMeterPosition, 2, 120, getHeight() - 4); volumeSlider->setBounds(levelMeterPosition, 2, 120, getHeight() - 4); - secondSeparatorPosition = position(5, true) + 5.0f; // Third seperator - - // Offset to make text look centred - oversampleSelector->setBounds(position(spacing - 8, true), 1, getHeight() - 2, getHeight() - 2); - - thirdSeparatorPosition = position(5, true) + 2.5f; // Fourth seperator + secondSeparatorPosition = position(2, true) + 4; // Third seperator // Hide these if there isn't enough space midiBlinker->setVisible(getWidth() > 500); cpuMeter->setVisible(getWidth() > 500); - - midiBlinker->setBounds(position(40, true) - 8, 0, 55, getHeight()); - fourthSeparatorPosition = position(10, true); - cpuMeter->setBounds(position(48, true), 0, 50, getHeight()); + + midiBlinker->setBounds(position(55, true) + 10, 0, 55, getHeight()); + cpuMeter->setBounds(position(50, true), 0, 50, getHeight()); latencyDisplayButton->setBounds(position(100, true), 0, 100, getHeight()); } diff --git a/Source/Statusbar.h b/Source/Statusbar.h index fb53802899..b6e774bf5c 100644 --- a/Source/Statusbar.h +++ b/Source/Statusbar.h @@ -19,7 +19,6 @@ class MIDIBlinker; class CPUMeter; class PluginProcessor; class VolumeSlider; -class OversampleSelector; class LatencyDisplayButton; class StatusbarSource : public Timer { @@ -89,6 +88,7 @@ class Statusbar : public Component void audioProcessedChanged(bool audioProcessed) override; void setLatencyDisplay(int value); + void updateZoomLevel(); bool wasLocked = false; // Make sure it doesn't re-lock after unlocking (because cmd is still down) @@ -96,22 +96,18 @@ class Statusbar : public Component std::unique_ptr volumeSlider; std::unique_ptr midiBlinker; std::unique_ptr cpuMeter; - - SmallIconButton powerButton, centreButton, fitAllButton, protectButton; - + + SmallIconButton zoomComboButton, centreButton; SmallIconButton overlayButton, overlaySettingsButton; - SmallIconButton snapEnableButton, snapSettingsButton; - - SmallIconButton alignmentButton, debugButton; - - std::unique_ptr oversampleSelector; + SmallIconButton powerButton, audioSettingsButton; std::unique_ptr latencyDisplayButton; Label zoomLabel; int currentLatency = 64; + float currentZoomLevel = 100.f; Value showDirection; @@ -122,8 +118,6 @@ class Statusbar : public Component int firstSeparatorPosition; int secondSeparatorPosition; - int thirdSeparatorPosition; - int fourthSeparatorPosition; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Statusbar) }; diff --git a/Source/Tabbar/Tabbar.cpp b/Source/Tabbar/Tabbar.cpp index 90b8bccbbd..07c0630fc6 100644 --- a/Source/Tabbar/Tabbar.cpp +++ b/Source/Tabbar/Tabbar.cpp @@ -18,129 +18,193 @@ class WelcomePanel : public Component { - class RecentlyOpenedListBox : public Component - , public SettingsFileListener - , public ListBoxModel { + class WelcomePanelTile : public Component + { + String tileName; + String tileSubtitle; + float snapshotScale; + bool isHovered = false; + std::unique_ptr snapshot = nullptr; public: - RecentlyOpenedListBox() - { - listBox.setRowHeight(26); - listBox.setModel(this); - update(); - - listBox.setColour(ListBox::backgroundColourId, Colours::transparentBlack); - - addAndMakeVisible(listBox); - - // To get a hover effect on viewport items - listBox.setMouseMoveSelectsRows(true); - - bouncer = std::make_unique(listBox.getViewport()); - } - - void settingsFileReloaded() override - { - update(); - } - - void update() + + std::function onClick = [](){}; + + WelcomePanelTile(String name, String subtitle, String svgImage, Colour iconColour, float scale) + : tileName(name) + , tileSubtitle(subtitle) + , snapshotScale(scale) { - items.clear(); - - auto settingsTree = SettingsFile::getInstance()->getValueTree(); - auto recentlyOpenedTree = settingsTree.getChildWithName("RecentlyOpened"); - if (recentlyOpenedTree.isValid()) { - for (int i = 0; i < recentlyOpenedTree.getNumChildren(); i++) { - auto path = File(recentlyOpenedTree.getChild(i).getProperty("Path").toString()); - items.add({ path.getFileName(), path }); - } + snapshot = Drawable::createFromImageData(svgImage.toRawUTF8(), svgImage.getNumBytesAsUTF8()); + if(snapshot) { + snapshot->replaceColour (Colours::black, iconColour); } - - listBox.updateContent(); - } - - std::function onPatchOpen = [](File) {}; - - private: - int getNumRows() override - { - return items.size(); + + resized(); } - - void listBoxItemClicked(int row, MouseEvent const& e) override + + void paint(Graphics& g) override { - onPatchOpen(items[row].second); + auto bounds = getLocalBounds().reduced(12); + + Path tilePath; + tilePath.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), Corners::largeCornerRadius); + + StackShadow::renderDropShadow(g, tilePath, Colour(0, 0, 0).withAlpha(0.1f), 7, {0, 2}); + g.setColour(findColour(PlugDataColour::canvasBackgroundColourId)); + g.fillPath(tilePath); + + if(snapshot) { + snapshot->drawAt(g, 0, 0, 1.0f); + } + + Path textAreaPath; + textAreaPath.addRoundedRectangle(bounds.getX(), bounds.getHeight() - 32, bounds.getWidth(), 44, Corners::largeCornerRadius, Corners::largeCornerRadius, false, false, true, true); + + auto hoverColour = findColour(PlugDataColour::toolbarHoverColourId).interpolatedWith(findColour(PlugDataColour::toolbarBackgroundColourId), 0.5f); + g.setColour(isHovered ? hoverColour : findColour(PlugDataColour::toolbarBackgroundColourId)); + g.fillPath(textAreaPath); + + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); + g.strokePath(tilePath, PathStrokeType(1.0f)); + + Fonts::drawStyledText(g, tileName, bounds.getX() + 10, bounds.getHeight() - 30, bounds.getWidth() - 8, 24, findColour(PlugDataColour::panelTextColourId), Semibold, 14); + + g.setColour(findColour(PlugDataColour::panelTextColourId).withAlpha(0.75f)); + g.setFont(Fonts::getCurrentFont().withHeight(13.5f)); + g.drawText(tileSubtitle, bounds.getX() + 10, bounds.getHeight() - 10, bounds.getWidth() - 8, 16, Justification::centredLeft); } - - void paint(Graphics& g) override + + void mouseEnter(const MouseEvent& e) override { - g.setColour(findColour(PlugDataColour::outlineColourId)); - PlugDataLook::drawSmoothedRectangle(g, PathStrokeType(1.0f), Rectangle(1, 32, getWidth() - 2, getHeight() - 32), Corners::defaultCornerRadius); - - Fonts::drawStyledText(g, "Recently Opened", 0, 0, getWidth(), 30, findColour(PlugDataColour::panelTextColourId), Semibold, 15, Justification::centred); + isHovered = true; + repaint(); } - - void mouseExit(MouseEvent const& e) override + + void mouseExit(const MouseEvent& e) override { + isHovered = false; repaint(); } - - void resized() override + + void mouseUp(const MouseEvent& e) override { - listBox.setBounds(getLocalBounds().withTrimmedTop(35)); + onClick(); } - - void paintListBoxItem(int rowNumber, Graphics& g, int width, int height, bool rowIsSelected) override + + void resized() override { - if (rowIsSelected && isMouseOver(true)) { - g.setColour(findColour(PlugDataColour::panelActiveBackgroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, Rectangle(5.5, 1.5, width - 9, height - 4), Corners::defaultCornerRadius); + if(snapshot) { + auto bounds = getLocalBounds().reduced(12).withTrimmedBottom(44); + snapshot->setTransformToFit(bounds.withSizeKeepingCentre(bounds.getWidth() * snapshotScale, bounds.getHeight() * snapshotScale).toFloat(), RectanglePlacement::centred); } - - auto colour = rowIsSelected ? findColour(PlugDataColour::panelActiveTextColourId) : findColour(PlugDataColour::panelTextColourId); - - Fonts::drawText(g, items[rowNumber].first, height + 4, 0, width - 4, height, colour, 14); - Fonts::drawIcon(g, Icons::File, 12, 0, height, colour, 12, false); } - - std::unique_ptr bouncer; - ListBox listBox; - Array> items; }; - + public: WelcomePanel() - : newButton(Icons::New, "New patch", "Create a new empty patch") - , openButton(Icons::Open, "Open patch...", "Open a saved patch") - { - addAndMakeVisible(newButton); - addAndMakeVisible(openButton); - - // Opening files from recently opened list will likely fail, - // since the file browser is what grants us the permission to read/write files -#if !JUCE_IOS - addAndMakeVisible(recentlyOpened); -#endif + recentlyOpenedViewport.setViewedComponent(&recentlyOpenedComponent); + recentlyOpenedViewport.setScrollBarsShown(true, false, false, false); + recentlyOpenedComponent.setVisible(true); + addAndMakeVisible(recentlyOpenedViewport); + + // Update async to make sure silhouettes are available + MessageManager::callAsync([_this = SafePointer(this)](){ + _this->update(); + }); } void resized() override { - if (getHeight() > 400) { - newButton.setBounds(getLocalBounds().withSizeKeepingCentre(275, 50).translated(0, -70)); - openButton.setBounds(getLocalBounds().withSizeKeepingCentre(275, 50).translated(0, -10)); - recentlyOpened.setBounds(getLocalBounds().withSizeKeepingCentre(275, 170).translated(0, 110)); - recentlyOpened.setVisible(true); - } else { - newButton.setBounds(getLocalBounds().withSizeKeepingCentre(275, 50).translated(0, -20)); - openButton.setBounds(getLocalBounds().withSizeKeepingCentre(275, 50).translated(0, 50)); - recentlyOpened.setVisible(false); + auto bounds = getLocalBounds().reduced(24).withTrimmedTop(36); + auto rowBounds = bounds.removeFromTop(160); + + const int desiredTileWidth = 190; + const int tileSpacing = 4; + + int totalWidth = bounds.getWidth(); + // Calculate the number of columns that can fit in the total width + int numColumns = totalWidth / (desiredTileWidth + tileSpacing); + // Adjust the tile width to fit within the available width + int actualTileWidth = (totalWidth - (numColumns - 1) * tileSpacing) / numColumns; + + if(newPatchTile) newPatchTile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); + rowBounds.removeFromLeft(4); + if(openPatchTile) openPatchTile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); + + bounds.removeFromTop(16); + + recentlyOpenedViewport.setBounds(getLocalBounds().withTrimmedTop(260)); + + int numRows = (tiles.size() + numColumns - 1) / numColumns; + int totalHeight = numRows * 160; + + auto scrollable = Rectangle(24, 0, totalWidth + 24, totalHeight); + recentlyOpenedComponent.setBounds(scrollable); + + // Start positioning the tiles + rowBounds = scrollable.removeFromTop(160); + for (auto* tile : tiles) + { + if (rowBounds.getWidth() < actualTileWidth) { + rowBounds = scrollable.removeFromTop(160); + } + tile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); + rowBounds.removeFromLeft(tileSpacing); } } + + void update() + { + newPatchTile = std::make_unique("New Patch", "Create a new empty patch", newIcon, findColour(PlugDataColour::panelTextColourId), 0.33f); + openPatchTile = std::make_unique("Open Patch", "Browse for a patch to open", openIcon, findColour(PlugDataColour::panelTextColourId), 0.33f); + + newPatchTile->onClick = [this]() { newTab(); }; + openPatchTile->onClick = [this](){ openPatch(); }; + + addAndMakeVisible(*newPatchTile); + addAndMakeVisible(*openPatchTile); + + tiles.clear(); + + auto settingsTree = SettingsFile::getInstance()->getValueTree(); + auto recentlyOpenedTree = settingsTree.getChildWithName("RecentlyOpened"); + + auto snapshotColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.3f); + + if (recentlyOpenedTree.isValid()) { + for (int i = 0; i < recentlyOpenedTree.getNumChildren(); i++) { + auto path = File(recentlyOpenedTree.getChild(i).getProperty("Path").toString()); + auto silhoutteSvg = Autosave::findPatchSilhouette(path); + + if(silhoutteSvg.isEmpty() && path.existsAsFile()) + { + silhoutteSvg = OfflineObjectRenderer::patchToSVGFast(path.loadFileAsString()); + } + + auto openTime = Time(static_cast(recentlyOpenedTree.getChild(i).getProperty("Time"))); + auto diff = Time::getCurrentTime() - openTime; + String date; + if(diff.inDays() == 0) date = "Today"; + else if(diff.inDays() == 1) date = "Yesterday"; + else date = openTime.toString(true, false); + String time = openTime.toString(false, true, false, true); + String timeDescription = date + ", " + time; + + auto* tile = tiles.add(new WelcomePanelTile(path.getFileName(), timeDescription, silhoutteSvg, snapshotColour, 1.0f)); + tile->onClick = [this, path](){ + onPatchOpen(path); + }; + recentlyOpenedComponent.addAndMakeVisible(tile); + } + } + + resized(); + } void show() { - recentlyOpened.update(); + update(); setVisible(true); } @@ -148,21 +212,48 @@ class WelcomePanel : public Component { { setVisible(false); } - + void paint(Graphics& g) override { - auto offset = getHeight() > 400 ? 0 : 50; g.fillAll(findColour(PlugDataColour::panelBackgroundColourId)); - Fonts::drawStyledText(g, "Welcome to plugdata", 0, getHeight() / 2 - 195 + offset, getWidth(), 40, findColour(PlugDataColour::panelTextColourId), Bold, 32, Justification::centred); - - Fonts::drawStyledText(g, "Open a file to begin patching", 0, getHeight() / 2 - 160 + offset, getWidth(), 40, findColour(PlugDataColour::panelTextColourId), Thin, 23, Justification::centred); + Fonts::drawStyledText(g, "Welcome to plugdata", 32, 12, getWidth(), 40, findColour(PlugDataColour::panelTextColourId), Bold, 30, Justification::centredLeft); + //Fonts::drawStyledText(g, "Open a file to begin patching", 32, 42, getWidth(), 40, findColour(PlugDataColour::panelTextColourId), Thin, 23, Justification::centredLeft); + + Fonts::drawStyledText(g, "Recently opened", 32, 228, getWidth(), 32, findColour(PlugDataColour::panelTextColourId), Bold, 24, Justification::centredLeft); } - - WelcomePanelButton newButton; - WelcomePanelButton openButton; - - RecentlyOpenedListBox recentlyOpened; + + void lookAndFeelChanged() override + { + update(); + } + + std::function onPatchOpen = [](File){}; + std::function newTab = [](){}; + std::function openPatch = [](){}; + + static inline const String newIcon = "\n" + "\n" + "\n" + " \n" + "\n"; + + static inline const String openIcon = "\n" + "\n" + "\n" + " \n" + "\n"; + + std::unique_ptr newPatchTile; + std::unique_ptr openPatchTile; + + Component recentlyOpenedComponent; + BouncingViewport recentlyOpenedViewport; + + OwnedArray tiles; }; class ButtonBar::GhostTab : public Component { @@ -439,15 +530,15 @@ TabComponent::TabComponent(PluginEditor* parent) welcomePanel = std::make_unique(); addAndMakeVisible(welcomePanel.get()); - welcomePanel->newButton.onClick = [this]() { + welcomePanel->newTab = [this]() { newTab(); }; - welcomePanel->openButton.onClick = [this]() { + welcomePanel->openPatch = [this]() { openProject(); }; - welcomePanel->recentlyOpened.onPatchOpen = [this](File patchFile) { + welcomePanel->onPatchOpen = [this](File patchFile) { openProjectFile(patchFile); }; diff --git a/Source/Utility/Autosave.h b/Source/Utility/Autosave.h index b2fe6f7947..3085baeac9 100644 --- a/Source/Utility/Autosave.h +++ b/Source/Utility/Autosave.h @@ -12,7 +12,7 @@ class Autosave : public Timer Value autosaveEnabled; PluginProcessor* pd; - moodycamel::ReaderWriterQueue> autoSaveQueue; + moodycamel::ReaderWriterQueue> autoSaveQueue; public: Autosave(PluginProcessor* procesor) @@ -66,6 +66,18 @@ class Autosave : public Timer callback(); } } + + static String findPatchSilhouette(File path) + { + auto entry = autoSaveTree.getChildWithProperty("Path", path.getFullPathName()); + if (entry.isValid() && entry.hasProperty("Snapshot")) { + MemoryOutputStream ostream; + Base64::convertFromBase64(ostream, entry.getProperty("Snapshot").toString()); + return String::fromUTF8(static_cast(ostream.getData()), ostream.getDataSize()); + } + + return {}; + } private: void valueChanged(Value& v) override @@ -104,7 +116,7 @@ class Autosave : public Timer // Simple way to filter out plugdata default patches which we don't want to save. if (!isInternalPatch(patchFile)) { - autoSaveQueue.enqueue({ patchFile.getFullPathName(), patch->getCanvasContent() }); + autoSaveQueue.enqueue({ patchFile.getFullPathName(), patch->getCanvasContent(), x}); } triggerAsyncUpdate(); @@ -121,11 +133,27 @@ class Autosave : public Timer return pathName.contains("Documents/plugdata/Abstractions") || pathName.contains("Documents/plugdata/Documentation") || pathName.contains("Documents/plugdata/Extra") || patch.getParentDirectory() == File::getSpecialLocation(File::tempDirectory); } + void handleAsyncUpdate() override { - std::pair pathAndContent; + std::tuple pathAndContent; while (autoSaveQueue.try_dequeue(pathAndContent)) { - auto& [path, content] = pathAndContent; + auto& [path, content, ptr] = pathAndContent; + + String patchSnapshot; + for(auto* editor : pd->getEditors()) + { + for(auto* canvas : editor->canvases) + { + auto& patch = canvas->patch; + if(patch.getPointer().get() == ptr) + { + + patchSnapshot = Base64::toBase64(canvas->generateSilhouetteSVG()); + break; + } + } + } // Make sure we get current time in the correct format used by the OS for file modification time auto tempFile = File::createTempFile("temp_time_test"); @@ -138,11 +166,13 @@ class Autosave : public Timer if (existingPatch.isValid()) { existingPatch.setProperty("Patch", Base64::toBase64(content), nullptr); existingPatch.setProperty("LastModified", (int64)time, nullptr); + existingPatch.setProperty("Snapshot", patchSnapshot, nullptr); } else { ValueTree newAutoSave = ValueTree("Save"); newAutoSave.setProperty("Path", path, nullptr); newAutoSave.setProperty("Patch", Base64::toBase64(content), nullptr); newAutoSave.setProperty("LastModified", (int64)time, nullptr); + newAutoSave.setProperty("Snapshot", patchSnapshot, nullptr); autoSaveTree.addChild(newAutoSave, 0, nullptr); if (autoSaveTree.getNumChildren() > 15) { diff --git a/Source/Utility/OfflineObjectRenderer.cpp b/Source/Utility/OfflineObjectRenderer.cpp index 884a1de4cd..6b4fafa7eb 100644 --- a/Source/Utility/OfflineObjectRenderer.cpp +++ b/Source/Utility/OfflineObjectRenderer.cpp @@ -70,6 +70,173 @@ ImageWithOffset OfflineObjectRenderer::patchToMaskedImage(String const& patch, f return ImageWithOffset(output, image.offset); } +String OfflineObjectRenderer::patchToSVGFast(String const& patch) +{ + int canvasDepth = -1; + + auto isObject = [](StringArray& tokens) { + return tokens[0] == "#X" && tokens[1] != "connect" && tokens[1] != "f" && tokens[2].containsOnly("-0123456789") && tokens[3].containsOnly("-0123456789"); + }; + + auto isStartingCanvas = [](StringArray& tokens) { + return tokens[0] == "#N" && tokens[1] == "canvas" && tokens[2].containsOnly("-0123456789") && tokens[3].containsOnly("-0123456789") && tokens[4].containsOnly("-0123456789") && tokens[5].containsOnly("-0123456789"); + }; + + auto isEndingCanvas = [](StringArray& tokens) { + return tokens[0] == "#X" && tokens[1] == "restore" && tokens[2].containsOnly("-0123456789") && tokens[3].containsOnly("-0123456789"); + }; + + auto isGraphCoords = [](StringArray& tokens) { + return tokens[0] == "#X" && tokens[1] == "coords" && tokens[5].containsOnly("-0123456789") && tokens[6].containsOnly("-0123456789"); + }; + + StringArray objects; + Rectangle nextGraphCoords; + String canvasName; + bool hasGraphCoords = false; + for (auto& line : StringArray::fromLines(patch)) { + + line = line.upToLastOccurrenceOf(";", false, false); + + auto tokens = StringArray::fromTokens(line, true); + + if (isStartingCanvas(tokens)) { + if(tokens.size() > 6) canvasName = tokens[6]; + canvasDepth++; + } + + if (canvasDepth == 0 && isObject(tokens)) { + objects.add(line); + } + + if(isGraphCoords(tokens) && tokens.size() > 6) + { + nextGraphCoords = Rectangle(tokens[5].getIntValue(), tokens[6].getIntValue()); + hasGraphCoords = true; + } + + if (isEndingCanvas(tokens)) { + if (canvasDepth == 1) { + if(hasGraphCoords) + { + objects.add(line + " " + String(nextGraphCoords.getWidth()) + " " + String(nextGraphCoords.getHeight())); + hasGraphCoords = false; + } + else { + objects.add(line + " " + String(canvasName.length() * 12) + " 24"); + } + } + canvasDepth--; + } + } + + Array> objectBounds; + for(auto& object : objects) + { + auto tokens = StringArray::fromTokens(object, true); + if((tokens[1] == "floatatom" || tokens[1] == "symbolatom" || tokens[1] == "listatom") && tokens.size() > 11) + { + objectBounds.add(Rectangle(tokens[2].getIntValue(), tokens[3].getIntValue(), tokens[4].getIntValue() * 8, tokens[11].getIntValue())); + continue; + } + switch(hash(tokens[4])){ + case hash("restore"): + { + if(tokens.size() < 6) break; + objectBounds.add(Rectangle(tokens[2].getIntValue(), tokens[3].getIntValue(), tokens[4].getIntValue(), tokens[5].getIntValue())); + break; + } + case hash("bng"): + case hash("tgl"): { + if(tokens.size() < 6) break; + objectBounds.add(Rectangle(tokens[2].getIntValue(), tokens[3].getIntValue(), tokens[5].getIntValue(), tokens[5].getIntValue())); + break; + } + case hash("vradio"): + { + if(tokens.size() < 7) break; + objectBounds.add(Rectangle(tokens[2].getIntValue(), tokens[3].getIntValue(), tokens[5].getIntValue(), tokens[5].getIntValue() * tokens[6].getIntValue())); + break; + } + case hash("hradio"): + { + if(tokens.size() < 7) break; + objectBounds.add(Rectangle(tokens[2].getIntValue(), tokens[3].getIntValue(), tokens[5].getIntValue() * tokens[6].getIntValue(), tokens[5].getIntValue())); + break; + } + case hash("cnv"): { + if(tokens.size() < 8) break; + objectBounds.add(Rectangle(tokens[2].getIntValue(), tokens[3].getIntValue(), tokens[6].getIntValue(), tokens[7].getIntValue())); + break; + } + case hash("vu"): + case hash("hsl"): + case hash("vsl"): + case hash("slider"): + { + if(tokens.size() < 7) break; + objectBounds.add(Rectangle(tokens[2].getIntValue(), tokens[3].getIntValue(), tokens[5].getIntValue(), tokens[6].getIntValue())); + break; + } + case hash("nbx"): { + if(tokens.size() < 7) break; + objectBounds.add(Rectangle(tokens[2].getIntValue(), tokens[3].getIntValue(), tokens[5].getIntValue() * 8, tokens[6].getIntValue())); + break; + } + /* // TODO: implement all of these! + case hash("numbox~"): + { + break; + } + case hash("pic"): + case hash("keyboard"): + case hash("scope~"): + case hash("function"): + case hash("knob"): + case hash("gatom"): + case hash("button"): + case hash("bicoeff"): + case hash("messbox"): + case hash("pad"): + { + + break; + } + case hash("note"): + { + + break; + } + */ + default: + { + if(tokens.size() < 4) break; + auto x = tokens[2].getIntValue(); + auto y = tokens[3].getIntValue(); + tokens.removeRange(0, 4); + objectBounds.add(Rectangle(x, y, tokens.joinIntoString(" ").length() * 8, 24)); + break; + } + } + } + + String svgContent; + auto regionOfInterest = Rectangle(); + for (auto& b : objectBounds) { + regionOfInterest = regionOfInterest.getUnion(b.reduced(Object::margin)); + } + + for (auto& b : objectBounds) + { + auto rect = b - regionOfInterest.getPosition(); + svgContent += String::formatted( + "\n", + rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight(), Corners::objectCornerRadius, Corners::objectCornerRadius); + } + + return "\n" + svgContent + ""; +} + ImageWithOffset OfflineObjectRenderer::patchToTempImage(String const& patch, float scale) { static std::unordered_map patchImageCache; diff --git a/Source/Utility/OfflineObjectRenderer.h b/Source/Utility/OfflineObjectRenderer.h index 92c4f8e6c3..4f7eef4043 100644 --- a/Source/Utility/OfflineObjectRenderer.h +++ b/Source/Utility/OfflineObjectRenderer.h @@ -28,6 +28,8 @@ class OfflineObjectRenderer { static OfflineObjectRenderer* findParentOfflineObjectRendererFor(Component* childComponent); + static String patchToSVGFast(String const& patch); + ImageWithOffset patchToMaskedImage(String const& patch, float scale, bool makeInvalidImage = false); bool checkIfPatchIsValid(String const& patch); From 1c7aa719f0d6c839453dff69e72ca3773fd6713c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 26 May 2024 21:02:56 +0200 Subject: [PATCH 0794/1030] Don't deselect stuff on right-click --- Source/Object.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index 5cf97d170f..46d9025041 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -756,7 +756,7 @@ void Object::mouseDown(MouseEvent const& e) if (e.mods.isRightButtonDown() && !cnv->isGraph) { PopupMenu::dismissAllActiveMenus(); if (!getValue(locked)) { - if(!e.mods.isAnyModifierKeyDown()) cnv->deselectAll(); + if(!e.mods.isAnyModifierKeyDown() && !e.mods.isRightButtonDown()) cnv->deselectAll(); cnv->setSelected(this, true); } Dialogs::showCanvasRightClickMenu(cnv, this, e.getScreenPosition()); From 8571d31c64a9f0897ab4a892d10222fdb9969946 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 26 May 2024 21:12:31 +0200 Subject: [PATCH 0795/1030] Small statusbar line fix --- Source/Statusbar.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 68c8982098..192a925365 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -873,8 +873,6 @@ void Statusbar::resized() levelMeter->setBounds(levelMeterPosition, 2, 120, getHeight() - 4); volumeSlider->setBounds(levelMeterPosition, 2, 120, getHeight() - 4); - secondSeparatorPosition = position(2, true) + 4; // Third seperator - // Hide these if there isn't enough space midiBlinker->setVisible(getWidth() > 500); cpuMeter->setVisible(getWidth() > 500); From abd29931323f5a6fac5aba625e96993662b3349f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 28 May 2024 13:31:37 +0200 Subject: [PATCH 0796/1030] Make sure nanovg context always exists by drawing welcome panel with nanovg, added fade to recently opened viewport --- Resources/Fonts/InterThin.ttf | Bin 408364 -> 403404 bytes Source/Canvas.cpp | 16 -- Source/Canvas.h | 5 - Source/Connection.cpp | 7 - Source/Connection.h | 4 +- Source/NVGSurface.cpp | 257 ++++++++++---------- Source/NVGSurface.h | 19 +- Source/Object.cpp | 13 - Source/Object.h | 3 - Source/Objects/ArrayObject.h | 2 +- Source/Objects/AtomHelper.h | 2 +- Source/Objects/CommentObject.h | 1 - Source/Objects/GraphOnParent.h | 1 - Source/Objects/IEMHelper.h | 2 +- Source/Objects/LuaObject.h | 11 +- Source/Objects/MessageObject.h | 1 - Source/Objects/ObjectBase.cpp | 1 - Source/Objects/ObjectBase.h | 27 +-- Source/Objects/PictureObject.h | 10 +- Source/Objects/ScalarObject.h | 1 - Source/Objects/TextObject.h | 1 - Source/PluginEditor.cpp | 21 +- Source/PluginEditor.h | 6 +- Source/Standalone/PlugDataWindow.h | 15 +- Source/Tabbar/Tabbar.cpp | 281 +--------------------- Source/Tabbar/Tabbar.h | 6 +- Source/Tabbar/WelcomePanel.h | 289 +++++++++++++++++++++++ Source/Utility/CachedTextRender.h | 23 +- Source/Utility/NVGComponent.h | 15 +- Source/Utility/NanoVGGraphicsContext.cpp | 4 +- Source/Utility/OSUtils.h | 2 +- 31 files changed, 463 insertions(+), 583 deletions(-) create mode 100644 Source/Tabbar/WelcomePanel.h diff --git a/Resources/Fonts/InterThin.ttf b/Resources/Fonts/InterThin.ttf index 31ac7883c4f16c6c9a9261b5668ad26be193e95e..af78ac31a79bdab962dcdd41039805e6936a8a24 100644 GIT binary patch literal 403404 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�~co#jGE#UbX?U#hyB-)XG5d;x% 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℞#RA)|&j2RPM&GXhE&7(gUwi!L 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 ITDjc+0%Zw%yZ`_I diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index be59ef95b1..c53a73796b 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -165,8 +165,6 @@ Canvas::Canvas(PluginEditor* parent, pd::Patch::Ptr p, Component* parentGraph) parameters.addParamRange("Y range", cGeneral, &yRange, { 1.0f, 0.0f }); parameters.addParamInt("Width", cDimensions, &patchWidth, 527); parameters.addParamInt("Height", cDimensions, &patchHeight, 327); - - editor->nvgSurface.addNVGContextListener(this); } Canvas::~Canvas() @@ -174,21 +172,7 @@ Canvas::~Canvas() zoomScale.removeListener(this); editor->removeModifierKeyListener(this); pd->unregisterMessageListener(patch.getPointer().get(), this); - editor->nvgSurface.removeNVGContextListener(this); - if(ioletBuffer) nvgDeleteFramebuffer(ioletBuffer); -} - -void Canvas::lookAndFeelChanged() -{ - editor->nvgSurface.sendContextDeleteMessage(); -} - -void Canvas::nvgContextDeleted(NVGcontext* nvg) -{ if(ioletBuffer) nvgDeleteFramebuffer(ioletBuffer); - if(resizeHandleImage) nvgDeleteImage(nvg, resizeHandleImage); - resizeHandleImage = 0; - ioletBuffer = nullptr; } bool Canvas::updateFramebuffers(NVGcontext* nvg, Rectangle invalidRegion, int maxUpdateTimeMs) diff --git a/Source/Canvas.h b/Source/Canvas.h index 2024a8c4bf..263e6de36b 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -60,7 +60,6 @@ class Canvas : public Component , public pd::MessageListener , public AsyncUpdater , public NVGComponent - , public NVGContextListener { public: Canvas(PluginEditor* parent, pd::Patch::Ptr patch, Component* parentGraph = nullptr); @@ -101,8 +100,6 @@ class Canvas : public Component void performSynchronise(); void handleAsyncUpdate() override; - void lookAndFeelChanged() override; - void updateDrawables(); bool keyPressed(KeyPress const& key) override; @@ -157,8 +154,6 @@ class Canvas : public Component void orderConnections(); - void nvgContextDeleted(NVGcontext* nvg) override; - void showSuggestions(Object* object, TextEditor* textEditor); void hideSuggestions(); diff --git a/Source/Connection.cpp b/Source/Connection.cpp index 509eb08bcc..91d78e96ee 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -33,7 +33,6 @@ Connection::Connection(Canvas* parent, Iolet* s, Iolet* e, t_outconnect* oc) , ptr(parent->pd) { cnv->selectedComponents.addChangeListener(this); - cnv->editor->nvgSurface.addNVGContextListener(this); locked.referTo(parent->locked); presentationMode.referTo(parent->presentationMode); @@ -102,7 +101,6 @@ Connection::~Connection() { cnv->pd->unregisterMessageListener(ptr.getRawUnchecked(), this); cnv->selectedComponents.removeChangeListener(this); - cnv->editor->nvgSurface.removeNVGContextListener(this); if (outlet) { outlet->repaint(); @@ -133,11 +131,6 @@ void Connection::changeListenerCallback(ChangeBroadcaster* source) setSelected(selectedItems->isSelected(this)); } -void Connection::nvgContextDeleted(NVGcontext* nvg) -{ - cacheId = -1; -} - void Connection::lookAndFeelChanged() { baseColour = convertColour(findColour(PlugDataColour::connectionColourId)); diff --git a/Source/Connection.h b/Source/Connection.h index 9db6b9540b..e05207a492 100644 --- a/Source/Connection.h +++ b/Source/Connection.h @@ -28,7 +28,6 @@ class Connection : public DrawablePath , public ChangeListener , public pd::MessageListener , public NVGComponent - , public NVGContextListener , public MultiTimer { public: @@ -73,8 +72,7 @@ class Connection : public DrawablePath void render(NVGcontext* nvg) override; void renderConnectionOrder(NVGcontext* nvg); - void nvgContextDeleted(NVGcontext* nvg) override; - + void updatePath(); void forceUpdate(); diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 9b70821743..5115bfe3a7 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -19,6 +19,7 @@ using namespace juce::gl; #include "PluginEditor.h" #include "PluginMode.h" #include "Canvas.h" +#include "Tabbar/WelcomePanel.h" #include "Sidebar/Sidebar.h" // meh... #define ENABLE_FPS_COUNT 0 @@ -71,7 +72,7 @@ class FrameTimer } return avg / (float)32; } - + float frame_times[32] = {}; int perf_head = 0; double startTime = 0, prevTime = 0; @@ -93,13 +94,16 @@ NVGSurface::NVGSurface(PluginEditor* e) : editor(e) setInterceptsMouseClicks(false, false); setWantsKeyboardFocus(false); - + setSize(1,1); // Start rendering asynchronously, so we are sure the window has been added to the desktop // kind of a hack, but works well enough MessageManager::callAsync([_this = SafePointer(this)](){ if(_this) { + _this->initialise(); + _this->updateBufferSize(); + // Render on vblank _this->vBlankAttachment = std::make_unique(_this.getComponent(), [_this](){ if(_this) { @@ -116,6 +120,58 @@ NVGSurface::~NVGSurface() detachContext(); } +void NVGSurface::initialise() +{ +#ifdef NANOVG_METAL_IMPLEMENTATION + auto renderScale = getRenderScale(); + auto* peer = getPeer()->getNativeHandle(); + auto* view = OSUtils::MTLCreateView(peer, 0, 0, getWidth(), getHeight()); + setView(view); + nvg = nvgCreateContext(view, NVG_ANTIALIAS | NVG_TRIPLE_BUFFER, getWidth() * renderScale, getHeight() * renderScale); + setVisible(true); +#if JUCE_IOS + resized(); +#endif +#else + setVisible(true); + glContext->attachTo(*this); + glContext->initialiseOnThread(); + glContext->makeActive(); + nvg = nvgCreateContext(NVG_ANTIALIAS); +#endif + + invalidateAll(); + + if (!nvg) std::cerr << "could not initialise nvg" << std::endl; + nvgCreateFontMem(nvg, "Inter", (unsigned char*)BinaryData::InterRegular_ttf, BinaryData::InterRegular_ttfSize, 0); + nvgCreateFontMem(nvg, "Inter-Regular", (unsigned char*)BinaryData::InterRegular_ttf, BinaryData::InterRegular_ttfSize, 0); + nvgCreateFontMem(nvg, "Inter-Bold", (unsigned char*)BinaryData::InterBold_ttf, BinaryData::InterBold_ttfSize, 0); + nvgCreateFontMem(nvg, "Inter-SemiBold", (unsigned char*)BinaryData::InterSemiBold_ttf, BinaryData::InterSemiBold_ttfSize, 0); + nvgCreateFontMem(nvg, "Inter-Tabular", (unsigned char*)BinaryData::InterTabular_ttf, BinaryData::InterTabular_ttfSize, 0); + nvgCreateFontMem(nvg, "Icon", (unsigned char*)BinaryData::IconFont_ttf, BinaryData::IconFont_ttfSize, 0); +} + +void NVGSurface::updateBufferSize() +{ + float pixelScale = getRenderScale(); + int scaledWidth = getWidth() * pixelScale; + int scaledHeight = getHeight() * pixelScale; + + if(fbWidth != scaledWidth || fbHeight != scaledHeight || !mainFBO) { + if(invalidFBO) nvgDeleteFramebuffer(invalidFBO); + if(mainFBO) nvgDeleteFramebuffer(mainFBO); + mainFBO = nvgCreateFramebuffer(nvg, scaledWidth, scaledHeight, NVG_IMAGE_PREMULTIPLIED); + invalidFBO = nvgCreateFramebuffer(nvg, scaledWidth, scaledHeight, NVG_IMAGE_PREMULTIPLIED); + fbWidth = scaledWidth; + fbHeight = scaledHeight; + invalidArea = getLocalBounds(); + lastScaleFactor = pixelScale; + } + + //scaleChanged = !approximatelyEqual(lastScaleFactor, pixelScale); +} + + #ifdef NANOVG_GL_IMPLEMENTATION void NVGSurface::timerCallback() { @@ -159,7 +215,8 @@ void NVGSurface::detachContext() void NVGSurface::propertyChanged(String const& name, var const& value) { if(name == "global_scale") { - sendContextDeleteMessage(); + // TODO: handle this? + //sendContextDeleteMessage(); } } @@ -183,7 +240,7 @@ void NVGSurface::updateBounds(Rectangle bounds) setBounds(bounds.withHeight(getHeight())); else setBounds(bounds.withWidth(getWidth())); - + resizing = true; #else setBounds(bounds); @@ -232,6 +289,7 @@ void NVGSurface::renderArea(Rectangle area) editor->pluginMode->render(nvg); } else { + bool hasCanvas = false; for(auto* split : editor->splitView.splits) { if(auto* cnv = split->getTabComponent()->getCurrentCanvas()) @@ -241,159 +299,92 @@ void NVGSurface::renderArea(Rectangle area) nvgScissor(nvg, 0, 0, split->getWidth(), split->getHeight()); cnv->performRender(nvg, area.translated(-split->getX(), 0)); nvgRestore(nvg); + hasCanvas = true; } } + if(!hasCanvas) + { + nvgSave(nvg); + editor->welcomePanel->render(nvg); + nvgRestore(nvg); + } } } -void NVGSurface::sendContextDeleteMessage() -{ - if (nvg == nullptr) - return; - - for(auto listener : nvgContextListeners) - { - if(listener) listener->nvgContextDeleted(nvg); - } -} - -void NVGSurface::addNVGContextListener(NVGContextListener* listener) -{ - nvgContextListeners.add(listener); -} - -void NVGSurface::removeNVGContextListener(NVGContextListener* listener) -{ - nvgContextListeners.removeAllInstancesOf(listener); -} - void NVGSurface::render() { auto startTime = Time::getMillisecondCounter(); - bool hasCanvas = editor->pluginMode != nullptr; + + if(!isAttached() && isVisible()) initialise(); + updateBufferSize(); + + auto pixelScale = getRenderScale(); + + bool hasCanvas = false; for(auto* split : editor->splitView.splits) { - if(split->getTabComponent()->getCurrentCanvas()) + if(auto* cnv = split->getTabComponent()->getCurrentCanvas()) { hasCanvas = true; - break; } } + + // Manage showing/hiding welcome panel + if(hasCanvas && editor->welcomePanel->isVisible()) { + editor->welcomePanel->setVisible(false); + editor->resized(); + updateBufferSize(); + } + else if(!hasCanvas && !editor->welcomePanel->isVisible()) { + editor->welcomePanel->setVisible(true); + editor->resized(); + updateBufferSize(); + } + + if(!invalidArea.isEmpty() && makeContextActive()) { + auto invalidated = invalidArea.expanded(1); - bool scaleChanged = false; - if(makeContextActive()) { - float pixelScale = getRenderScale(); - int scaledWidth = getWidth() * pixelScale; - int scaledHeight = getHeight() * pixelScale; + // First, draw only the invalidated region to a separate framebuffer + // I've found that nvgScissor doesn't always clip everything, meaning that there will be graphical glitches if we don't do this + nvgBindFramebuffer(invalidFBO); + nvgViewport(0, 0, getWidth() * pixelScale, getHeight() * pixelScale); + nvgClear(nvg); - if(fbWidth != scaledWidth || fbHeight != scaledHeight || !mainFBO) { - if(invalidFBO) nvgDeleteFramebuffer(invalidFBO); - if(mainFBO) nvgDeleteFramebuffer(mainFBO); - mainFBO = nvgCreateFramebuffer(nvg, scaledWidth, scaledHeight, NVG_IMAGE_PREMULTIPLIED); - invalidFBO = nvgCreateFramebuffer(nvg, scaledWidth, scaledHeight, NVG_IMAGE_PREMULTIPLIED); - fbWidth = scaledWidth; - fbHeight = scaledHeight; - invalidArea = getLocalBounds(); - scaleChanged = !approximatelyEqual(lastScaleFactor, pixelScale); - lastScaleFactor = pixelScale; - } - else if(!invalidArea.isEmpty()) { - auto invalidated = invalidArea.expanded(1); - - // First, draw only the invalidated region to a separate framebuffer - // I've found that nvgScissor doesn't always clip everything, meaning that there will be graphical glitches if we don't do this - nvgBindFramebuffer(invalidFBO); - nvgViewport(0, 0, getWidth() * pixelScale, getHeight() * pixelScale); - nvgClear(nvg); - - nvgBeginFrame(nvg, getWidth(), getHeight(), pixelScale); - nvgScissor (nvg, invalidated.getX(), invalidated.getY(), invalidated.getWidth(), invalidated.getHeight()); - nvgGlobalCompositeOperation(nvg, NVG_SOURCE_OVER); - - renderArea(invalidated); - nvgEndFrame(nvg); - - nvgBindFramebuffer(mainFBO); - nvgViewport(0, 0, getWidth() * pixelScale, getHeight() * pixelScale); - nvgBeginFrame(nvg, getWidth(), getHeight(), pixelScale); - nvgBeginPath(nvg); - nvgRect(nvg, invalidated.getX(), invalidated.getY(), invalidated.getWidth(), invalidated.getHeight()); - nvgScissor(nvg, invalidated.getX(), invalidated.getY(), invalidated.getWidth(), invalidated.getHeight()); - nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, getWidth(), getHeight(), 0, invalidFBO->image, 1)); - nvgFill(nvg); + nvgBeginFrame(nvg, getWidth(), getHeight(), pixelScale); + nvgScissor (nvg, invalidated.getX(), invalidated.getY(), invalidated.getWidth(), invalidated.getHeight()); + + renderArea(invalidated); + nvgEndFrame(nvg); + + nvgBindFramebuffer(mainFBO); + nvgViewport(0, 0, getWidth() * pixelScale, getHeight() * pixelScale); + nvgBeginFrame(nvg, getWidth(), getHeight(), pixelScale); + nvgBeginPath(nvg); + nvgRect(nvg, invalidated.getX(), invalidated.getY(), invalidated.getWidth(), invalidated.getHeight()); + nvgScissor(nvg, invalidated.getX(), invalidated.getY(), invalidated.getWidth(), invalidated.getHeight()); + nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, getWidth(), getHeight(), 0, invalidFBO->image, 1)); + nvgFill(nvg); #if ENABLE_FB_DEBUGGING - static Random rng; - nvgBeginPath(nvg); - nvgFillColor(nvg, nvgRGBA(rng.nextInt(255), rng.nextInt(255), rng.nextInt(255), 0x50)); - nvgRect(nvg, 0, 0, getWidth(), getHeight()); - nvgFill(nvg); + static Random rng; + nvgBeginPath(nvg); + nvgFillColor(nvg, nvgRGBA(rng.nextInt(255), rng.nextInt(255), rng.nextInt(255), 0x50)); + nvgRect(nvg, 0, 0, getWidth(), getHeight()); + nvgFill(nvg); #endif - nvgEndFrame(nvg); - - nvgBindFramebuffer(nullptr); - needsBufferSwap = true; - invalidArea = Rectangle(0, 0, 0, 0); - } + nvgEndFrame(nvg); + + nvgBindFramebuffer(nullptr); + needsBufferSwap = true; + invalidArea = Rectangle(0, 0, 0, 0); #if ENABLE_FPS_COUNT frameTimer->addFrameTime(); #endif } - if(hasCanvas && (!isAttached() || scaleChanged)) - { - setVisible(true); - setInterceptsMouseClicks(false, false); - setWantsKeyboardFocus(false); - - sendContextDeleteMessage(); - if(invalidFBO) nvgDeleteFramebuffer(invalidFBO); - if(mainFBO) nvgDeleteFramebuffer(mainFBO); - if(nvg) nvgDeleteContext(nvg); - invalidFBO = nullptr; - mainFBO = nullptr; - nvg = nullptr; - -#ifdef NANOVG_METAL_IMPLEMENTATION - auto renderScale = getRenderScale(); - auto* peer = getPeer()->getNativeHandle(); - auto* view = OSUtils::MTLCreateView(peer, 0, 0, getWidth(), getHeight()); - setView(view); - nvg = nvgCreateContext(view, NVG_ANTIALIAS | NVG_TRIPLE_BUFFER, getWidth() * renderScale, getHeight() * renderScale); - setVisible(true); -#if JUCE_IOS - resized(); -#endif -#else - glContext->attachTo(*this); - glContext->initialiseOnThread(); - nvg = nvgCreateContext(NVG_ANTIALIAS); -#endif - - invalidateAll(); - - if (!nvg) std::cerr << "could not initialise nvg" << std::endl; - nvgCreateFontMem(nvg, "Inter", (unsigned char*)BinaryData::InterRegular_ttf, BinaryData::InterRegular_ttfSize, 0); - nvgCreateFontMem(nvg, "Inter-Regular", (unsigned char*)BinaryData::InterRegular_ttf, BinaryData::InterRegular_ttfSize, 0); - nvgCreateFontMem(nvg, "Inter-Tabular", (unsigned char*)BinaryData::InterTabular_ttf, BinaryData::InterTabular_ttfSize, 0); - nvgCreateFontMem(nvg, "Icon", (unsigned char*)BinaryData::IconFont_ttf, BinaryData::IconFont_ttfSize, 0); - } - else if(!hasCanvas && isAttached()) - { - sendContextDeleteMessage(); - - if(invalidFBO) nvgDeleteFramebuffer(invalidFBO); - if(mainFBO) nvgDeleteFramebuffer(mainFBO); - invalidFBO = nullptr; - mainFBO = nullptr; - - if(nvg) nvgDeleteContext(nvg); - nvg = nullptr; - detachContext(); - } - else if(needsBufferSwap && isAttached() && mainFBO) { + if(needsBufferSwap && makeContextActive()) { float pixelScale = getRenderScale(); nvgViewport(0, 0, getWidth() * pixelScale, getHeight() * pixelScale); @@ -418,7 +409,7 @@ void NVGSurface::render() #endif nvgEndFrame(nvg); - + #ifdef NANOVG_GL_IMPLEMENTATION glContext->swapBuffers(); if (resizing) { @@ -433,7 +424,7 @@ void NVGSurface::render() auto elapsed = Time::getMillisecondCounter() - startTime; // We update frambuffers after we call swapBuffers to make sure the frame is on time - if(isAttached() && elapsed < 14) { + if(elapsed < 14) { for(auto* split : editor->splitView.splits) { if(auto* cnv = split->getTabComponent()->getCurrentCanvas()) diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index d1a361efb9..84bde43d32 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -21,13 +21,6 @@ using namespace juce::gl; #define NANOVG_GL_IMPLEMENTATION 1 #endif -class NVGContextListener -{ -public: - virtual void nvgContextDeleted(NVGcontext* nvg) {}; - - JUCE_DECLARE_WEAK_REFERENCEABLE(NVGContextListener); -}; class FrameTimer; class PluginEditor; @@ -44,6 +37,10 @@ public Component, public Timer public: NVGSurface(PluginEditor* editor); ~NVGSurface(); + + void initialise(); + void updateBufferSize(); + void render(); void triggerRepaint(); @@ -56,10 +53,6 @@ public Component, public Timer #endif void propertyChanged(String const& name, var const& value) override; - - void sendContextDeleteMessage(); - void addNVGContextListener(NVGContextListener* listener); - void removeNVGContextListener(NVGContextListener* listener); Rectangle getInvalidArea() { return invalidArea; } @@ -133,10 +126,6 @@ public Component, public Timer bool resizing = false; Rectangle newBounds; - int framesToSkip = 0; - - Array> nvgContextListeners; - #if NANOVG_GL_IMPLEMENTATION std::unique_ptr glContext; #endif diff --git a/Source/Object.cpp b/Source/Object.cpp index 46d9025041..d6bc30966f 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -34,7 +34,6 @@ Object::Object(Canvas* parent, String const& name, Point position) : NVGComponent(this) , cnv(parent) , gui(nullptr) - , textEditorRenderer(parent->editor->nvgSurface) , ds(parent->dragState) { setTopLeftPosition(position - Point(margin, margin)); @@ -57,7 +56,6 @@ Object::Object(Canvas* parent, String const& name, Point position) Object::Object(pd::WeakReference object, Canvas* parent) : NVGComponent(this) , gui(nullptr) - , textEditorRenderer(parent->editor->nvgSurface) , ds(parent->dragState) { cnv = parent; @@ -73,7 +71,6 @@ Object::~Object() hideEditor(); // Make sure the editor is not still open, that could lead to issues with listeners attached to the editor (i.e. suggestioncomponent) cnv->selectedComponents.removeChangeListener(this); - cnv->editor->nvgSurface.removeNVGContextListener(this); } Rectangle Object::getObjectBounds() @@ -148,16 +145,6 @@ void Object::initialise() setAccessible(false); // TODO: implement accessibility. We disable default, since it makes stuff slow on macOS setCachedComponentImage(new InvalidationListener(this)); - cnv->editor->nvgSurface.addNVGContextListener(this); -} - -void Object::nvgContextDeleted(NVGcontext* nvg) -{ - if(fb) nvgDeleteFramebuffer(fb); - fb = nullptr; - if(activityOverlayImage) nvgDeleteImage(nvg, activityOverlayImage); - activityOverlayImage = 0; - fbDirty = true; } void Object::timerCallback() diff --git a/Source/Object.h b/Source/Object.h index 61d08f1718..366966185c 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -37,7 +37,6 @@ class Object : public Component , public Timer , public KeyListener , public NVGComponent - , public NVGContextListener , private TextEditor::Listener { public: @@ -66,8 +65,6 @@ class Object : public Component void hideEditor(); bool isInitialEditorShown(); - void nvgContextDeleted(NVGcontext* nvg) override; - String getType(bool withOriginPrefix = true) const; Rectangle getSelectableBounds(); diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index 3764b5d1ee..98c2d684f6 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -1273,7 +1273,7 @@ class ArrayObject final : public ObjectBase { if (title.isNotEmpty()) { if (!labels) { - labels = std::make_unique(cnv->editor->nvgSurface); + labels = std::make_unique(); } auto bounds = object->getBounds().reduced(Object::margin).removeFromTop(fontHeight + 2).withWidth(Font(fontHeight).getStringWidth(title)); diff --git a/Source/Objects/AtomHelper.h b/Source/Objects/AtomHelper.h index b274f53efe..5e33e60e45 100644 --- a/Source/Objects/AtomHelper.h +++ b/Source/Objects/AtomHelper.h @@ -282,7 +282,7 @@ class AtomHelper { if (text.isNotEmpty()) { if (!labels) { - labels = std::make_unique(cnv->editor->nvgSurface); + labels = std::make_unique(); } auto bounds = getLabelBounds(); diff --git a/Source/Objects/CommentObject.h b/Source/Objects/CommentObject.h index 1e963ee369..e1e000a83d 100644 --- a/Source/Objects/CommentObject.h +++ b/Source/Objects/CommentObject.h @@ -21,7 +21,6 @@ class CommentObject final : public ObjectBase public: CommentObject(pd::WeakReference obj, Object* object) : ObjectBase(obj, object) - , textRenderer(cnv->editor->nvgSurface) { objectParameters.addParamInt("Width (chars)", cDimensions, &sizeProperty); locked = getValue(object->locked); diff --git a/Source/Objects/GraphOnParent.h b/Source/Objects/GraphOnParent.h index 6e7e613af4..6e1ad88787 100644 --- a/Source/Objects/GraphOnParent.h +++ b/Source/Objects/GraphOnParent.h @@ -25,7 +25,6 @@ class GraphOnParent final : public ObjectBase { GraphOnParent(pd::WeakReference obj, Object* object) : ObjectBase(obj, object) , subpatch(new pd::Patch(obj, cnv->pd, false)) - , textRenderer(cnv->editor->nvgSurface) { resized(); diff --git a/Source/Objects/IEMHelper.h b/Source/Objects/IEMHelper.h index 068e4fba05..cd087591a1 100644 --- a/Source/Objects/IEMHelper.h +++ b/Source/Objects/IEMHelper.h @@ -285,7 +285,7 @@ class IEMHelper { if (text.isNotEmpty() || (gui->showVU())) { if (!labels) { - labels = std::make_unique(cnv->editor->nvgSurface); + labels = std::make_unique(); object->cnv->addChildComponent(labels.get()); if (gui->showVU()) labels->setObjectToTrack(object); diff --git a/Source/Objects/LuaObject.h b/Source/Objects/LuaObject.h index 5110767bab..fbe108dd8b 100644 --- a/Source/Objects/LuaObject.h +++ b/Source/Objects/LuaObject.h @@ -20,7 +20,7 @@ void pdlua_gfx_mouse_drag(t_pdlua* o, int x, int y); void pdlua_gfx_repaint(t_pdlua* o, int firsttime); } -class LuaObject : public ObjectBase, public Timer, public NVGContextListener { +class LuaObject : public ObjectBase, public Timer { Colour currentColour; @@ -83,8 +83,6 @@ class LuaObject : public ObjectBase, public Timer, public NVGContextListener { allDrawTargets[pdlua.get()].push_back(this); } - cnv->editor->nvgSurface.addNVGContextListener(this); - parentHierarchyChanged(); startTimerHz(60); } @@ -97,7 +95,6 @@ class LuaObject : public ObjectBase, public Timer, public NVGContextListener { listeners.erase(std::remove(listeners.begin(), listeners.end(), this), listeners.end()); } - cnv->editor->nvgSurface.removeNVGContextListener(this); zoomScale.removeListener(this); } @@ -120,12 +117,6 @@ class LuaObject : public ObjectBase, public Timer, public NVGContextListener { } } - void nvgContextDeleted(NVGcontext* nvg) override { - if(framebuffer) nvgDeleteFramebuffer(framebuffer); - framebuffer = nullptr; - imageNeedsRefresh = true; - }; - Rectangle getPdBounds() override { if (auto gobj = ptr.get()) { diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index 6916bcf615..0db387f870 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -21,7 +21,6 @@ class MessageObject final : public ObjectBase public: MessageObject(pd::WeakReference obj, Object* parent) : ObjectBase(obj, parent) - , textRenderer(cnv->editor->nvgSurface) { objectParameters.addParamInt("Width (chars)", cDimensions, &sizeProperty); } diff --git a/Source/Objects/ObjectBase.cpp b/Source/Objects/ObjectBase.cpp index 353c57f144..b51a603499 100644 --- a/Source/Objects/ObjectBase.cpp +++ b/Source/Objects/ObjectBase.cpp @@ -139,7 +139,6 @@ ObjectBase::ObjectBase(pd::WeakReference obj, Object* parent) , object(parent) , cnv(parent->cnv) , pd(parent->cnv->pd) - , imageRenderer(cnv->editor->nvgSurface) , objectSizeListener(parent) { object->addComponentListener(&objectSizeListener); diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index cf56cf652f..089446ea58 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -25,37 +25,25 @@ class Patch; class Object; -class ObjectLabel : public Label, public NVGComponent, public NVGContextListener { +class ObjectLabel : public Label, public NVGComponent { hash32 lastTextHash = 0; int imageId = 0; int lastWidth = 0, lastHeight = 0; float lastScale = 1.0f; - NVGSurface& surface; bool updateColour = false; Colour lastColour; public: - explicit ObjectLabel(NVGSurface& s) : NVGComponent(this), surface(s) + explicit ObjectLabel() : NVGComponent(this) { setJustificationType(Justification::centredLeft); setBorderSize(BorderSize(0, 0, 0, 0)); setMinimumHorizontalScale(0.2f); setEditable(false, false); setInterceptsMouseClicks(false, false); - surface.addNVGContextListener(this); } - - ~ObjectLabel() - { - surface.removeNVGContextListener(this); - } - - void nvgContextDeleted(NVGcontext* nvg) { - if(imageId) nvgDeleteImage(nvg, imageId); - imageId = 0; - } - + void renderLabel(NVGcontext* nvg, float scale) { auto textHash = hash(getText()); @@ -102,21 +90,18 @@ class ObjectLabel : public Label, public NVGComponent, public NVGContextListener private: }; -class VUScale : public Component, public NVGComponent, public NVGContextListener +class VUScale : public Component, public NVGComponent { - NVGSurface& surface; Colour textColour; StringArray scale = {"+12", "+6", "+2", "-0dB", "-2", "-6", "-12", "-20", "-30", "-50", "-99"}; StringArray scaleDecim = {"+12", "", "", "-0dB", "", "", "-12", "", "", "", "-99"}; public: - VUScale(NVGSurface& s) : NVGComponent(this), surface(s) + VUScale() : NVGComponent(this) { - surface.addNVGContextListener(this); } ~VUScale() { - surface.removeNVGContextListener(this); } void setColour(const Colour& colour) @@ -147,7 +132,7 @@ class VUScale : public Component, public NVGComponent, public NVGContextListener class ObjectLabels : public Component { public: - ObjectLabels(NVGSurface& s) : objectLabel(s), vuScale(s) + ObjectLabels() { addAndMakeVisible(objectLabel); addAndMakeVisible(vuScale); diff --git a/Source/Objects/PictureObject.h b/Source/Objects/PictureObject.h index 396fd76e3a..4e5a22d806 100644 --- a/Source/Objects/PictureObject.h +++ b/Source/Objects/PictureObject.h @@ -5,7 +5,7 @@ */ // ELSE pic -class PictureObject final : public ObjectBase, public NVGContextListener { +class PictureObject final : public ObjectBase { Value path = SynchronousValue(); Value latch = SynchronousValue(); @@ -45,15 +45,11 @@ class PictureObject final : public ObjectBase, public NVGContextListener { objectParameters.addParamBool("Report Size", cAppearance, &reportSize, { "No", "Yes" }, 0); objectParameters.addParamReceiveSymbol(&receiveSymbol); objectParameters.addParamSendSymbol(&sendSymbol); - - cnv->editor->nvgSurface.addNVGContextListener(this); } ~PictureObject() { // TODO: delete image buffers! - - cnv->editor->nvgSurface.removeNVGContextListener(this); } bool isTransparent() override @@ -76,10 +72,6 @@ class PictureObject final : public ObjectBase, public NVGContextListener { } } } - - void nvgContextDeleted(NVGcontext* nvg) override { - imageNeedsReload = true; - }; void mouseUp(MouseEvent const& e) override { diff --git a/Source/Objects/ScalarObject.h b/Source/Objects/ScalarObject.h index 4ea09efc49..32297228d9 100644 --- a/Source/Objects/ScalarObject.h +++ b/Source/Objects/ScalarObject.h @@ -366,7 +366,6 @@ class DrawableSymbol final : public DrawableTemplate DrawableSymbol(t_scalar* s, t_gobj* obj, t_word* data, t_template* templ, Canvas* cnv, int x, int y, t_template* parent = nullptr) : DrawableTemplate(s, data, templ, parent, cnv, x, y) , object(reinterpret_cast(obj)) - , textRenderer(cnv->editor->nvgSurface) { mouseListener.globalMouseDown = [this](MouseEvent const& e) { handleMouseDown(e.getEventRelativeTo(this)); diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index 050785f445..7371caa9f7 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -183,7 +183,6 @@ class TextBase : public ObjectBase public: TextBase(pd::WeakReference obj, Object* parent, bool valid = true) : ObjectBase(obj, parent) - , cachedTextRender(cnv->editor->nvgSurface) , isValid(valid) { objectText = getText(); diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index f6895c965c..4f01b6f5fe 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -29,6 +29,7 @@ #include "Dialogs/Dialogs.h" #include "Statusbar.h" #include "Tabbar/TabBarButtonComponent.h" +#include "Tabbar/WelcomePanel.h" #include "Sidebar/Sidebar.h" #include "Object.h" #include "PluginMode.h" @@ -62,9 +63,9 @@ PluginEditor::PluginEditor(PluginProcessor& p) }) { #if JUCE_IOS - //constrainer.setMinimumSize(100, 100); - //pluginConstrainer.setMinimumSize(100, 100); - //setResizable(true, false); + //constrainer.setMinimumSize(100, 100); + //pluginConstrainer.setMinimumSize(100, 100); + //setResizable(true, false); #else // if we are inside a DAW / host set up the border resizer now if (!ProjectInfo::isStandalone) { @@ -75,17 +76,21 @@ PluginEditor::PluginEditor(PluginProcessor& p) constrainer.setMinimumSize(850, 650); } #endif - + mainMenuButton.setButtonText(Icons::Menu); undoButton.setButtonText(Icons::Undo); redoButton.setButtonText(Icons::Redo); pluginModeButton.setButtonText(Icons::PluginMode); - + editButton.setButtonText(Icons::Edit); runButton.setButtonText(Icons::Lock); presentButton.setButtonText(Icons::Presentation); - + addKeyListener(commandManager.getKeyMappings()); + + welcomePanel = std::make_unique(this); + addAndMakeVisible(*welcomePanel); + welcomePanel->setAlwaysOnTop(true); setWantsKeyboardFocus(true); commandManager.registerAllCommandsForTarget(this); @@ -418,7 +423,8 @@ void PluginEditor::resized() auto workArea = Rectangle(paletteWidth, toolbarHeight, (getWidth() - sidebar->getWidth() - paletteWidth), workAreaHeight); splitView.setBounds(workArea); - nvgSurface.updateBounds(workArea.withTrimmedTop(31)); + welcomePanel->setBounds(workArea.withTrimmedTop(welcomePanel->isVisible() ? 0 : 31)); + nvgSurface.updateBounds(workArea.withTrimmedTop(welcomePanel->isVisible() ? 0 : 31)); sidebar->setBounds(getWidth() - sidebar->getWidth(), toolbarHeight, sidebar->getWidth(), workAreaHeight); @@ -668,7 +674,6 @@ void PluginEditor::createNewWindow(TabBarButtonComponent* tabButton) newWindow->toFront(true); closeTab(originalCanvas); - nvgSurface.sendContextDeleteMessage(); } bool PluginEditor::isActiveWindow() diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 27c588a583..0dbd7e96ff 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -18,6 +18,7 @@ #include "Tabbar/SplitView.h" + #include "Utility/ObjectThemeManager.h" #include "NVGSurface.h" @@ -63,6 +64,7 @@ class Palettes; class Autosave; class PluginMode; class TouchSelectionHelper; +class WelcomePanel; class PluginEditor : public AudioProcessorEditor , public Value::Listener , public ApplicationCommandTarget @@ -190,9 +192,9 @@ class PluginEditor : public AudioProcessorEditor static ObjectThemeManager* getObjectManager() { return &objectManager; }; std::unique_ptr calloutArea; - -private: + std::unique_ptr welcomePanel; +private: std::unique_ptr touchSelectionHelper; // Used by standalone to handle dragging the window diff --git a/Source/Standalone/PlugDataWindow.h b/Source/Standalone/PlugDataWindow.h index 680fd9455c..9394ce909d 100644 --- a/Source/Standalone/PlugDataWindow.h +++ b/Source/Standalone/PlugDataWindow.h @@ -456,17 +456,6 @@ class PlugDataWindow : public DocumentWindow } #endif - void clearAllBuffers() - { - if (!mainComponent) - return; - - if (auto* editor = mainComponent->getEditor()) { - if (auto* pdEditor = dynamic_cast(editor)) { - pdEditor->nvgSurface.sendContextDeleteMessage(); - } - } - } void propertyChanged(String const& name, var const& value) override { @@ -481,8 +470,7 @@ class PlugDataWindow : public DocumentWindow setUsingNativeTitleBar(nativeWindow); - pdEditor->nvgSurface.detachContext(); - clearAllBuffers(); + //pdEditor->nvgSurface.initialise(); if (!nativeWindow) { #if JUCE_WINDOWS @@ -625,7 +613,6 @@ class PlugDataWindow : public DocumentWindow void activeWindowStatusChanged() override { - clearAllBuffers(); repaint(); } diff --git a/Source/Tabbar/Tabbar.cpp b/Source/Tabbar/Tabbar.cpp index 07c0630fc6..02d30183f9 100644 --- a/Source/Tabbar/Tabbar.cpp +++ b/Source/Tabbar/Tabbar.cpp @@ -16,246 +16,6 @@ #include "Utility/StackShadow.h" #include "Utility/Autosave.h" -class WelcomePanel : public Component { - - class WelcomePanelTile : public Component - { - String tileName; - String tileSubtitle; - float snapshotScale; - bool isHovered = false; - std::unique_ptr snapshot = nullptr; - public: - - std::function onClick = [](){}; - - WelcomePanelTile(String name, String subtitle, String svgImage, Colour iconColour, float scale) - : tileName(name) - , tileSubtitle(subtitle) - , snapshotScale(scale) - { - snapshot = Drawable::createFromImageData(svgImage.toRawUTF8(), svgImage.getNumBytesAsUTF8()); - if(snapshot) { - snapshot->replaceColour (Colours::black, iconColour); - } - - resized(); - } - - void paint(Graphics& g) override - { - auto bounds = getLocalBounds().reduced(12); - - Path tilePath; - tilePath.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), Corners::largeCornerRadius); - - StackShadow::renderDropShadow(g, tilePath, Colour(0, 0, 0).withAlpha(0.1f), 7, {0, 2}); - g.setColour(findColour(PlugDataColour::canvasBackgroundColourId)); - g.fillPath(tilePath); - - if(snapshot) { - snapshot->drawAt(g, 0, 0, 1.0f); - } - - Path textAreaPath; - textAreaPath.addRoundedRectangle(bounds.getX(), bounds.getHeight() - 32, bounds.getWidth(), 44, Corners::largeCornerRadius, Corners::largeCornerRadius, false, false, true, true); - - auto hoverColour = findColour(PlugDataColour::toolbarHoverColourId).interpolatedWith(findColour(PlugDataColour::toolbarBackgroundColourId), 0.5f); - g.setColour(isHovered ? hoverColour : findColour(PlugDataColour::toolbarBackgroundColourId)); - g.fillPath(textAreaPath); - - g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); - g.strokePath(tilePath, PathStrokeType(1.0f)); - - Fonts::drawStyledText(g, tileName, bounds.getX() + 10, bounds.getHeight() - 30, bounds.getWidth() - 8, 24, findColour(PlugDataColour::panelTextColourId), Semibold, 14); - - g.setColour(findColour(PlugDataColour::panelTextColourId).withAlpha(0.75f)); - g.setFont(Fonts::getCurrentFont().withHeight(13.5f)); - g.drawText(tileSubtitle, bounds.getX() + 10, bounds.getHeight() - 10, bounds.getWidth() - 8, 16, Justification::centredLeft); - } - - void mouseEnter(const MouseEvent& e) override - { - isHovered = true; - repaint(); - } - - void mouseExit(const MouseEvent& e) override - { - isHovered = false; - repaint(); - } - - void mouseUp(const MouseEvent& e) override - { - onClick(); - } - - void resized() override - { - if(snapshot) { - auto bounds = getLocalBounds().reduced(12).withTrimmedBottom(44); - snapshot->setTransformToFit(bounds.withSizeKeepingCentre(bounds.getWidth() * snapshotScale, bounds.getHeight() * snapshotScale).toFloat(), RectanglePlacement::centred); - } - } - }; - -public: - WelcomePanel() - { - recentlyOpenedViewport.setViewedComponent(&recentlyOpenedComponent); - recentlyOpenedViewport.setScrollBarsShown(true, false, false, false); - recentlyOpenedComponent.setVisible(true); - addAndMakeVisible(recentlyOpenedViewport); - - // Update async to make sure silhouettes are available - MessageManager::callAsync([_this = SafePointer(this)](){ - _this->update(); - }); - } - - void resized() override - { - auto bounds = getLocalBounds().reduced(24).withTrimmedTop(36); - auto rowBounds = bounds.removeFromTop(160); - - const int desiredTileWidth = 190; - const int tileSpacing = 4; - - int totalWidth = bounds.getWidth(); - // Calculate the number of columns that can fit in the total width - int numColumns = totalWidth / (desiredTileWidth + tileSpacing); - // Adjust the tile width to fit within the available width - int actualTileWidth = (totalWidth - (numColumns - 1) * tileSpacing) / numColumns; - - if(newPatchTile) newPatchTile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); - rowBounds.removeFromLeft(4); - if(openPatchTile) openPatchTile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); - - bounds.removeFromTop(16); - - recentlyOpenedViewport.setBounds(getLocalBounds().withTrimmedTop(260)); - - int numRows = (tiles.size() + numColumns - 1) / numColumns; - int totalHeight = numRows * 160; - - auto scrollable = Rectangle(24, 0, totalWidth + 24, totalHeight); - recentlyOpenedComponent.setBounds(scrollable); - - // Start positioning the tiles - rowBounds = scrollable.removeFromTop(160); - for (auto* tile : tiles) - { - if (rowBounds.getWidth() < actualTileWidth) { - rowBounds = scrollable.removeFromTop(160); - } - tile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); - rowBounds.removeFromLeft(tileSpacing); - } - } - - void update() - { - newPatchTile = std::make_unique("New Patch", "Create a new empty patch", newIcon, findColour(PlugDataColour::panelTextColourId), 0.33f); - openPatchTile = std::make_unique("Open Patch", "Browse for a patch to open", openIcon, findColour(PlugDataColour::panelTextColourId), 0.33f); - - newPatchTile->onClick = [this]() { newTab(); }; - openPatchTile->onClick = [this](){ openPatch(); }; - - addAndMakeVisible(*newPatchTile); - addAndMakeVisible(*openPatchTile); - - tiles.clear(); - - auto settingsTree = SettingsFile::getInstance()->getValueTree(); - auto recentlyOpenedTree = settingsTree.getChildWithName("RecentlyOpened"); - - auto snapshotColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.3f); - - if (recentlyOpenedTree.isValid()) { - for (int i = 0; i < recentlyOpenedTree.getNumChildren(); i++) { - auto path = File(recentlyOpenedTree.getChild(i).getProperty("Path").toString()); - auto silhoutteSvg = Autosave::findPatchSilhouette(path); - - if(silhoutteSvg.isEmpty() && path.existsAsFile()) - { - silhoutteSvg = OfflineObjectRenderer::patchToSVGFast(path.loadFileAsString()); - } - - auto openTime = Time(static_cast(recentlyOpenedTree.getChild(i).getProperty("Time"))); - auto diff = Time::getCurrentTime() - openTime; - String date; - if(diff.inDays() == 0) date = "Today"; - else if(diff.inDays() == 1) date = "Yesterday"; - else date = openTime.toString(true, false); - String time = openTime.toString(false, true, false, true); - String timeDescription = date + ", " + time; - - auto* tile = tiles.add(new WelcomePanelTile(path.getFileName(), timeDescription, silhoutteSvg, snapshotColour, 1.0f)); - tile->onClick = [this, path](){ - onPatchOpen(path); - }; - recentlyOpenedComponent.addAndMakeVisible(tile); - } - } - - resized(); - } - - void show() - { - update(); - setVisible(true); - } - - void hide() - { - setVisible(false); - } - - void paint(Graphics& g) override - { - g.fillAll(findColour(PlugDataColour::panelBackgroundColourId)); - - Fonts::drawStyledText(g, "Welcome to plugdata", 32, 12, getWidth(), 40, findColour(PlugDataColour::panelTextColourId), Bold, 30, Justification::centredLeft); - //Fonts::drawStyledText(g, "Open a file to begin patching", 32, 42, getWidth(), 40, findColour(PlugDataColour::panelTextColourId), Thin, 23, Justification::centredLeft); - - Fonts::drawStyledText(g, "Recently opened", 32, 228, getWidth(), 32, findColour(PlugDataColour::panelTextColourId), Bold, 24, Justification::centredLeft); - } - - void lookAndFeelChanged() override - { - update(); - } - - std::function onPatchOpen = [](File){}; - std::function newTab = [](){}; - std::function openPatch = [](){}; - - static inline const String newIcon = "\n" - "\n" - "\n" - " \n" - "\n"; - - static inline const String openIcon = "\n" - "\n" - "\n" - " \n" - "\n"; - - std::unique_ptr newPatchTile; - std::unique_ptr openPatchTile; - - Component recentlyOpenedComponent; - BouncingViewport recentlyOpenedViewport; - - OwnedArray tiles; -}; - class ButtonBar::GhostTab : public Component { public: explicit GhostTab(PlugDataLook& lnfRef) @@ -526,22 +286,7 @@ TabComponent::TabComponent(PluginEditor* parent) newButton.onClick = [this]() { newTab(); }; - - welcomePanel = std::make_unique(); - addAndMakeVisible(welcomePanel.get()); - - welcomePanel->newTab = [this]() { - newTab(); - }; - - welcomePanel->openPatch = [this]() { - openProject(); - }; - - welcomePanel->onPatchOpen = [this](File patchFile) { - openProjectFile(patchFile); - }; - + setVisible(false); setTabBarDepth(0); tabs->addMouseListener(this, true); @@ -630,29 +375,24 @@ void TabComponent::moveTab(int currentIndex, int newIndex) tabs->moveTab(currentIndex, newIndex, true); } -void TabComponent::openProject() -{ - editor->openProject(); -} - void TabComponent::onTabChange(int tabIndex) { editor->updateCommandStatus(); - + // Show welcome panel if there are no tabs open if (tabs->getNumTabs() == 0) { setTabBarDepth(0); tabs->setVisible(false); - welcomePanel->show(); } else { tabs->setVisible(true); - welcomePanel->hide(); setTabBarDepth(30); // we need to update the dropzones, because no resize will be automatically triggered when there is a tab added from welcome screen if (auto* parentHolder = dynamic_cast(getParentComponent())) parentHolder->updateDropZones(); } + editor->resized(); + auto* cnv = getCurrentCanvas(); if (!cnv || tabIndex == -1 || editor->pd->isPerformingGlobalSync) return; @@ -665,6 +405,8 @@ void TabComponent::onTabChange(int tabIndex) if (tabBar->getCurrentCanvas()) tabBar->getCurrentCanvas()->tabChanged(); } + + } void TabComponent::changeCallback(int newCurrentTabIndex, String const& newTabName) @@ -691,15 +433,6 @@ void TabComponent::changeCallback(int newCurrentTabIndex, String const& newTabNa currentTabChanged(newCurrentTabIndex, newTabName); } -void TabComponent::openProjectFile(File& patchFile) -{ - editor->autosave->checkForMoreRecentAutosave(patchFile, [this, patchFile]() { - editor->pd->loadPatch(URL(patchFile), editor); - SettingsFile::getInstance()->addToRecentlyOpened(patchFile); - editor->pd->titleChanged(); - }); -} - void TabComponent::setTabBarDepth(int newDepth) { if (tabDepth != newDepth) { @@ -721,8 +454,6 @@ void TabComponent::handleAsyncUpdate() void TabComponent::resized() { auto content = getLocalBounds(); - - welcomePanel->setBounds(content); newButton.setBounds(3, 0, tabDepth, tabDepth); // slighly offset to make it centred next to the tabs auto tabBounds = content.removeFromTop(tabDepth).withTrimmedLeft(tabDepth); diff --git a/Source/Tabbar/Tabbar.h b/Source/Tabbar/Tabbar.h index c6d867c400..3ac7c90a5d 100644 --- a/Source/Tabbar/Tabbar.h +++ b/Source/Tabbar/Tabbar.h @@ -52,12 +52,11 @@ class ButtonBar : public TabbedButtonBar JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ButtonBar) }; -class WelcomePanel; class TabComponent : public Component , public AsyncUpdater { MainToolbarButton newButton = MainToolbarButton(Icons::Add); - std::unique_ptr welcomePanel; + PluginEditor* editor; public: @@ -80,9 +79,6 @@ class TabComponent : public Component int getTabBarDepth() const noexcept { return tabDepth; } void changeCallback(int newCurrentTabIndex, String const& newTabName); - void openProject(); - void openProjectFile(File& patchFile); - void currentTabChanged(int newCurrentTabIndex, String const& newCurrentTabName); void handleAsyncUpdate() override; void resized() override; diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Tabbar/WelcomePanel.h new file mode 100644 index 0000000000..608380212d --- /dev/null +++ b/Source/Tabbar/WelcomePanel.h @@ -0,0 +1,289 @@ +/* + // Copyright (c) 2024 Timothy Schoen + // For information on usage and redistribution, and for a DISCLAIMER OF ALL + // WARRANTIES, see the file, "LICENSE.txt," in this distribution. +*/ + +#pragma once +#include "Utility/Autosave.h" +#include "Utility/CachedTextRender.h" + +class WelcomePanel : public Component, public NVGComponent { + + class WelcomePanelTile : public Component + { + float snapshotScale; + bool isHovered = false; + String tileName; + std::unique_ptr snapshot = nullptr; + + CachedTextRender titleRenderer, subtitleRenderer; + public: + + std::function onClick = [](){}; + + WelcomePanelTile(String name, String subtitle, String svgImage, Colour iconColour, float scale) + : snapshotScale(scale), tileName(name) + { + snapshot = Drawable::createFromImageData(svgImage.toRawUTF8(), svgImage.getNumBytesAsUTF8()); + if(snapshot) { + snapshot->replaceColour (Colours::black, iconColour); + } + + resized(); + + auto textColour = findColour(PlugDataColour::panelTextColourId); + titleRenderer.prepareLayout(name, Fonts::getBoldFont().withHeight(14), textColour, 500, 500); + subtitleRenderer.prepareLayout(subtitle, Fonts::getCurrentFont().withHeight(13.5f), textColour, 500, 500); + } + + void paint(Graphics& g) override + { + auto bounds = getLocalBounds().reduced(12); + + Path tilePath; + tilePath.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), Corners::largeCornerRadius); + + StackShadow::renderDropShadow(g, tilePath, Colour(0, 0, 0).withAlpha(0.1f), 7, {0, 2}); + g.setColour(findColour(PlugDataColour::canvasBackgroundColourId)); + g.fillPath(tilePath); + + if(snapshot) { + snapshot->drawAt(g, 0, 0, 1.0f); + } + + Path textAreaPath; + textAreaPath.addRoundedRectangle(bounds.getX(), bounds.getHeight() - 32, bounds.getWidth(), 44, Corners::largeCornerRadius, Corners::largeCornerRadius, false, false, true, true); + + auto hoverColour = findColour(PlugDataColour::toolbarHoverColourId).interpolatedWith(findColour(PlugDataColour::toolbarBackgroundColourId), 0.5f); + g.setColour(isHovered ? hoverColour : findColour(PlugDataColour::toolbarBackgroundColourId)); + g.fillPath(textAreaPath); + + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); + g.strokePath(tilePath, PathStrokeType(1.0f)); + + auto* nvg = dynamic_cast(g.getInternalContext()).getContext(); + + nvgSave(nvg); + nvgTranslate(nvg, 0, bounds.getHeight() - 30); + titleRenderer.renderText(nvg, Rectangle(bounds.getX() + 10, 0, bounds.getWidth() - 8, 24), 2.0f); + nvgTranslate(nvg, 0, 20); + subtitleRenderer.renderText(nvg, Rectangle(bounds.getX() + 10, 0, bounds.getWidth() - 8, 16), 2.0f); + nvgRestore(nvg); + + + //Fonts::drawStyledText(g, tileName, bounds.getX() + 10, bounds.getHeight() - 30, bounds.getWidth() - 8, 24, findColour(PlugDataColour::panelTextColourId), Semibold, 14); + /* + g.setColour(findColour(PlugDataColour::panelTextColourId).withAlpha(0.75f)); + g.setFont(Fonts::getCurrentFont().withHeight(13.5f)); + g.drawText(tileSubtitle, , Justification::centredLeft); */ + } + + void mouseEnter(const MouseEvent& e) override + { + isHovered = true; + repaint(); + } + + void mouseExit(const MouseEvent& e) override + { + isHovered = false; + repaint(); + } + + void mouseUp(const MouseEvent& e) override + { + onClick(); + } + + void resized() override + { + if(snapshot) { + auto bounds = getLocalBounds().reduced(12).withTrimmedBottom(44); + snapshot->setTransformToFit(bounds.withSizeKeepingCentre(bounds.getWidth() * snapshotScale, bounds.getHeight() * snapshotScale).toFloat(), RectanglePlacement::centred); + } + } + }; + +public: + WelcomePanel(PluginEditor* pluginEditor) : NVGComponent(this), editor(pluginEditor) + { + recentlyOpenedViewport.setViewedComponent(&recentlyOpenedComponent, false); + recentlyOpenedViewport.setScrollBarsShown(true, false, false, false); + recentlyOpenedComponent.setVisible(true); + addAndMakeVisible(recentlyOpenedViewport); + + // Update async to make sure silhouettes are available + MessageManager::callAsync([_this = SafePointer(this)](){ + _this->update(); + }); + + setCachedComponentImage(new NVGSurface::InvalidationListener(editor->nvgSurface, this)); + } + + void resized() override + { + auto bounds = getLocalBounds().reduced(24).withTrimmedTop(36); + auto rowBounds = bounds.removeFromTop(160); + + const int desiredTileWidth = 190; + const int tileSpacing = 4; + + int totalWidth = bounds.getWidth(); + // Calculate the number of columns that can fit in the total width + int numColumns = totalWidth / (desiredTileWidth + tileSpacing); + // Adjust the tile width to fit within the available width + int actualTileWidth = (totalWidth - (numColumns - 1) * tileSpacing) / numColumns; + + if(newPatchTile) newPatchTile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); + rowBounds.removeFromLeft(4); + if(openPatchTile) openPatchTile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); + + bounds.removeFromTop(16); + + recentlyOpenedViewport.setBounds(getLocalBounds().withTrimmedTop(260)); + + int numRows = (tiles.size() + numColumns - 1) / numColumns; + int totalHeight = numRows * 160; + + auto scrollable = Rectangle(24, 6, totalWidth + 24, totalHeight); + recentlyOpenedComponent.setBounds(scrollable); + + // Start positioning the tiles + rowBounds = scrollable.removeFromTop(160); + for (auto* tile : tiles) + { + if (rowBounds.getWidth() < actualTileWidth) { + rowBounds = scrollable.removeFromTop(160); + } + tile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); + rowBounds.removeFromLeft(tileSpacing); + } + } + + void update() + { + newPatchTile = std::make_unique("New Patch", "Create a new empty patch", newIcon, findColour(PlugDataColour::panelTextColourId), 0.33f); + openPatchTile = std::make_unique("Open Patch", "Browse for a patch to open", openIcon, findColour(PlugDataColour::panelTextColourId), 0.33f); + + newPatchTile->onClick = [this]() { editor->newProject(); }; + openPatchTile->onClick = [this](){ editor->openProject(); }; + + addAndMakeVisible(*newPatchTile); + addAndMakeVisible(*openPatchTile); + + tiles.clear(); + + auto settingsTree = SettingsFile::getInstance()->getValueTree(); + auto recentlyOpenedTree = settingsTree.getChildWithName("RecentlyOpened"); + + auto snapshotColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.3f); + + if (recentlyOpenedTree.isValid()) { + for (int i = 0; i < recentlyOpenedTree.getNumChildren(); i++) { + auto patchFile = File(recentlyOpenedTree.getChild(i).getProperty("Path").toString()); + auto silhoutteSvg = Autosave::findPatchSilhouette(patchFile); + + if(silhoutteSvg.isEmpty() && patchFile.existsAsFile()) + { + silhoutteSvg = OfflineObjectRenderer::patchToSVGFast(patchFile.loadFileAsString()); + } + + auto openTime = Time(static_cast(recentlyOpenedTree.getChild(i).getProperty("Time"))); + auto diff = Time::getCurrentTime() - openTime; + String date; + if(diff.inDays() == 0) date = "Today"; + else if(diff.inDays() == 1) date = "Yesterday"; + else date = openTime.toString(true, false); + String time = openTime.toString(false, true, false, true); + String timeDescription = date + ", " + time; + + auto* tile = tiles.add(new WelcomePanelTile(patchFile.getFileName(), timeDescription, silhoutteSvg, snapshotColour, 1.0f)); + tile->onClick = [this, patchFile]() mutable { + editor->autosave->checkForMoreRecentAutosave(patchFile, [this, patchFile]() { + editor->pd->loadPatch(URL(patchFile), editor); + SettingsFile::getInstance()->addToRecentlyOpened(patchFile); + editor->pd->titleChanged(); + }); + }; + recentlyOpenedComponent.addAndMakeVisible(tile); + } + } + + resized(); + } + + void show() + { + update(); + setVisible(true); + } + + void hide() + { + setVisible(false); + } + + void render(NVGcontext* nvg) override + { + if(!nvgContext) nvgContext = std::make_unique(nvg); + + nvgBeginPath(nvg); + nvgRect(nvg, 0, 0, getWidth(), getHeight()); + nvgFillColor(nvg, convertColour(findColour(PlugDataColour::panelBackgroundColourId))); + nvgFill(nvg); + + Graphics g(*nvgContext); + g.reduceClipRegion(editor->nvgSurface.getInvalidArea()); + paintEntireComponent(g, false); + + auto gradient = nvgLinearGradient(nvg, 0, recentlyOpenedViewport.getY(), 0, recentlyOpenedViewport.getY() + 20, convertColour(findColour(PlugDataColour::panelBackgroundColourId)), nvgRGBAf(1, 1, 1, 0)); + + nvgFillPaint(nvg, gradient); + nvgBeginPath(nvg); + nvgRect(nvg, recentlyOpenedViewport.getX() + 8, recentlyOpenedViewport.getY(), recentlyOpenedViewport.getWidth() - 16, 20); + nvgFill(nvg); + + nvgBeginPath(nvg); + nvgFillColor(nvg, convertColour(findColour(PlugDataColour::panelTextColourId))); + nvgTextAlign(nvg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); + nvgFontSize(nvg, 30); + nvgFontFace(nvg, "Inter-Bold"); + nvgText(nvg, 32, 32, "Welcome to plugdata", nullptr); + + nvgBeginPath(nvg); + nvgFontSize(nvg, 24); + nvgText(nvg, 32, 240, "Recently Opened", nullptr); + } + + void lookAndFeelChanged() override + { + update(); + } + + static inline const String newIcon = "\n" + "\n" + "\n" + " \n" + "\n"; + + static inline const String openIcon = "\n" + "\n" + "\n" + " \n" + "\n"; + + std::unique_ptr newPatchTile; + std::unique_ptr openPatchTile; + + Component recentlyOpenedComponent; + BouncingViewport recentlyOpenedViewport; + + std::unique_ptr nvgContext = nullptr; + + OwnedArray tiles; + PluginEditor* editor; +}; diff --git a/Source/Utility/CachedTextRender.h b/Source/Utility/CachedTextRender.h index 1bd50bd40a..5eee0133b0 100644 --- a/Source/Utility/CachedTextRender.h +++ b/Source/Utility/CachedTextRender.h @@ -1,25 +1,11 @@ #pragma once -class CachedTextRender : public NVGContextListener +class CachedTextRender { public: - CachedTextRender(NVGSurface& nvgSurface) : surface(nvgSurface) - { - surface.addNVGContextListener(this); - } - - ~CachedTextRender() - { - surface.removeNVGContextListener(this); - } - - void nvgContextDeleted(NVGcontext* nvg) override - { - if(imageId) nvgDeleteImage(nvg, imageId); - imageId = 0; - } - + CachedTextRender() = default; + void renderText(NVGcontext* nvg, Rectangle const& bounds, float scale) { if(updateImage || imageId <= 0 || lastRenderBounds != bounds || lastScale != scale) @@ -48,7 +34,7 @@ class CachedTextRender : public NVGContextListener auto attributedText = AttributedString(text); attributedText.setColour(colour); attributedText.setJustification(Justification::centredLeft); - attributedText.setFont(Font(15)); + attributedText.setFont(font); layout = TextLayout(); layout.createLayout(attributedText, width); @@ -115,7 +101,6 @@ class CachedTextRender : public NVGContextListener } private: - NVGSurface& surface; int imageId = 0; int imageWidth = 0, imageHeight = 0; diff --git a/Source/Utility/NVGComponent.h b/Source/Utility/NVGComponent.h index b19b7a4fa4..f6d1690c1d 100644 --- a/Source/Utility/NVGComponent.h +++ b/Source/Utility/NVGComponent.h @@ -4,7 +4,6 @@ class NVGComponent { - public: NVGComponent(Component*); @@ -24,18 +23,15 @@ class NVGComponent JUCE_DECLARE_WEAK_REFERENCEABLE(NVGComponent) }; -class NVGImageRenderer : public NVGContextListener +class NVGImageRenderer { - NVGSurface& surface; public: - NVGImageRenderer(NVGSurface& nvgSurace) : surface(nvgSurace) + NVGImageRenderer() { - surface.addNVGContextListener(this); } ~NVGImageRenderer() { - surface.removeNVGContextListener(this); } static int convertImage(NVGcontext* nvg, Image& image, int imageToUpdate = -1) @@ -93,13 +89,6 @@ class NVGImageRenderer : public NVGContextListener nvgFill(nvg); } - - void nvgContextDeleted(NVGcontext* nvg) override - { - if(imageId) nvgDeleteImage(nvg, imageId); - imageId = 0; - } - private: int imageId = 0; int lastHeight = 0; diff --git a/Source/Utility/NanoVGGraphicsContext.cpp b/Source/Utility/NanoVGGraphicsContext.cpp index 506c3885ef..df72fa6d97 100644 --- a/Source/Utility/NanoVGGraphicsContext.cpp +++ b/Source/Utility/NanoVGGraphicsContext.cpp @@ -289,7 +289,8 @@ void NanoVGGraphicsContext::setPath (const juce::Path& path, const juce::AffineT // Flag is used to flip winding when drawing shapes with holes. bool solid = true; - + nvgPathWinding(nvg, NVG_SOLID); + while (i.next()) { switch (i.elementType) @@ -315,7 +316,6 @@ void NanoVGGraphicsContext::setPath (const juce::Path& path, const juce::AffineT break; } } - } void NanoVGGraphicsContext::fillPath (const juce::Path& path, const juce::AffineTransform& transform) diff --git a/Source/Utility/OSUtils.h b/Source/Utility/OSUtils.h index 6473818f7c..bd279194d8 100644 --- a/Source/Utility/OSUtils.h +++ b/Source/Utility/OSUtils.h @@ -58,7 +58,7 @@ struct OSUtils { } private: - bool scrolling; + bool scrolling = false; void* observer; static inline ScrollTracker* instance = create(); }; From 6be866602f77b7b64fbd4d34d161ed89a3fc41f2 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 28 May 2024 14:02:01 +0200 Subject: [PATCH 0797/1030] Fix debug overlay setting, reset zoom to 100 when clicking on zoom label --- Source/Dialogs/OverlayDisplaySettings.h | 12 ++++++-- Source/Statusbar.cpp | 37 ++++++++++++++++++++++--- Source/Statusbar.h | 3 +- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/Source/Dialogs/OverlayDisplaySettings.h b/Source/Dialogs/OverlayDisplaySettings.h index 60e07e00f8..71b753cff2 100644 --- a/Source/Dialogs/OverlayDisplaySettings.h +++ b/Source/Dialogs/OverlayDisplaySettings.h @@ -9,7 +9,7 @@ #include "LookAndFeel.h" #include "Components/PropertiesPanel.h" -class OverlayDisplaySettings : public Component { +class OverlayDisplaySettings : public Component, public Value::Listener { public: class OverlaySelector : public Component , public Button::Listener { @@ -140,6 +140,7 @@ class OverlayDisplaySettings : public Component { connection.add(new OverlaySelector(overlayTree, Behind, "behind", "Behind", "Connection cables behind objects")); debugModeValue.referTo(SettingsFile::getInstance()->getPropertyAsValue("debug_connections")); + debugModeValue.addListener(this); connectionDebugToggle.reset(new PropertiesPanel::BoolComponent("Debug", debugModeValue, { "No", "Yes" })); connectionDebugToggle->setTooltip("Enable connection debugging tooltips"); addAndMakeVisible(*connectionDebugToggle); @@ -154,7 +155,14 @@ class OverlayDisplaySettings : public Component { } } setSize(335, 200); - + } + + void valueChanged(Value& v) override + { + if(v.refersToSameSourceAs(debugModeValue)) + { + set_plugdata_debugging_enabled(getValue(debugModeValue)); + } } void resized() override diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 192a925365..7f80ce7992 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -646,6 +646,36 @@ class CPUMeter : public Component JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CPUMeter); }; +class ZoomLabel : public Component +{ +public: + ZoomLabel(Statusbar* parent) : statusbar(parent) + { + setRepaintsOnMouseActivity(true); + } +private: + + void paint(Graphics& g) override + { + // We can use a tabular numbers font here, but I'm not sure it really looks better that way + //g.setFont(Fonts::getTabularNumbersFont().withHeight(14)); + g.setColour(findColour(PlugDataColour::toolbarTextColourId).contrasting(isMouseOver() ? 0.35f : 0.0f)); + g.drawText(String(int(statusbar->currentZoomLevel)) + "%", 0, 0, 44, getHeight(), Justification::centredLeft); + } + + void mouseDown(const MouseEvent& e) override + { + auto* editor = findParentComponentOfClass(); + if (auto* cnv = editor->getCurrentCanvas()) { + cnv->zoomScale.setValue(1.0f); + cnv->setTransform(AffineTransform().scaled(1.0f)); + cnv->viewport->resized(); + } + } + + Statusbar* statusbar; +}; + Statusbar::Statusbar(PluginProcessor* processor) : pd(processor) { @@ -653,6 +683,7 @@ Statusbar::Statusbar(PluginProcessor* processor) cpuMeter = std::make_unique(); midiBlinker = std::make_unique(); volumeSlider = std::make_unique(); + zoomLabel = std::make_unique(this); pd->statusbarSource->addListener(levelMeter.get()); pd->statusbarSource->addListener(midiBlinker.get()); @@ -713,6 +744,7 @@ Statusbar::Statusbar(PluginProcessor* processor) addAndMakeVisible(*levelMeter); addAndMakeVisible(*midiBlinker); addAndMakeVisible(*cpuMeter); + addAndMakeVisible(*zoomLabel); levelMeter->toBehind(volumeSlider.get()); @@ -814,9 +846,6 @@ void Statusbar::updateZoomLevel() void Statusbar::paint(Graphics& g) { - g.setColour(findColour(PlugDataColour::toolbarTextColourId)); - g.drawText(String(int(currentZoomLevel)) + "%", 10, 0, 44, getHeight(), Justification::centredLeft); - g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); auto* editor = findParentComponentOfClass(); @@ -839,7 +868,7 @@ void Statusbar::resized() auto spacing = getHeight(); - position(34); // Space for zoom label + zoomLabel->setBounds(position(34), 0, 34, getHeight()); zoomComboButton.setBounds(position(8) - 11, 0, getHeight(), getHeight()); firstSeparatorPosition = position(4) + 3.5f; // fifth seperator diff --git a/Source/Statusbar.h b/Source/Statusbar.h index b6e774bf5c..8dd99f3f0e 100644 --- a/Source/Statusbar.h +++ b/Source/Statusbar.h @@ -72,6 +72,7 @@ class StatusbarSource : public Timer { }; class VolumeSlider; +class ZoomLabel; class Statusbar : public Component , public StatusbarSource::Listener , public ModifierKeyListener { @@ -104,7 +105,7 @@ class Statusbar : public Component std::unique_ptr latencyDisplayButton; - Label zoomLabel; + std::unique_ptr zoomLabel; int currentLatency = 64; float currentZoomLevel = 100.f; From 953a79a6a660ea1a8c55ef500f735a666fe0a5fd Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 28 May 2024 14:16:51 +0200 Subject: [PATCH 0798/1030] Show iolet tooltips when creating connections --- Source/Iolet.cpp | 15 ++++++++++++++- Source/PluginEditor.h | 3 ++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index 54c59bc5cd..5e85b41cc6 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -15,6 +15,7 @@ using namespace juce::gl; #include "Object.h" #include "Canvas.h" +#include "PluginEditor.h" #include "Connection.h" #include "LookAndFeel.h" @@ -131,7 +132,8 @@ void Iolet::mouseDrag(MouseEvent const& e) if (nearest && cnv->nearestIolet != nearest) { nearest->isTargeted = true; - + cnv->editor->tooltipWindow.displayTip(nearest->getScreenPosition(), nearest->getTooltip()); + if (cnv->nearestIolet) { cnv->nearestIolet->isTargeted = false; cnv->nearestIolet->repaint(); @@ -140,6 +142,7 @@ void Iolet::mouseDrag(MouseEvent const& e) cnv->nearestIolet = nearest; cnv->nearestIolet->repaint(); } else if (!nearest && cnv->nearestIolet) { + cnv->editor->tooltipWindow.hideTip(); cnv->nearestIolet->isTargeted = false; cnv->nearestIolet->repaint(); cnv->nearestIolet = nullptr; @@ -305,6 +308,11 @@ void Iolet::mouseEnter(MouseEvent const& e) { isTargeted = true; object->drawIoletExpanded = true; + + if(cnv->connectionsBeingCreated.size() == 1) + { + cnv->editor->tooltipWindow.displayTip(getScreenPosition(), getTooltip()); + } for (auto& iolet : object->iolets) iolet->repaint(); @@ -315,6 +323,11 @@ void Iolet::mouseExit(MouseEvent const& e) isTargeted = false; object->drawIoletExpanded = false; + if(cnv->connectionsBeingCreated.size() == 1) + { + cnv->editor->tooltipWindow.hideTip(); + } + for (auto& iolet : object->iolets) iolet->repaint(); } diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 0dbd7e96ff..6c6e3e33e0 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -194,6 +194,8 @@ class PluginEditor : public AudioProcessorEditor std::unique_ptr calloutArea; std::unique_ptr welcomePanel; + CheckedTooltip tooltipWindow; + private: std::unique_ptr touchSelectionHelper; @@ -205,7 +207,6 @@ class PluginEditor : public AudioProcessorEditor MainToolbarButton mainMenuButton, undoButton, redoButton, addObjectMenuButton, pluginModeButton; ToolbarRadioButton editButton, runButton, presentButton; - CheckedTooltip tooltipWindow; TextButton seperators[8]; From 3eb14235aa734a9a71c006669f2e38cfe97eaa96 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Tue, 28 May 2024 17:49:42 +0200 Subject: [PATCH 0799/1030] Make limiter button very obvious, add limiter threshold options --- Source/Dialogs/AudioOutputSettings.h | 74 ++++++++++++++++++++++++---- Source/PluginProcessor.cpp | 12 ++++- Source/PluginProcessor.h | 1 + Source/Statusbar.cpp | 23 +++++++-- Source/Statusbar.h | 4 +- Source/Utility/Limiter.h | 11 ++++- Source/Utility/SettingsFile.h | 1 + 7 files changed, 108 insertions(+), 18 deletions(-) diff --git a/Source/Dialogs/AudioOutputSettings.h b/Source/Dialogs/AudioOutputSettings.h index ebd022ecd1..e772203b66 100644 --- a/Source/Dialogs/AudioOutputSettings.h +++ b/Source/Dialogs/AudioOutputSettings.h @@ -62,6 +62,61 @@ class OversampleSettings : public Component { TextButton eight = TextButton("8x"); }; +class LimiterSettings : public Component { +public: + std::function onChange = [](int) {}; + + explicit LimiterSettings(int currentSelection) + { + one.setConnectedEdges(Button::ConnectedOnRight); + two.setConnectedEdges(Button::ConnectedOnLeft | Button::ConnectedOnRight); + three.setConnectedEdges(Button::ConnectedOnLeft | Button::ConnectedOnRight); + four.setConnectedEdges(Button::ConnectedOnLeft); + + auto buttons = Array { &one, &two, &three, &four }; + + int i = 0; + for (auto* button : buttons) { + button->setRadioGroupId(hash("oversampling_selector")); + button->setClickingTogglesState(true); + button->onClick = [this, i]() { + onChange(i); + }; + + button->setColour(TextButton::textColourOffId, findColour(PlugDataColour::popupMenuTextColourId)); + button->setColour(TextButton::textColourOnId, findColour(PlugDataColour::popupMenuActiveTextColourId)); + button->setColour(TextButton::buttonColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.04f)); + button->setColour(TextButton::buttonOnColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.075f)); + button->setColour(ComboBox::outlineColourId, Colours::transparentBlack); + + addAndMakeVisible(button); + i++; + } + + buttons[currentSelection]->setToggleState(true, dontSendNotification); + + setSize(180, 50); + } + + private: + void resized() override + { + auto b = getLocalBounds().reduced(4, 4); + auto buttonWidth = b.getWidth() / 4; + + one.setBounds(b.removeFromLeft(buttonWidth)); + two.setBounds(b.removeFromLeft(buttonWidth).expanded(1, 0)); + three.setBounds(b.removeFromLeft(buttonWidth).expanded(1, 0)); + four.setBounds(b.removeFromLeft(buttonWidth).expanded(1, 0)); + } + + TextButton one = TextButton("-12db"); + TextButton two = TextButton("-6db"); + TextButton three = TextButton("0db"); + TextButton four = TextButton("3db"); +}; + + class AudioOutputSettings : public Component { class LimiterEnableButton : public Component @@ -118,20 +173,19 @@ class AudioOutputSettings : public Component { }; public: - AudioOutputSettings(PluginProcessor* pd) : oversampleSettings(SettingsFile::getInstance()->getValueTree().getProperty("Oversampling")) + AudioOutputSettings(PluginProcessor* pd) : oversampleSettings(SettingsFile::getInstance()->getProperty("oversampling")), limiterSettings(SettingsFile::getInstance()->getProperty("limiter_threshold")) { - enableLimiterButton = std::make_unique(this, Icons::Protection, "Enable limiter", SettingsFile::getInstance()->getProperty("protected")); - enableLimiterButton->onClick = [pd](bool state){ - pd->setProtectedMode(state); - SettingsFile::getInstance()->setProperty("protected", state); + addAndMakeVisible(limiterSettings); + limiterSettings.onChange = [pd](int value){ + pd->setLimiterThreshold(value); }; - addAndMakeVisible(*enableLimiterButton); + addAndMakeVisible(oversampleSettings); oversampleSettings.onChange = [pd](int value){ pd->setOversampling(value); }; - setSize(160, 125); + setSize(170, 125); } ~AudioOutputSettings() @@ -143,7 +197,7 @@ class AudioOutputSettings : public Component { { auto bounds = getLocalBounds().reduced(4.0f).withTrimmedTop(24); - enableLimiterButton->setBounds(bounds.removeFromTop(32)); + limiterSettings.setBounds(bounds.removeFromTop(28)); bounds.removeFromTop(32); oversampleSettings.setBounds(bounds.removeFromTop(28)); @@ -153,7 +207,7 @@ class AudioOutputSettings : public Component { { g.setColour(findColour(PlugDataColour::popupMenuTextColourId)); g.setFont(Fonts::getBoldFont().withHeight(15)); - g.drawText("Limiter", 0, 0, getWidth(), 24, Justification::centred); + g.drawText("Limiter Threshold", 0, 0, getWidth(), 24, Justification::centred); g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.drawLine(4, 24, getWidth() - 8, 24); @@ -181,7 +235,7 @@ class AudioOutputSettings : public Component { private: static inline bool isShowing = false; - std::unique_ptr enableLimiterButton; + LimiterSettings limiterSettings; OversampleSettings oversampleSettings; }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 2bdf8b46e2..4f3b52610f 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -156,6 +156,7 @@ PluginProcessor::PluginProcessor() oversampling = settingsFile->getProperty("oversampling"); setProtectedMode(settingsFile->getProperty("protected")); + setLimiterThreshold(settingsFile->getProperty("limiter_threshold")); enableInternalSynth = settingsFile->getProperty("internal_synth"); auto currentThemeTree = settingsFile->getCurrentTheme(); @@ -434,7 +435,7 @@ void PluginProcessor::setOversampling(int amount) if (oversampling == amount) return; - settingsFile->setProperty("Oversampling", var(amount)); + settingsFile->setProperty("oversampling", var(amount)); settingsFile->saveSettings(); // TODO: i think this is unnecessary? oversampling = amount; @@ -446,6 +447,15 @@ void PluginProcessor::setOversampling(int amount) suspendProcessing(false); } +void PluginProcessor::setLimiterThreshold(int amount) +{ + auto threshold = (std::vector{-12, -6, 0, 3})[amount]; + limiter.setThreshold(threshold); + + settingsFile->setProperty("limiter_threshold", var(amount)); + settingsFile->saveSettings(); // TODO: i think this is unnecessary? +} + void PluginProcessor::setProtectedMode(bool enabled) { protectedMode = enabled; diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index db92fff87c..4ca5c709ad 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -37,6 +37,7 @@ class PluginProcessor : public AudioProcessor, public AsyncUpdater static AudioProcessor::BusesProperties buildBusesProperties(); void setOversampling(int amount); + void setLimiterThreshold(int amount); void setProtectedMode(bool enabled); void prepareToPlay(double sampleRate, int samplesPerBlock) override; void numChannelsChanged() override; diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 7f80ce7992..f7acd284bc 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -168,6 +168,7 @@ class LatencyDisplayButton : public Component, public MultiTimer, public Settabl class VolumeSlider : public Slider { public: + VolumeSlider() : Slider(Slider::LinearHorizontal, Slider::NoTextBox) { @@ -284,7 +285,7 @@ class LevelMeter : public Component auto width = getWidth() - 12.0f; auto x = 6.0f; - auto outerBorderWidth = 2.0f; + auto outerBorderWidth = 2.5f; auto doubleOuterBorderWidth = 2.0f * outerBorderWidth; auto bgHeight = getHeight() - doubleOuterBorderWidth; auto bgWidth = width - doubleOuterBorderWidth; @@ -809,8 +810,20 @@ Statusbar::Statusbar(PluginProcessor* processor) OverlayDisplaySettings::show(editor, overlaySettingsButton.getScreenBounds()); }; addAndMakeVisible(overlaySettingsButton); + + limiterButton.setColour(ComboBox::outlineColourId, Colours::transparentBlack); + limiterButton.setColour(TextButton::buttonColourId, findColour(PlugDataColour::levelMeterBackgroundColourId)); + limiterButton.setColour(TextButton::buttonOnColourId, findColour(PlugDataColour::levelMeterThumbColourId).withAlpha(0.3f)); + limiterButton.setClickingTogglesState(true); + limiterButton.setToggleState(SettingsFile::getInstance()->getProperty("protected"), dontSendNotification); + limiterButton.onClick = [this](){ + auto state = limiterButton.getToggleState(); + pd->setProtectedMode(state); + SettingsFile::getInstance()->setProperty("protected", state); + }; + addAndMakeVisible(limiterButton); - + zoomComboButton.setTooltip(String("Select zoom")); addAndMakeVisible(audioSettingsButton); @@ -898,10 +911,12 @@ void Statusbar::resized() powerButton.setBounds(position(spacing, true) + 16, 0, getHeight(), getHeight()); // TODO: combine these both into one - int levelMeterPosition = position(95, true); + int levelMeterPosition = position(132, true); levelMeter->setBounds(levelMeterPosition, 2, 120, getHeight() - 4); volumeSlider->setBounds(levelMeterPosition, 2, 120, getHeight() - 4); - + + limiterButton.setBounds(volumeSlider->getBounds().removeFromRight(42).translated(32, 0).reduced(2)); + // Hide these if there isn't enough space midiBlinker->setVisible(getWidth() > 500); cpuMeter->setVisible(getWidth() > 500); diff --git a/Source/Statusbar.h b/Source/Statusbar.h index 8dd99f3f0e..2807cdbc54 100644 --- a/Source/Statusbar.h +++ b/Source/Statusbar.h @@ -102,7 +102,9 @@ class Statusbar : public Component SmallIconButton overlayButton, overlaySettingsButton; SmallIconButton snapEnableButton, snapSettingsButton; SmallIconButton powerButton, audioSettingsButton; - + + TextButton limiterButton = TextButton("Limit", "Enable limiter"); + std::unique_ptr latencyDisplayButton; std::unique_ptr zoomLabel; diff --git a/Source/Utility/Limiter.h b/Source/Utility/Limiter.h index 4dc6d00377..9508d9364c 100644 --- a/Source/Utility/Limiter.h +++ b/Source/Utility/Limiter.h @@ -40,15 +40,21 @@ class Limiter { secondStageCompressor.reset(); } + void setThreshold (float newThreshold) + { + thresholddB = newThreshold; + update(); + } + private: void update() { - firstStageCompressor.setThreshold(-8.0f); + firstStageCompressor.setThreshold(thresholddB - 2.0f); firstStageCompressor.setRatio(4.0f); firstStageCompressor.setAttack(2.0f); firstStageCompressor.setRelease(200.0f); - secondStageCompressor.setThreshold(-6.0f); + secondStageCompressor.setThreshold(thresholddB); secondStageCompressor.setRatio(1000.0f); secondStageCompressor.setAttack(0.001f); secondStageCompressor.setRelease(releaseTime); @@ -59,4 +65,5 @@ class Limiter { double sampleRate = 44100.0; float releaseTime = 100.0; + float thresholddB = -6.0f; }; diff --git a/Source/Utility/SettingsFile.h b/Source/Utility/SettingsFile.h index c75860f009..7db28de05f 100644 --- a/Source/Utility/SettingsFile.h +++ b/Source/Utility/SettingsFile.h @@ -102,6 +102,7 @@ class SettingsFile : public ValueTree::Listener { "browser_path", var(ProjectInfo::appDataDir.getFullPathName()) }, { "theme", var("light") }, { "oversampling", var(0) }, + { "limiter_threshold", var(1) }, { "protected", var(1) }, { "debug_connections", var(1) }, { "internal_synth", var(0) }, From e9cd6790f205fc094bcef6f6c06167d8f6da9f81 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 29 May 2024 02:02:00 +0200 Subject: [PATCH 0800/1030] Better way to store patch snapshots for recently opened, allow pinning recently opened items --- Resources/Fonts/IconFont.ttf | Bin 61232 -> 60892 bytes Source/Canvas.cpp | 59 ++++++++++----- Source/Canvas.h | 2 +- Source/Constants.h | 5 +- Source/Dialogs/AudioOutputSettings.h | 4 +- Source/NVGSurface.cpp | 4 +- Source/PluginEditor.cpp | 11 ++- Source/Statusbar.cpp | 10 --- Source/Tabbar/WelcomePanel.h | 108 ++++++++++++++++++--------- Source/Utility/Autosave.h | 37 +-------- Source/Utility/SettingsFile.cpp | 10 +-- 11 files changed, 137 insertions(+), 113 deletions(-) diff --git a/Resources/Fonts/IconFont.ttf b/Resources/Fonts/IconFont.ttf index 948f388dec2c1de412b8f451a00a14080f8af95a..12c8b97957da0ed6297acdaa3b22414459dfa795 100644 GIT binary patch delta 846 zcmdmRkNM7RW(fvH1_lORh6V;^h5$FW5Z^_TjI|65Zx2kAu=9Jn11QIlo>Q6T+wy-k zkZHldaDPTdYGR5yOT#^&+#Mh`%K!@SyyM_zU|Mc&ODZBRt)2kn1NAez;C;a7$2W)X5q}JS z6aNbSCjtTjZUSorUI}Umz7SFq3KFUlIwheObb^Qd3YsM0v2S)zGQOHFH=cASon&K+Gl zeJA~C`ls~28H5?i85S9N7|R$(7*8@$G08L8W;)GG$!vwWlldcyFl!C#DK>7lTDF_) zyzF}%bR5n&Dma!nZgKqLRN(Z^t;x6fS_YI$97oQEjaef*8TLOLs>I8NLz6shC zyeDKzXhB#>cuPb`WJu(uD6goBsB_Uw(N57#(XV2fVjJRQ;=1A$;wL2tB=jYkBylCR zBwHlkOQ}g~%XpSqmvt!nQ%+m1Ql3WMm;8$SGn*|q=8DxbF}VEu!oHU^i$R9L3K+9? zj7D-yB5dq@j0|jsY)a~kdWXTM)WVAdq7a<{tJ8D{h1CVC$CPOl+_?AFDuF^ZJ}n;;ghmD z*~*0J089NP4}bsuJwRm;jiOAS-S=GF*BRvR;rI7$_QcfH$;pjkq8!qeY9>}dQ@lG& g{yG4Y6~fW@oQ&65EFi1JaoD}tPVxO&W?=FJ06x|c-T(jq delta 1206 zcmXX`eM}Q)7=PcREtdvLTdrR~3%x6MEuU@g+9F@_H86;Xl~sg}kMTJM1Ccd~SvrhP zw#poYI9!-9#wCU=*&@rLxaf!pA&f1A8M17QEhckY)&MceOf=^nihtbuJip)bd!P6B z+`YNAU!%TSkJ2NI5Q;_bq9{~TUS50PYJ3HU&?YseFRb2-fg0J_bFw4*VN468ISBm` z?rLvoW5xLqsI9@{?gAoag9I(609(3x2T#a;0PMW3|^bDU>%m8 zgQ{<)i1=6%yb*IImW-W_4J#@YA1I#2mB*b|%9I&Ouku6Xta3}`Q%$JutH;&L>gVy+ z_`&!^N=sd&zSB50Q<`mUK)a#K)!o#I`f~l0fiUbdj2nJVm`t=J)hA6SZ5w^YapStl zYWgKPC3!kIoHCH|t$DyaODpMI`UazA27=5(i`z1pT9ta0m9S;(8FrPka%J2%)*+k4 z7DzLtjifE7H)oI;+nMiW{+%7R7ug5xR~*Q(;7oEp;^zfI$QOQe>0Qn4BKMNV>j~$a z--Gg2@(c5~3ho#B3m+G`i&l#R#fv4TlE#wDr3s~z6+-3m{(&k})#<9%jRW=7pah*0pGFT$ zA0Z_&BPS|_&qA{JuDqgx33F4(xUa$o5Cp$tBN`eJ66uB8IJj!Q7j@CovTF1tTu`T0M>#Z9v``EbW;vk%+g$?7u?|LoQ&^6nWkw=0v0dO? zP;z_h&OGdK!Ni>40O;h+u5_G*Io9QYH?nI04%6zK4!#h3Sk5lEIN12IL}|g8Cm8W2 z`6-c=I@?Z@y1F4-Zu(qEE|ci+g+xxm=;c^ErlZc@B?+4mRQMF% z8*%S_5F5l}FU9zR0mL$5M{OmJdld$9xII%!q#Hrt&GXbq2%D^$F~}=?mFDZ+!J(s} zWUI|&wAmDj(Is+}Ng#2eRdSmk?~JHJM%#{o5aB%~A;xaq8hgh_0B1A4!Z1YWdy=?K zC&~>r$tXE@R*j(%z)getValueTree().getChildWithName("RecentlyOpened"); + for (int i = 0; i < recentlyOpenedTree.getNumChildren(); i++) { + auto recentlyOpenedFile = File(recentlyOpenedTree.getChild(i).getProperty("Path").toString()); + // Check if patch is in the recently opened list + if(File(recentlyOpenedFile) == patchFile) + { + // If so, generate an svg sihouette that we can show on the welcome page + String svgSilhouette; + + auto regionOfInterest = Rectangle(); + for (auto* object : objects) { + regionOfInterest = regionOfInterest.getUnion(object->getBounds().reduced(Object::margin)); + } + + for (auto* object : objects) + { + auto rect = object->getBounds().reduced(Object::margin) - regionOfInterest.getPosition(); + svgSilhouette += String::formatted( + "\n", + rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight(), Corners::objectCornerRadius, Corners::objectCornerRadius); + } + svgSilhouette = "\n" + svgSilhouette + ""; + + recentlyOpenedTree.getChild(i).setProperty("Snapshot", svgSilhouette, nullptr); + break; + } + } + } +} + + void Canvas::renderAllObjects(NVGcontext* nvg, Rectangle area) { for(auto* obj : objects) @@ -2193,23 +2232,3 @@ void Canvas::resized() connectionLayer.setBounds(getLocalBounds()); objectLayer.setBounds(getLocalBounds()); } - -String Canvas::generateSilhouetteSVG() -{ - String svgContent; - - auto regionOfInterest = Rectangle(); - for (auto* object : objects) { - regionOfInterest = regionOfInterest.getUnion(object->getBounds().reduced(Object::margin)); - } - - for (auto* object : objects) - { - auto rect = object->getBounds().reduced(Object::margin) - regionOfInterest.getPosition(); - svgContent += String::formatted( - "\n", - rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight(), Corners::objectCornerRadius, Corners::objectCornerRadius); - } - - return "\n" + svgContent + ""; -} diff --git a/Source/Canvas.h b/Source/Canvas.h index 263e6de36b..36ad4529b8 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -133,7 +133,7 @@ class Canvas : public Component void jumpToOrigin(); void zoomToFitAll(); - String generateSilhouetteSVG(); + void updatePatchSnapshot(); float getRenderScale() const; diff --git a/Source/Constants.h b/Source/Constants.h index 3ec173cbef..66cc0ab3c7 100644 --- a/Source/Constants.h +++ b/Source/Constants.h @@ -80,8 +80,9 @@ struct Icons { inline static String const Trash = "~"; inline static String const CanvasSettings = "&"; inline static String const Eyedropper = "@"; - inline static String const Debug = "?"; - + inline static String const HeartFilled = "?"; + inline static String const HeartStroked = ">"; + inline static String const Reset = "'"; inline static String const More = "."; inline static String const MIDI = "`"; diff --git a/Source/Dialogs/AudioOutputSettings.h b/Source/Dialogs/AudioOutputSettings.h index e772203b66..d70e9ea3e0 100644 --- a/Source/Dialogs/AudioOutputSettings.h +++ b/Source/Dialogs/AudioOutputSettings.h @@ -173,7 +173,9 @@ class AudioOutputSettings : public Component { }; public: - AudioOutputSettings(PluginProcessor* pd) : oversampleSettings(SettingsFile::getInstance()->getProperty("oversampling")), limiterSettings(SettingsFile::getInstance()->getProperty("limiter_threshold")) + AudioOutputSettings(PluginProcessor* pd) + : limiterSettings(SettingsFile::getInstance()->getProperty("limiter_threshold")) + , oversampleSettings(SettingsFile::getInstance()->getProperty("oversampling")) { addAndMakeVisible(limiterSettings); limiterSettings.onChange = [pd](int value){ diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 5115bfe3a7..ec99a0898b 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -331,12 +331,12 @@ void NVGSurface::render() // Manage showing/hiding welcome panel if(hasCanvas && editor->welcomePanel->isVisible()) { - editor->welcomePanel->setVisible(false); + editor->welcomePanel->hide(); editor->resized(); updateBufferSize(); } else if(!hasCanvas && !editor->welcomePanel->isVisible()) { - editor->welcomePanel->setVisible(true); + editor->welcomePanel->show(); editor->resized(); updateBufferSize(); } diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 4f01b6f5fe..ae30c31c7f 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -53,14 +53,14 @@ PluginEditor::PluginEditor(PluginProcessor& p) , nvgSurface(this) , pluginConstrainer(*getConstrainer()) , autosave(std::make_unique(pd)) - , touchSelectionHelper(std::make_unique(this)) , tooltipWindow(nullptr, [](Component* c) { if (auto* cnv = c->findParentComponentOfClass()) { return !getValue(cnv->locked); } - + return true; }) + , touchSelectionHelper(std::make_unique(this)) { #if JUCE_IOS //constrainer.setMinimumSize(100, 100); @@ -729,8 +729,10 @@ void PluginEditor::saveProjectAs(std::function const& nestedCallback) result.deleteFile(); if(!result.hasFileExtension("pd")) result = result.getFullPathName() + ".pd"; - - getCurrentCanvas()->patch.savePatch(resultURL); + + auto* cnv = getCurrentCanvas(); + cnv->updatePatchSnapshot(); + cnv->patch.savePatch(resultURL); SettingsFile::getInstance()->addToRecentlyOpened(result); pd->titleChanged(); } @@ -757,6 +759,7 @@ void PluginEditor::saveProject(std::function const& nestedCallback) } if (cnv->patch.getCurrentFile().existsAsFile()) { + cnv->updatePatchSnapshot(); cnv->patch.savePatch(); SettingsFile::getInstance()->addToRecentlyOpened(cnv->patch.getCurrentFile()); nestedCallback(); diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index f7acd284bc..9938c4b4f6 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -719,16 +719,6 @@ Statusbar::Statusbar(PluginProcessor* processor) addAndMakeVisible(centreButton); - /* - protectButton.setTooltip("Clip output signal and filter non-finite values"); - protectButton.setClickingTogglesState(true); - protectButton.setToggleState(SettingsFile::getInstance()->getProperty("protected"), dontSendNotification); - protectButton.onClick = [this]() { - int state = protectButton.getToggleState(); - pd->setProtectedMode(state); - SettingsFile::getInstance()->setProperty("protected", state); - }; */ - volumeSlider->setRange(0.0f, 1.0f); volumeSlider->setValue(0.8f); volumeSlider->setDoubleClickReturnValue(true, 0.8f); diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Tabbar/WelcomePanel.h index 608380212d..897b38e56d 100644 --- a/Source/Tabbar/WelcomePanel.h +++ b/Source/Tabbar/WelcomePanel.h @@ -8,7 +8,7 @@ #include "Utility/Autosave.h" #include "Utility/CachedTextRender.h" -class WelcomePanel : public Component, public NVGComponent { +class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater { class WelcomePanelTile : public Component { @@ -16,14 +16,15 @@ class WelcomePanel : public Component, public NVGComponent { bool isHovered = false; String tileName; std::unique_ptr snapshot = nullptr; - CachedTextRender titleRenderer, subtitleRenderer; - public: + public: + bool isFavourited; std::function onClick = [](){}; + std::function onFavourite = nullptr; - WelcomePanelTile(String name, String subtitle, String svgImage, Colour iconColour, float scale) - : snapshotScale(scale), tileName(name) + WelcomePanelTile(String name, String subtitle, String svgImage, Colour iconColour, float scale, bool favourited) + : snapshotScale(scale), tileName(name), isFavourited(favourited) { snapshot = Drawable::createFromImageData(svgImage.toRawUTF8(), svgImage.getNumBytesAsUTF8()); if(snapshot) { @@ -63,7 +64,6 @@ class WelcomePanel : public Component, public NVGComponent { g.strokePath(tilePath, PathStrokeType(1.0f)); auto* nvg = dynamic_cast(g.getInternalContext()).getContext(); - nvgSave(nvg); nvgTranslate(nvg, 0, bounds.getHeight() - 30); titleRenderer.renderText(nvg, Rectangle(bounds.getX() + 10, 0, bounds.getWidth() - 8, 24), 2.0f); @@ -71,12 +71,26 @@ class WelcomePanel : public Component, public NVGComponent { subtitleRenderer.renderText(nvg, Rectangle(bounds.getX() + 10, 0, bounds.getWidth() - 8, 16), 2.0f); nvgRestore(nvg); - - //Fonts::drawStyledText(g, tileName, bounds.getX() + 10, bounds.getHeight() - 30, bounds.getWidth() - 8, 24, findColour(PlugDataColour::panelTextColourId), Semibold, 14); - /* - g.setColour(findColour(PlugDataColour::panelTextColourId).withAlpha(0.75f)); - g.setFont(Fonts::getCurrentFont().withHeight(13.5f)); - g.drawText(tileSubtitle, , Justification::centredLeft); */ + if(onFavourite) + { + auto favouriteIconBounds = getHeartIconBounds(); + nvgFontFace(nvg, "Icon"); + + if(isFavourited) { + nvgFillColor(nvg, nvgRGB(250, 50, 40)); + nvgText(nvg, favouriteIconBounds.getX(), favouriteIconBounds.getY() + 14, Icons::HeartFilled.toRawUTF8(), nullptr); + } + else if(isMouseOver()) + { + nvgFillColor(nvg, NVGComponent::convertColour(findColour(PlugDataColour::panelTextColourId))); + nvgText(nvg, favouriteIconBounds.getX(), favouriteIconBounds.getY() + 14, Icons::HeartStroked.toRawUTF8(), nullptr); + } + } + } + + Rectangle getHeartIconBounds() + { + return Rectangle(20, getHeight() - 76, 16, 16); } void mouseEnter(const MouseEvent& e) override @@ -93,7 +107,15 @@ class WelcomePanel : public Component, public NVGComponent { void mouseUp(const MouseEvent& e) override { - onClick(); + if(onFavourite && getHeartIconBounds().contains(e.x, e.y)) + { + isFavourited = !isFavourited; + onFavourite(isFavourited); + repaint(); + } + else { + onClick(); + } } void resized() override @@ -112,13 +134,9 @@ class WelcomePanel : public Component, public NVGComponent { recentlyOpenedViewport.setScrollBarsShown(true, false, false, false); recentlyOpenedComponent.setVisible(true); addAndMakeVisible(recentlyOpenedViewport); - - // Update async to make sure silhouettes are available - MessageManager::callAsync([_this = SafePointer(this)](){ - _this->update(); - }); - + setCachedComponentImage(new NVGSurface::InvalidationListener(editor->nvgSurface, this)); + triggerAsyncUpdate(); } void resized() override @@ -141,6 +159,7 @@ class WelcomePanel : public Component, public NVGComponent { bounds.removeFromTop(16); + auto viewPos = recentlyOpenedViewport.getViewPosition(); recentlyOpenedViewport.setBounds(getLocalBounds().withTrimmedTop(260)); int numRows = (tiles.size() + numColumns - 1) / numColumns; @@ -153,18 +172,32 @@ class WelcomePanel : public Component, public NVGComponent { rowBounds = scrollable.removeFromTop(160); for (auto* tile : tiles) { - if (rowBounds.getWidth() < actualTileWidth) { - rowBounds = scrollable.removeFromTop(160); + if(tile->isFavourited) { + if (rowBounds.getWidth() < actualTileWidth) { + rowBounds = scrollable.removeFromTop(160); + } + tile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); + rowBounds.removeFromLeft(tileSpacing); + } + } + + for (auto* tile : tiles) + { + if(!tile->isFavourited) { + if (rowBounds.getWidth() < actualTileWidth) { + rowBounds = scrollable.removeFromTop(160); + } + tile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); + rowBounds.removeFromLeft(tileSpacing); } - tile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); - rowBounds.removeFromLeft(tileSpacing); } + recentlyOpenedViewport.setViewPosition(viewPos); } - void update() + void handleAsyncUpdate() override { - newPatchTile = std::make_unique("New Patch", "Create a new empty patch", newIcon, findColour(PlugDataColour::panelTextColourId), 0.33f); - openPatchTile = std::make_unique("Open Patch", "Browse for a patch to open", openIcon, findColour(PlugDataColour::panelTextColourId), 0.33f); + newPatchTile = std::make_unique("New Patch", "Create a new empty patch", newIcon, findColour(PlugDataColour::panelTextColourId), 0.33f, false); + openPatchTile = std::make_unique("Open Patch", "Browse for a patch to open", openIcon, findColour(PlugDataColour::panelTextColourId), 0.33f, false); newPatchTile->onClick = [this]() { editor->newProject(); }; openPatchTile->onClick = [this](){ editor->openProject(); }; @@ -177,19 +210,21 @@ class WelcomePanel : public Component, public NVGComponent { auto settingsTree = SettingsFile::getInstance()->getValueTree(); auto recentlyOpenedTree = settingsTree.getChildWithName("RecentlyOpened"); - auto snapshotColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.3f); - if (recentlyOpenedTree.isValid()) { + // Place favourited patches at the top for (int i = 0; i < recentlyOpenedTree.getNumChildren(); i++) { - auto patchFile = File(recentlyOpenedTree.getChild(i).getProperty("Path").toString()); - auto silhoutteSvg = Autosave::findPatchSilhouette(patchFile); + auto subTree = recentlyOpenedTree.getChild(i); + auto patchFile = File(subTree.getProperty("Path").toString()); + auto silhoutteSvg = subTree.getProperty("Snapshot").toString(); + auto favourited = subTree.hasProperty("Pinned") && static_cast(subTree.getProperty("Pinned")); + auto snapshotColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.3f); if(silhoutteSvg.isEmpty() && patchFile.existsAsFile()) { silhoutteSvg = OfflineObjectRenderer::patchToSVGFast(patchFile.loadFileAsString()); } - auto openTime = Time(static_cast(recentlyOpenedTree.getChild(i).getProperty("Time"))); + auto openTime = Time(static_cast(subTree.getProperty("Time"))); auto diff = Time::getCurrentTime() - openTime; String date; if(diff.inDays() == 0) date = "Today"; @@ -198,7 +233,7 @@ class WelcomePanel : public Component, public NVGComponent { String time = openTime.toString(false, true, false, true); String timeDescription = date + ", " + time; - auto* tile = tiles.add(new WelcomePanelTile(patchFile.getFileName(), timeDescription, silhoutteSvg, snapshotColour, 1.0f)); + auto* tile = tiles.add(new WelcomePanelTile(patchFile.getFileName(), timeDescription, silhoutteSvg, snapshotColour, 1.0f, favourited)); tile->onClick = [this, patchFile]() mutable { editor->autosave->checkForMoreRecentAutosave(patchFile, [this, patchFile]() { editor->pd->loadPatch(URL(patchFile), editor); @@ -206,6 +241,10 @@ class WelcomePanel : public Component, public NVGComponent { editor->pd->titleChanged(); }); }; + tile->onFavourite = [this, subTree](bool shouldBeFavourite) mutable { + subTree.setProperty("Pinned", shouldBeFavourite, nullptr); + resized(); + }; recentlyOpenedComponent.addAndMakeVisible(tile); } } @@ -213,9 +252,10 @@ class WelcomePanel : public Component, public NVGComponent { resized(); } + void show() { - update(); + triggerAsyncUpdate(); setVisible(true); } @@ -258,7 +298,7 @@ class WelcomePanel : public Component, public NVGComponent { void lookAndFeelChanged() override { - update(); + triggerAsyncUpdate(); } static inline const String newIcon = "\n" diff --git a/Source/Utility/Autosave.h b/Source/Utility/Autosave.h index 3085baeac9..a0c20fc030 100644 --- a/Source/Utility/Autosave.h +++ b/Source/Utility/Autosave.h @@ -12,7 +12,7 @@ class Autosave : public Timer Value autosaveEnabled; PluginProcessor* pd; - moodycamel::ReaderWriterQueue> autoSaveQueue; + moodycamel::ReaderWriterQueue> autoSaveQueue; public: Autosave(PluginProcessor* procesor) @@ -66,18 +66,6 @@ class Autosave : public Timer callback(); } } - - static String findPatchSilhouette(File path) - { - auto entry = autoSaveTree.getChildWithProperty("Path", path.getFullPathName()); - if (entry.isValid() && entry.hasProperty("Snapshot")) { - MemoryOutputStream ostream; - Base64::convertFromBase64(ostream, entry.getProperty("Snapshot").toString()); - return String::fromUTF8(static_cast(ostream.getData()), ostream.getDataSize()); - } - - return {}; - } private: void valueChanged(Value& v) override @@ -116,7 +104,7 @@ class Autosave : public Timer // Simple way to filter out plugdata default patches which we don't want to save. if (!isInternalPatch(patchFile)) { - autoSaveQueue.enqueue({ patchFile.getFullPathName(), patch->getCanvasContent(), x}); + autoSaveQueue.enqueue({ patchFile.getFullPathName(), patch->getCanvasContent()}); } triggerAsyncUpdate(); @@ -136,24 +124,9 @@ class Autosave : public Timer void handleAsyncUpdate() override { - std::tuple pathAndContent; + std::pair pathAndContent; while (autoSaveQueue.try_dequeue(pathAndContent)) { - auto& [path, content, ptr] = pathAndContent; - - String patchSnapshot; - for(auto* editor : pd->getEditors()) - { - for(auto* canvas : editor->canvases) - { - auto& patch = canvas->patch; - if(patch.getPointer().get() == ptr) - { - - patchSnapshot = Base64::toBase64(canvas->generateSilhouetteSVG()); - break; - } - } - } + auto& [path, content] = pathAndContent; // Make sure we get current time in the correct format used by the OS for file modification time auto tempFile = File::createTempFile("temp_time_test"); @@ -166,13 +139,11 @@ class Autosave : public Timer if (existingPatch.isValid()) { existingPatch.setProperty("Patch", Base64::toBase64(content), nullptr); existingPatch.setProperty("LastModified", (int64)time, nullptr); - existingPatch.setProperty("Snapshot", patchSnapshot, nullptr); } else { ValueTree newAutoSave = ValueTree("Save"); newAutoSave.setProperty("Path", path, nullptr); newAutoSave.setProperty("Patch", Base64::toBase64(content), nullptr); newAutoSave.setProperty("LastModified", (int64)time, nullptr); - newAutoSave.setProperty("Snapshot", patchSnapshot, nullptr); autoSaveTree.addChild(newAutoSave, 0, nullptr); if (autoSaveTree.getNumChildren() > 15) { diff --git a/Source/Utility/SettingsFile.cpp b/Source/Utility/SettingsFile.cpp index 1639064a49..5c9b67ae9d 100644 --- a/Source/Utility/SettingsFile.cpp +++ b/Source/Utility/SettingsFile.cpp @@ -179,10 +179,6 @@ void SettingsFile::initialisePathsTree() break; } } - - - - } void SettingsFile::addToRecentlyOpened(File const& path) @@ -213,8 +209,10 @@ void SettingsFile::addToRecentlyOpened(File const& path) // Find oldest entry for (int i = 0; i < recentlyOpened.getNumChildren(); i++) { - auto time = static_cast(recentlyOpened.getChild(i).getProperty("Time")); - if (time < minTime) { + auto child = recentlyOpened.getChild(i); + auto pinned = child.hasProperty("Pinned") && static_cast(child.getProperty("Pinned")); + auto time = static_cast(child.getProperty("Time")); + if (time < minTime && !pinned) { minIdx = i; minTime = time; } From db5df9c85155588948a81f66de84d17934f9740d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 29 May 2024 02:08:18 +0200 Subject: [PATCH 0801/1030] Small fixes for welcome panel pinning --- Source/Tabbar/WelcomePanel.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Tabbar/WelcomePanel.h index 897b38e56d..133eced378 100644 --- a/Source/Tabbar/WelcomePanel.h +++ b/Source/Tabbar/WelcomePanel.h @@ -90,7 +90,7 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater Rectangle getHeartIconBounds() { - return Rectangle(20, getHeight() - 76, 16, 16); + return Rectangle(20, getHeight() - 80, 16, 16); } void mouseEnter(const MouseEvent& e) override @@ -241,7 +241,13 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater editor->pd->titleChanged(); }); }; - tile->onFavourite = [this, subTree](bool shouldBeFavourite) mutable { + tile->onFavourite = [this, path = subTree.getProperty("Path")](bool shouldBeFavourite) mutable { + auto settingsTree = SettingsFile::getInstance()->getValueTree(); + auto recentlyOpenedTree = settingsTree.getChildWithName("RecentlyOpened"); + + // Settings file could be reloaded, we can't assume the old recently opened tree is still valid! + // So look up the entry by file path + auto subTree = recentlyOpenedTree.getChildWithProperty("Path", path); subTree.setProperty("Pinned", shouldBeFavourite, nullptr); resized(); }; From 6f7e653e18801a7cb033d39a142995ac1d718881 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 29 May 2024 02:32:39 +0200 Subject: [PATCH 0802/1030] Limiter fix, welcome panel improvement --- Source/Tabbar/WelcomePanel.h | 2 +- Source/Utility/Limiter.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Tabbar/WelcomePanel.h index 133eced378..9a17f84c44 100644 --- a/Source/Tabbar/WelcomePanel.h +++ b/Source/Tabbar/WelcomePanel.h @@ -77,7 +77,7 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater nvgFontFace(nvg, "Icon"); if(isFavourited) { - nvgFillColor(nvg, nvgRGB(250, 50, 40)); + nvgFillColor(nvg, nvgRGBA(250, 50, 40, 200)); nvgText(nvg, favouriteIconBounds.getX(), favouriteIconBounds.getY() + 14, Icons::HeartFilled.toRawUTF8(), nullptr); } else if(isMouseOver()) diff --git a/Source/Utility/Limiter.h b/Source/Utility/Limiter.h index 9508d9364c..52f41457a3 100644 --- a/Source/Utility/Limiter.h +++ b/Source/Utility/Limiter.h @@ -16,7 +16,9 @@ class Limiter { secondStageCompressor.process(dsp::ProcessContextReplacing(block)); for (size_t channel = 0; channel < block.getNumChannels(); ++channel) { - FloatVectorOperations::clip(block.getChannelPointer(channel), block.getChannelPointer(channel), -1.0f, 1.0f, block.getNumSamples()); + // Clip if limter goes far out of bounds + // We'd rather not do hard clipping, but it's for the better when things get really loud + FloatVectorOperations::clip(block.getChannelPointer(channel), block.getChannelPointer(channel), -std::sqrt(2.0f), std::sqrt(2.0f), block.getNumSamples()); } } From e18084ccede5cac50a003f4de9189e8a57f5ec56 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 29 May 2024 02:38:14 +0200 Subject: [PATCH 0803/1030] Arch Linux build fix --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 0927261426..74158fc8d1 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -297,7 +297,7 @@ jobs: - name: Install Dependencies (pacman) if: ${{ matrix.pacman == 'pacman' }} - run: pacman -Sy && pacman -S --noconfirm cmake wget bzip2 git alsa-lib freetype2 libx11 libxcursor libxi libxext libxinerama libxrandr libxrender webkit2gtk cmake make gcc pkgconf python python-pip curl ccache freeglut mesa glfw-x11 glew jack2 && pacman --noconfirm -Syu + run: pacman -Sy && pacman -S --noconfirm cmake wget bzip2 git alsa-lib freetype2 libx11 libxcursor libxi libxext libxinerama libxrandr libxrender webkit2gtk cmake make gcc pkgconf python python-pip curl ccache freeglut mesa glfw-x11 glew jack2 openssl && pacman --noconfirm -Syu - uses: actions/checkout@v3 with: From 15e916da6382c05eaa310a43d0c865856f3ca0f2 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 29 May 2024 16:51:36 +0930 Subject: [PATCH 0804/1030] improve presentation mode with virtual plugin view improve DnD fastSVG parsing --- Source/Canvas.cpp | 54 ++++++++++++++++++++++-- Source/Canvas.h | 3 ++ Source/Utility/OfflineObjectRenderer.cpp | 41 ++++++++++++++++++ 3 files changed, 94 insertions(+), 4 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 61fe7c53a3..8744f839b6 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -357,7 +357,7 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) } } - if(hasViewport && (showOrigin || showBorder)) { + if(hasViewport && (showOrigin || showBorder) && !::getValue(presentationMode)) { nvgBeginPath(nvg); auto borderWidth = getValue(patchWidth); @@ -418,11 +418,57 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) } } - // if canvas is a graph, or in presentation mode, don't render connections at all - if (::getValue(presentationMode) || isGraph) + + if (::getValue(presentationMode) || isGraph) { renderAllObjects(nvg, invalidRegion); + // render presentation mode as clipped 'virtual' plugin view + if (::getValue(presentationMode) && !editor->pluginMode) { + auto borderWidth = getValue(patchWidth); + auto borderHeight = getValue(patchHeight); + auto pos = Point(halfSize, halfSize); + + nvgSave(nvg); + + // background colour to crop outside of border area + nvgBeginPath(nvg); + nvgFillColor(nvg, convertColour(findColour(PlugDataColour::levelMeterBackgroundColourId))); + nvgRect(nvg, 0, 0, infiniteCanvasSize, infiniteCanvasSize); + + nvgPathWinding(nvg, NVG_HOLE); + nvgRoundedRect(nvg, pos.getX(), pos.getY(), borderWidth, borderHeight, Corners::windowCornerRadius); + nvgFillColor(nvg, convertColour(findColour(PlugDataColour::levelMeterBackgroundColourId))); + nvgFill(nvg); + + // background drop shadow to simulate a virtual plugin + nvgBeginPath(nvg); + nvgFillColor(nvg, convertColour(findColour(PlugDataColour::levelMeterBackgroundColourId))); + nvgRect(nvg, 0, 0, infiniteCanvasSize, infiniteCanvasSize); + + nvgPathWinding(nvg, NVG_HOLE); + nvgRoundedRect(nvg, pos.getX(), pos.getY(), borderWidth, borderHeight, Corners::windowCornerRadius); + + const int shadowSize = 40; + auto borderArea = Rectangle(0, 0, borderWidth, borderHeight); + auto expanededBorder = borderArea.expanded(shadowSize); + if (lastPresentationBounds != borderArea || presentationShadowImage == -1) { + lastPresentationBounds = borderArea; + Image shadow(Image::ARGB, expanededBorder.getWidth(), expanededBorder.getHeight(), true); + Graphics g(shadow); + auto shadowPath = Path(); + shadowPath.addRoundedRectangle(expanededBorder.reduced(shadowSize).withPosition(shadowSize, shadowSize), Corners::windowCornerRadius); + StackShadow::renderDropShadow(g, shadowPath, Colours::black, shadowSize); + presentationShadowImage = NVGImageRenderer::convertImage(nvg, shadow); + + } + auto shadowImage = nvgImagePattern(nvg, pos.getX() - shadowSize, pos.getY() - shadowSize, expanededBorder.getWidth(), expanededBorder.getHeight(), 0, presentationShadowImage, 0.33f); + nvgFillPaint(nvg, shadowImage); + nvgFill(nvg); + + nvgRestore(nvg); + } + } + // render connections infront or behind objects depending on lock mode or overlay setting else { - // render connections infront or behind objects depending on lock mode or overlay setting if (connectionsBehind) { renderAllConnections(nvg, invalidRegion); renderAllObjects(nvg, invalidRegion); diff --git a/Source/Canvas.h b/Source/Canvas.h index 36ad4529b8..5d7e41b628 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -246,6 +246,9 @@ class Canvas : public Component NVGframebuffer* ioletBuffer = nullptr; int resizeHandleImage = 0; float bufferScale; + + int presentationShadowImage = -1; + Rectangle lastPresentationBounds; Array> drawables; diff --git a/Source/Utility/OfflineObjectRenderer.cpp b/Source/Utility/OfflineObjectRenderer.cpp index 6b4fafa7eb..53c737836c 100644 --- a/Source/Utility/OfflineObjectRenderer.cpp +++ b/Source/Utility/OfflineObjectRenderer.cpp @@ -134,11 +134,52 @@ String OfflineObjectRenderer::patchToSVGFast(String const& patch) for(auto& object : objects) { auto tokens = StringArray::fromTokens(object, true); + if((tokens[1] == "floatatom" || tokens[1] == "symbolatom" || tokens[1] == "listatom") && tokens.size() > 11) { objectBounds.add(Rectangle(tokens[2].getIntValue(), tokens[3].getIntValue(), tokens[4].getIntValue() * 8, tokens[11].getIntValue())); continue; } + + if(tokens[1] == "text") { + auto fontMetrics = Fonts::getCurrentFont().withHeight(15); + StringArray textString; + textString.addArray(tokens, 4, tokens.size() - 2 - 4); + + int textAreaWidth = 0; + int lines = 1; + + // calcuate the length of the text string: + // if char number is specified, then use that + // if it's not, then it's auto sizing, which is max of 92 chars, or min of the text length + if(tokens[tokens.size() - 2] == "f") { + textAreaWidth = tokens[tokens.size() - 1].getIntValue() * 8; + } + else { + int autoWidth = 0; + for (auto text : textString) { + autoWidth += fontMetrics.getStringWidth(text + " "); + } + textAreaWidth = jmin(92 * 8, autoWidth); + } + + int wordsInLine = 1; + int lineWidth = 0; + int wordIdx = 0; + while (wordIdx < textString.size()) { + lineWidth += fontMetrics.getStringWidth(textString[wordIdx] + " "); + if (lineWidth > textAreaWidth) { + if (wordsInLine == 1) { + break; + } + lines++; + } + wordIdx++; + wordsInLine++; + } + objectBounds.add(Rectangle(tokens[2].getIntValue(), tokens[3].getIntValue(), textAreaWidth, lines * 12)); + continue; + } switch(hash(tokens[4])){ case hash("restore"): { From ff2b033d1023a6e739c879a018422053cf8cdf3e Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 29 May 2024 17:19:14 +0930 Subject: [PATCH 0805/1030] add theme setting for presentation mode background colour id --- Source/Canvas.cpp | 8 +++++--- Source/Constants.h | 2 ++ Source/LookAndFeel.cpp | 14 +++++++------- Source/LookAndFeel.h | 2 ++ 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 8744f839b6..e10b52a2d1 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -427,21 +427,23 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) auto borderHeight = getValue(patchHeight); auto pos = Point(halfSize, halfSize); + auto bgColour = convertColour(findColour(PlugDataColour::presentationBackgroundColourId)); + nvgSave(nvg); // background colour to crop outside of border area nvgBeginPath(nvg); - nvgFillColor(nvg, convertColour(findColour(PlugDataColour::levelMeterBackgroundColourId))); + nvgFillColor(nvg, bgColour); nvgRect(nvg, 0, 0, infiniteCanvasSize, infiniteCanvasSize); nvgPathWinding(nvg, NVG_HOLE); nvgRoundedRect(nvg, pos.getX(), pos.getY(), borderWidth, borderHeight, Corners::windowCornerRadius); - nvgFillColor(nvg, convertColour(findColour(PlugDataColour::levelMeterBackgroundColourId))); + nvgFillColor(nvg, bgColour); nvgFill(nvg); // background drop shadow to simulate a virtual plugin nvgBeginPath(nvg); - nvgFillColor(nvg, convertColour(findColour(PlugDataColour::levelMeterBackgroundColourId))); + nvgFillColor(nvg, bgColour); nvgRect(nvg, 0, 0, infiniteCanvasSize, infiniteCanvasSize); nvgPathWinding(nvg, NVG_HOLE); diff --git a/Source/Constants.h b/Source/Constants.h index 66cc0ab3c7..1eb40d3b00 100644 --- a/Source/Constants.h +++ b/Source/Constants.h @@ -258,6 +258,8 @@ enum PlugDataColour { canvasTextColourId, canvasDotsColourId, + presentationBackgroundColourId, + guiObjectBackgroundColourId, guiObjectInternalOutlineColour, textObjectBackgroundColourId, diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index 60be50a9c7..c5da6246f3 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -1487,7 +1487,7 @@ const String PlugDataLook::defaultThemesXml = " " " > const PlugDa { canvasTextColourId, { "Canvas text", "canvas_text", "Canvas" } }, { canvasDotsColourId, { "Canvas dots colour", "canvas_dots", "Canvas" } }, + { presentationBackgroundColourId, { "Presentation background", "presentation_background", "Canvas" } }, + { guiObjectBackgroundColourId, { "GUI object background", "default_object_background", "Object" } }, { guiObjectInternalOutlineColour, { "GUI Object internal outline colour", "gui_internal_outline_colour", "Object" } }, { textObjectBackgroundColourId, { "Object background", "text_object_background", "Object" } }, From f9b62effbe74eb46ac4c1cde553e8ff9f9e4a04d Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 29 May 2024 17:36:19 +0930 Subject: [PATCH 0806/1030] show current action for limiter button in tooltip --- Source/Statusbar.cpp | 5 +++++ Source/Statusbar.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 9938c4b4f6..565986fbe6 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -806,6 +806,11 @@ Statusbar::Statusbar(PluginProcessor* processor) limiterButton.setColour(TextButton::buttonOnColourId, findColour(PlugDataColour::levelMeterThumbColourId).withAlpha(0.3f)); limiterButton.setClickingTogglesState(true); limiterButton.setToggleState(SettingsFile::getInstance()->getProperty("protected"), dontSendNotification); + + limiterButton.onStateChange = [this](){ + limiterButton.setTooltip(limiterButton.getToggleState() ? "Turn off limiter" : "Turn on limiter"); + }; + limiterButton.onClick = [this](){ auto state = limiterButton.getToggleState(); pd->setProtectedMode(state); diff --git a/Source/Statusbar.h b/Source/Statusbar.h index 2807cdbc54..24138c6bf2 100644 --- a/Source/Statusbar.h +++ b/Source/Statusbar.h @@ -103,7 +103,7 @@ class Statusbar : public Component SmallIconButton snapEnableButton, snapSettingsButton; SmallIconButton powerButton, audioSettingsButton; - TextButton limiterButton = TextButton("Limit", "Enable limiter"); + TextButton limiterButton = TextButton("Limit"); std::unique_ptr latencyDisplayButton; From 5809db5818ad81fcc4b8f10d181db335e3edfc2d Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 29 May 2024 18:04:09 +0930 Subject: [PATCH 0807/1030] swap AlignHCentre/AlignVCentre (not sure why it's inverted behaviour, and use clearer language to describe what they do --- Source/Dialogs/Dialogs.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index 2a864bf614..071151878a 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -667,15 +667,15 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent }; PopupMenu alignMenu; - alignMenu.addCustomItem(AlignLeft, std::make_unique(Icons::AlignLeft, "Left"), nullptr, "Left"); - alignMenu.addCustomItem(AlignHCentre, std::make_unique(Icons::AlignHCentre, "Centre horizontally"), nullptr, "Centre horizontally"); - alignMenu.addCustomItem(AlignRight, std::make_unique(Icons::AlignRight, "Right"), nullptr, "Right"); - alignMenu.addCustomItem(AlignHDistribute, std::make_unique(Icons::AlignHDistribute, "Distribute horizonally"), nullptr, "Distribute horizonally"); + alignMenu.addCustomItem(AlignLeft, std::make_unique(Icons::AlignLeft, "Align left"), nullptr, "Align left"); + alignMenu.addCustomItem(AlignHCentre, std::make_unique(Icons::AlignVCentre, "Align centre"), nullptr, "Align centre"); + alignMenu.addCustomItem(AlignRight, std::make_unique(Icons::AlignRight, "Align right"), nullptr, "Align right"); + alignMenu.addCustomItem(AlignHDistribute, std::make_unique(Icons::AlignHDistribute, "Space horizonally"), nullptr, "Space horizonally"); alignMenu.addSeparator(); - alignMenu.addCustomItem(AlignTop, std::make_unique(Icons::AlignTop, "Top"), nullptr, "Top"); - alignMenu.addCustomItem(AlignVCentre, std::make_unique(Icons::AlignVCentre, "Centre verticaly"), nullptr, "Centre verticaly"); - alignMenu.addCustomItem(AlignBottom, std::make_unique(Icons::AlignBottom, "Bottom"), nullptr, "Bottom"); - alignMenu.addCustomItem(AlignVDistribute, std::make_unique(Icons::AlignVDistribute, "Distribute vertically"), nullptr, "Distribute vertically"); + alignMenu.addCustomItem(AlignTop, std::make_unique(Icons::AlignTop, "Align top"), nullptr, "Align top"); + alignMenu.addCustomItem(AlignVCentre, std::make_unique(Icons::AlignHCentre, "Align middle"), nullptr, "Align middle"); + alignMenu.addCustomItem(AlignBottom, std::make_unique(Icons::AlignBottom, "Align bottom"), nullptr, "Align bottom"); + alignMenu.addCustomItem(AlignVDistribute, std::make_unique(Icons::AlignVDistribute, "Space vertically"), nullptr, "Space vertically"); popupMenu.addSubMenu("Align", alignMenu, !locked); popupMenu.addSeparator(); From f9c1e87590037e725254b66b2d8a513325eed57a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 29 May 2024 14:31:12 +0200 Subject: [PATCH 0808/1030] Fixed scrollbar positioning, fixed theming bugs --- Source/Canvas.cpp | 13 ++++++++++--- Source/Canvas.h | 3 +++ Source/CanvasViewport.h | 4 ++-- Source/PluginEditor.cpp | 8 ++++---- Source/Statusbar.cpp | 8 ++++++++ Source/Statusbar.h | 4 +++- 6 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index e10b52a2d1..d754f7d412 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -184,7 +184,7 @@ bool Canvas::updateFramebuffers(NVGcontext* nvg, Rectangle invalidRegion, i auto zoom = isScrolling ? 2.0f : getValue(zoomScale); // First, check if we need to update our iolet buffer - if(!ioletBuffer || !approximatelyEqual(zoom, bufferScale)) + if(!ioletBuffer || !approximatelyEqual(zoom, bufferScale) || needsFramebufferUpdate) { int const logicalSize = 16 * 4; int const pixelSize = logicalSize * pixelScale * zoom; @@ -239,7 +239,7 @@ bool Canvas::updateFramebuffers(NVGcontext* nvg, Rectangle invalidRegion, i editor->nvgSurface.invalidateAll(); } - if(!resizeHandleImage || !approximatelyEqual(zoom, bufferScale)) + if(!resizeHandleImage || !approximatelyEqual(zoom, bufferScale) || needsFramebufferUpdate) { int const logicalSize = 9; int const pixelSize = logicalSize * pixelScale * zoom; @@ -287,6 +287,8 @@ bool Canvas::updateFramebuffers(NVGcontext* nvg, Rectangle invalidRegion, i } } + needsFramebufferUpdate = false; + return true; } @@ -460,7 +462,6 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) shadowPath.addRoundedRectangle(expanededBorder.reduced(shadowSize).withPosition(shadowSize, shadowSize), Corners::windowCornerRadius); StackShadow::renderDropShadow(g, shadowPath, Colours::black, shadowSize); presentationShadowImage = NVGImageRenderer::convertImage(nvg, shadow); - } auto shadowImage = nvgImagePattern(nvg, pos.getX() - shadowSize, pos.getY() - shadowSize, expanededBorder.getWidth(), expanededBorder.getHeight(), 0, presentationShadowImage, 0.33f); nvgFillPaint(nvg, shadowImage); @@ -529,6 +530,12 @@ float Canvas::getRenderScale() const } +void Canvas::lookAndFeelChanged() +{ + needsFramebufferUpdate = true; + presentationShadowImage = -1; +} + void Canvas::updatePatchSnapshot() { auto patchFile = patch.getCurrentFile(); diff --git a/Source/Canvas.h b/Source/Canvas.h index 5d7e41b628..62d201e23f 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -137,6 +137,8 @@ class Canvas : public Component float getRenderScale() const; + void lookAndFeelChanged() override; + bool autoscroll(MouseEvent const& e); // Multi-dragger functions @@ -248,6 +250,7 @@ class Canvas : public Component float bufferScale; int presentationShadowImage = -1; + bool needsFramebufferUpdate = false; Rectangle lastPresentationBounds; Array> drawables; diff --git a/Source/CanvasViewport.h b/Source/CanvasViewport.h index 8b0e25a509..6f7a3175db 100644 --- a/Source/CanvasViewport.h +++ b/Source/CanvasViewport.h @@ -397,8 +397,8 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent auto thickness = getScrollBarThickness(); auto localArea = getLocalBounds().reduced(8); - vbar.setBounds(localArea.removeFromRight(thickness).withTrimmedBottom(thickness).translated(-1, 0)); - hbar.setBounds(localArea.removeFromBottom(thickness)); + vbar.setBounds(localArea.removeFromRight(thickness).withTrimmedBottom(thickness).translated(-3, 0)); + hbar.setBounds(localArea.removeFromBottom(thickness).translated(0, -3)); float scale = 1.0f / std::sqrt(std::abs(cnv->getTransform().getDeterminant())); auto contentArea = getViewArea() * scale; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index ae30c31c7f..19cb982a6c 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -415,16 +415,16 @@ void PluginEditor::resized() callOutSafeArea.setBounds(0, toolbarHeight, getWidth(), getHeight() - toolbarHeight - 30); - statusbar->setBounds(0, getHeight() - Statusbar::statusbarHeight, getWidth(), statusbar->getHeight()); + statusbar->setBounds(0, getHeight() - Statusbar::statusbarHeight, getWidth(), Statusbar::statusbarHeight); - auto workAreaHeight = getHeight() - toolbarHeight - statusbar->getHeight(); + auto workAreaHeight = getHeight() - toolbarHeight - Statusbar::statusbarHeight; palettes->setBounds(0, toolbarHeight, palettes->getWidth(), workAreaHeight); auto workArea = Rectangle(paletteWidth, toolbarHeight, (getWidth() - sidebar->getWidth() - paletteWidth), workAreaHeight); splitView.setBounds(workArea); - welcomePanel->setBounds(workArea.withTrimmedTop(welcomePanel->isVisible() ? 0 : 31)); - nvgSurface.updateBounds(workArea.withTrimmedTop(welcomePanel->isVisible() ? 0 : 31)); + welcomePanel->setBounds(workArea); + nvgSurface.updateBounds(welcomePanel->isVisible() ? workArea : workArea.withTrimmedTop(31)); sidebar->setBounds(getWidth() - sidebar->getWidth(), toolbarHeight, sidebar->getWidth(), workAreaHeight); diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 565986fbe6..ea1f6f5b6a 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -938,6 +938,13 @@ void Statusbar::audioProcessedChanged(bool audioProcessed) powerButton.setColour(TextButton::textColourOnId, colour); } +void Statusbar::lookAndFeelChanged() +{ + limiterButton.setColour(ComboBox::outlineColourId, Colours::transparentBlack); + limiterButton.setColour(TextButton::buttonColourId, findColour(PlugDataColour::levelMeterBackgroundColourId)); + limiterButton.setColour(TextButton::buttonOnColourId, findColour(PlugDataColour::levelMeterThumbColourId).withAlpha(0.3f)); +} + StatusbarSource::StatusbarSource() : numChannels(0) { @@ -954,6 +961,7 @@ void StatusbarSource::setBufferSize(int bufferSize) this->bufferSize = bufferSize; } + void StatusbarSource::process(bool hasMidiInput, bool hasMidiOutput, int channels) { if (channels == 1) { diff --git a/Source/Statusbar.h b/Source/Statusbar.h index 24138c6bf2..7fba99a7f5 100644 --- a/Source/Statusbar.h +++ b/Source/Statusbar.h @@ -49,7 +49,7 @@ class StatusbarSource : public Timer { void removeListener(Listener* l); void setCPUUsage(float cpuUsage); - + AudioSampleRingBuffer peakBuffer; private: @@ -85,6 +85,8 @@ class Statusbar : public Component void paint(Graphics& g) override; void resized() override; + + void lookAndFeelChanged() override; void audioProcessedChanged(bool audioProcessed) override; From 02ed92c6940f4c2c4d07a777b55dbcdf813ad7b6 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 29 May 2024 14:32:56 +0200 Subject: [PATCH 0809/1030] No outline on reconnect handle --- Source/Connection.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Source/Connection.cpp b/Source/Connection.cpp index 91d78e96ee..48232812af 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -249,18 +249,14 @@ void Connection::render(NVGcontext* nvg) auto expandedEndHandle = endReconnectHandle.contains(mousePos.toFloat()) ? endReconnectHandle.expanded(3.0f) : endReconnectHandle; nvgFillColor(nvg, handleColour); - nvgStrokeColor(nvg, convertColour(cnv->findColour(PlugDataColour::objectOutlineColourId))); - nvgStrokeWidth(nvg, 0.5f); - + nvgBeginPath(nvg); nvgCircle(nvg, expandedStartHandle.getCentreX(), expandedStartHandle.getCentreY(), expandedStartHandle.getWidth() / 2); nvgFill(nvg); - nvgStroke(nvg); nvgBeginPath(nvg); nvgCircle(nvg, expandedEndHandle.getCentreX(), expandedEndHandle.getCentreY(), expandedEndHandle.getWidth() / 2); nvgFill(nvg); - nvgStroke(nvg); } // draw direction arrow if activated in overlay menu From 601936b385c886e7544e2b57440b8c20533438a7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 29 May 2024 14:33:44 +0200 Subject: [PATCH 0810/1030] Fixed [scope~] documentation --- Resources/Documentation/ELSE/scope~.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Resources/Documentation/ELSE/scope~.md b/Resources/Documentation/ELSE/scope~.md index d8a11997c2..4489b170d8 100644 --- a/Resources/Documentation/ELSE/scope~.md +++ b/Resources/Documentation/ELSE/scope~.md @@ -1,5 +1,5 @@ --- -title: oscope~ +title: scope~ description: oscilloscope display @@ -8,8 +8,6 @@ categories: pdcategory: ELSE, UI, Analysis -arguments: - inlets: 1st: - type: signal From 806ca983e932a2fa495cf2b664310a8df746a5c2 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 29 May 2024 14:41:43 +0200 Subject: [PATCH 0811/1030] Fixed outline inconsistencies --- Source/PluginEditor.cpp | 15 ++++++++------- Source/Statusbar.cpp | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 19cb982a6c..c977539b13 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -337,13 +337,6 @@ void PluginEditor::paint(Graphics& g) } else { g.fillAll(baseColour); } - - // Draw lines in case tabbar is not visible. Otherwise the sidebar outlines will stop too soon - if(!getCurrentCanvas()) { - g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); - g.drawLine(palettes->isExpanded() ? palettes->getRight() : 29.5f, toolbarHeight, palettes->isExpanded() ? palettes->getRight() : 29.5f, toolbarHeight + 30); - g.drawLine(sidebar->getX(), toolbarHeight, sidebar->getX(), toolbarHeight + 30); - } } // Paint file drop outline @@ -357,6 +350,14 @@ void PluginEditor::paintOverChildren(Graphics& g) g.setColour(findColour(PlugDataColour::dataColourId)); g.drawRoundedRectangle(getLocalBounds().reduced(1).toFloat(), Corners::windowCornerRadius, 2.0f); } + + // Draw extra lines in case tabbar is not visible. Otherwise some outlines will stop too soon + if(!getCurrentCanvas()) { + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); + g.drawLine(palettes->isExpanded() ? palettes->getRight() : 29.5f, toolbarHeight, palettes->isExpanded() ? palettes->getRight() : 29.5f, toolbarHeight + 30); + g.drawLine(sidebar->getX() + 0.5f, toolbarHeight, sidebar->getX() + 0.5f, toolbarHeight + 30); + g.drawLine(palettes->isExpanded() ? palettes->getRight() : 29.0f, toolbarHeight - 0.5f, sidebar->getX() + 1.0f, toolbarHeight - 0.5f); + } } CallOutBox& PluginEditor::showCalloutBox(std::unique_ptr content, Rectangle screenBounds) diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index ea1f6f5b6a..115459f7f0 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -857,8 +857,8 @@ void Statusbar::paint(Graphics& g) g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); auto* editor = findParentComponentOfClass(); - auto start = !editor->palettes->isExpanded() ? 29.5f : 0.0f; - auto end = editor->sidebar->isHidden() ? 29.5f : 0.0f; + auto start = !editor->palettes->isExpanded() ? 29.0f : 0.0f; + auto end = editor->sidebar->isHidden() ? 29.0f : 0.0f; g.drawLine(start, 0.5f, static_cast(getWidth()) - end, 0.5f); g.drawLine(firstSeparatorPosition, 6.0f, firstSeparatorPosition, getHeight() - 6.0f); From c27d96ddcea3e4b307588e3dcf949a9c7e9ba290 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 29 May 2024 17:56:56 +0200 Subject: [PATCH 0812/1030] Fix for internal GM synth, fixed iOS compilation --- CMakeLists.txt | 11 +++++++++++ Source/PluginProcessor.cpp | 3 ++- Source/Standalone/InternalSynth.cpp | 11 +++++++++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f15f5b41e..e7656749e8 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -247,6 +247,7 @@ target_compile_definitions(juce $ ) +if(NOT "${CMAKE_SYSTEM_NAME}" MATCHES "iOS") target_link_libraries(juce PRIVATE juce::juce_audio_utils @@ -256,6 +257,16 @@ target_link_libraries(juce juce::juce_opengl melatonin_blur ) +else() +target_link_libraries(juce + PRIVATE + juce::juce_audio_utils + juce::juce_audio_plugin_client + juce::juce_dsp + juce::juce_cryptography + melatonin_blur + ) +endif() target_compile_options(juce PUBLIC $<$:${JUCE_LTO_FLAGS}>) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 4f3b52610f..f2b2cc7b9e 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -237,7 +237,8 @@ void PluginProcessor::initialiseFilesystem() // Create filesystem for this specific version tempVersionDataDir.moveFileTo(versionDataDir); - if(versionDataDir.isDirectory()) internalSynth->extractSoundfont(); + if(versionDataDir.isDirectory()) + internalSynth->extractSoundfont(); } if (!deken.exists()) { deken.createDirectory(); diff --git a/Source/Standalone/InternalSynth.cpp b/Source/Standalone/InternalSynth.cpp index 23b4043cdd..81040614e8 100644 --- a/Source/Standalone/InternalSynth.cpp +++ b/Source/Standalone/InternalSynth.cpp @@ -142,6 +142,8 @@ void InternalSynth::process(AudioBuffer& buffer, MidiBuffer& midiMessages return; } + unprepareLock.lock(); + // Pass MIDI messages to fluidsynth for (auto const& event : midiMessages) { auto const message = event.getMessage(); @@ -173,13 +175,18 @@ void InternalSynth::process(AudioBuffer& buffer, MidiBuffer& midiMessages fluid_synth_sysex(synth, reinterpret_cast(message.getSysExData()), message.getSysExDataSize(), nullptr, nullptr, nullptr, 0); } } - + + buffer.clear(); + internalBuffer.clear(); + // Run audio through fluidsynth - fluid_synth_process(synth, buffer.getNumSamples(), internalBuffer.getNumChannels(), const_cast(internalBuffer.getArrayOfReadPointers()), internalBuffer.getNumChannels(), const_cast(internalBuffer.getArrayOfWritePointers())); + fluid_synth_process(synth, buffer.getNumSamples(), buffer.getNumChannels(), const_cast(internalBuffer.getArrayOfReadPointers()), buffer.getNumChannels(), const_cast(internalBuffer.getArrayOfWritePointers())); for (int ch = 0; ch < buffer.getNumChannels(); ch++) { buffer.addFrom(ch, 0, internalBuffer, ch, 0, buffer.getNumSamples()); } + + unprepareLock.unlock(); #endif } From 4be4b1e62f8bbe2b1b76e16236170e04dc06e7a3 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 29 May 2024 23:16:35 +0200 Subject: [PATCH 0813/1030] Fix for resetting DAW latency --- Source/Statusbar.cpp | 2 +- Source/Statusbar.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 115459f7f0..30106e7ac5 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -695,7 +695,7 @@ Statusbar::Statusbar(PluginProcessor* processor) latencyDisplayButton = std::make_unique(); addChildComponent(latencyDisplayButton.get()); latencyDisplayButton->onClick = [this](){ - pd->performLatencyCompensationChange(64); + pd->performLatencyCompensationChange(0); }; powerButton.setButtonText(Icons::Power); diff --git a/Source/Statusbar.h b/Source/Statusbar.h index 7fba99a7f5..38977924cd 100644 --- a/Source/Statusbar.h +++ b/Source/Statusbar.h @@ -111,7 +111,7 @@ class Statusbar : public Component std::unique_ptr zoomLabel; - int currentLatency = 64; + int currentLatency = 0; float currentZoomLevel = 100.f; Value showDirection; From b43686bb6d5037d9e83fe3ff10d265a7d81aa125 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 30 May 2024 00:42:20 +0200 Subject: [PATCH 0814/1030] Small Metal and iOS fixes --- Source/CanvasViewport.h | 4 ++-- Source/Dialogs/Dialogs.cpp | 2 ++ Source/NVGSurface.cpp | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/CanvasViewport.h b/Source/CanvasViewport.h index 6f7a3175db..da5fa3ac62 100644 --- a/Source/CanvasViewport.h +++ b/Source/CanvasViewport.h @@ -397,8 +397,8 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent auto thickness = getScrollBarThickness(); auto localArea = getLocalBounds().reduced(8); - vbar.setBounds(localArea.removeFromRight(thickness).withTrimmedBottom(thickness).translated(-3, 0)); - hbar.setBounds(localArea.removeFromBottom(thickness).translated(0, -3)); + vbar.setBounds(localArea.removeFromRight(thickness).withTrimmedBottom(thickness).translated(-1, 0)); + hbar.setBounds(localArea.removeFromBottom(thickness).translated(0, -1)); float scale = 1.0f / std::sqrt(std::abs(cnv->getTransform().getDeterminant())); auto contentArea = getViewArea() * scale; diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index 071151878a..3c134b7af9 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -91,6 +91,7 @@ Dialog::Dialog(std::unique_ptr

* ownerPtr, Component* editor, int childWi } } +#if !JUCE_IOS void Dialog::mouseDrag(MouseEvent const &e) { if (dragging) { @@ -101,6 +102,7 @@ void Dialog::mouseDrag(MouseEvent const &e) dragger.dragWindow(this, e, nullptr); } } +#endif bool Dialog::wantsRoundedCorners() const { diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index ec99a0898b..3e44bd2836 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -258,7 +258,7 @@ void NVGSurface::resized() #if JUCE_IOS OSUtils::MTLResizeView(view, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); #endif - mnvgSetViewBounds(view, (renderScale * bounds.getWidth()) - 4, (renderScale * bounds.getHeight()) - 4); + mnvgSetViewBounds(view, (renderScale * bounds.getWidth()), (renderScale * bounds.getHeight())); } #endif } From 7b531fe4fc589c6e9af19136ace26b210e817a81 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 30 May 2024 15:14:04 +0930 Subject: [PATCH 0815/1030] Use dedicated Icon rendering for SmallIcons & fix Limit badge to aid in knowning when it's active --- Source/Components/Buttons.h | 16 +++++-------- Source/LookAndFeel.cpp | 5 ++++ Source/Sidebar/AutomationPanel.h | 3 +-- Source/Sidebar/PaletteItem.cpp | 1 - Source/Sidebar/PaletteItem.h | 2 +- Source/Statusbar.cpp | 40 ++++++++++++++++++++++---------- 6 files changed, 41 insertions(+), 26 deletions(-) diff --git a/Source/Components/Buttons.h b/Source/Components/Buttons.h index ca52bbffa9..de3dfc8627 100644 --- a/Source/Components/Buttons.h +++ b/Source/Components/Buttons.h @@ -87,20 +87,17 @@ class SmallIconButton : public TextButton { void paint(Graphics& g) override { - auto font = Fonts::getIconFont().withHeight(11.5); - g.setFont(font); + auto colour = findColour(PlugDataColour::toolbarTextColourId); if (!isEnabled()) { - g.setColour(Colours::grey); + colour = Colours::grey; } else if (getToggleState()) { - g.setColour(findColour(PlugDataColour::toolbarActiveColourId)); + colour = findColour(PlugDataColour::toolbarActiveColourId); } else if (isMouseOver()) { - g.setColour(findColour(PlugDataColour::toolbarTextColourId).brighter(0.8f)); - } else { - g.setColour(findColour(PlugDataColour::toolbarTextColourId)); + colour = findColour(PlugDataColour::toolbarTextColourId).brighter(0.8f); } - g.drawText(getButtonText(), 0, 0, getWidth(), getHeight(), Justification::centred); + Fonts::drawIcon(g, getButtonText(), getLocalBounds(), colour, 12); } }; @@ -196,9 +193,8 @@ class WelcomePanelButton : public Component { class ReorderButton : public SmallIconButton { public: ReorderButton() - : SmallIconButton() + : SmallIconButton(Icons::Reorder) { - setButtonText(Icons::Reorder); setSize(25, 25); } diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index c5da6246f3..8711e0b9f0 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -352,6 +352,11 @@ int PlugDataLook::getCallOutBoxBorderSize(CallOutBox const& c) void PlugDataLook::drawButtonText(Graphics& g, TextButton& button, bool isMouseOverButton, bool isButtonDown) { Font font(getTextButtonFont(button, button.getHeight())); + + if (static_cast(button.getProperties().getVarPointer("bold_text"))) { + font = Font(Fonts::getCurrentFont()).withHeight(button.getHeight() * 0.7f).withStyle(FontStyle::RegularBoldened); + } + g.setFont(font); auto colour = button.findColour(button.getToggleState() ? TextButton::textColourOnId : TextButton::textColourOffId) diff --git a/Source/Sidebar/AutomationPanel.h b/Source/Sidebar/AutomationPanel.h index d645baffe3..3f7013b40f 100644 --- a/Source/Sidebar/AutomationPanel.h +++ b/Source/Sidebar/AutomationPanel.h @@ -50,7 +50,6 @@ class AutomationItem : public ObjectDragAndDrop range.addListener(this); mode.addListener(this); - deleteButton.setButtonText(Icons::Clear); deleteButton.onClick = [this]() mutable { onDelete(this); }; @@ -385,7 +384,7 @@ class AutomationItem : public ObjectDragAndDrop std::function onDelete = [](AutomationItem*) {}; std::unique_ptr hostContextMenu; - SmallIconButton deleteButton; + SmallIconButton deleteButton = SmallIconButton(Icons::Clear); ExpandButton settingsButton; Value range = Value(var(Array { var(0.0f), var(127.0f) })); diff --git a/Source/Sidebar/PaletteItem.cpp b/Source/Sidebar/PaletteItem.cpp index 5915544cb5..8aca959bf0 100644 --- a/Source/Sidebar/PaletteItem.cpp +++ b/Source/Sidebar/PaletteItem.cpp @@ -39,7 +39,6 @@ PaletteItem::PaletteItem(PluginEditor* e, PaletteDraggableList* parent, ValueTre addChildComponent(reorderButton.get()); - deleteButton.setButtonText(Icons::Clear); deleteButton.setTooltip("Delete item"); deleteButton.setSize(25, 25); deleteButton.onClick = [this]() { diff --git a/Source/Sidebar/PaletteItem.h b/Source/Sidebar/PaletteItem.h index 003c1020c5..3d8c7ffb9d 100644 --- a/Source/Sidebar/PaletteItem.h +++ b/Source/Sidebar/PaletteItem.h @@ -39,7 +39,7 @@ class PaletteItem : public ObjectDragAndDrop { ValueTree itemTree; Label nameLabel; - SmallIconButton deleteButton; + SmallIconButton deleteButton = SmallIconButton(Icons::Clear); std::unique_ptr reorderButton; diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 30106e7ac5..518ef5f842 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -800,10 +800,9 @@ Statusbar::Statusbar(PluginProcessor* processor) OverlayDisplaySettings::show(editor, overlaySettingsButton.getScreenBounds()); }; addAndMakeVisible(overlaySettingsButton); - - limiterButton.setColour(ComboBox::outlineColourId, Colours::transparentBlack); - limiterButton.setColour(TextButton::buttonColourId, findColour(PlugDataColour::levelMeterBackgroundColourId)); - limiterButton.setColour(TextButton::buttonOnColourId, findColour(PlugDataColour::levelMeterThumbColourId).withAlpha(0.3f)); + + limiterButton.setLookAndFeel(&LookAndFeel::getDefaultLookAndFeel()); + limiterButton.getProperties().set("bold_text", true); limiterButton.setClickingTogglesState(true); limiterButton.setToggleState(SettingsFile::getInstance()->getProperty("protected"), dontSendNotification); @@ -817,7 +816,6 @@ Statusbar::Statusbar(PluginProcessor* processor) SettingsFile::getInstance()->setProperty("protected", state); }; addAndMakeVisible(limiterButton); - zoomComboButton.setTooltip(String("Select zoom")); @@ -829,6 +827,8 @@ Statusbar::Statusbar(PluginProcessor* processor) setLatencyDisplay(pd->getLatencySamples() - pd::Instance::getBlockSize()); setSize(getWidth(), statusbarHeight); + + lookAndFeelChanged(); } Statusbar::~Statusbar() @@ -902,16 +902,16 @@ void Statusbar::resized() position(22, true); #endif - audioSettingsButton.setBounds(position(24, true), 0, getHeight(), getHeight()); - powerButton.setBounds(position(spacing, true) + 16, 0, getHeight(), getHeight()); + audioSettingsButton.setBounds(position(getHeight(), true), 0, getHeight(), getHeight()); + powerButton.setBounds(position(getHeight() - 16, true), 0, getHeight(), getHeight()); + + limiterButton.setBounds(position(44, true), 4, 44, getHeight() - 8); // TODO: combine these both into one - int levelMeterPosition = position(132, true); + int levelMeterPosition = position(112, true); levelMeter->setBounds(levelMeterPosition, 2, 120, getHeight() - 4); volumeSlider->setBounds(levelMeterPosition, 2, 120, getHeight() - 4); - - limiterButton.setBounds(volumeSlider->getBounds().removeFromRight(42).translated(32, 0).reduced(2)); - + // Hide these if there isn't enough space midiBlinker->setVisible(getWidth() > 500); cpuMeter->setVisible(getWidth() > 500); @@ -942,7 +942,23 @@ void Statusbar::lookAndFeelChanged() { limiterButton.setColour(ComboBox::outlineColourId, Colours::transparentBlack); limiterButton.setColour(TextButton::buttonColourId, findColour(PlugDataColour::levelMeterBackgroundColourId)); - limiterButton.setColour(TextButton::buttonOnColourId, findColour(PlugDataColour::levelMeterThumbColourId).withAlpha(0.3f)); + auto limiterButtonActiveColour = findColour(PlugDataColour::toolbarActiveColourId).withAlpha(0.3f); + limiterButton.setColour(TextButton::buttonOnColourId, limiterButtonActiveColour); + + auto blendColours = [](const juce::Colour& bottomColour, const juce::Colour& topColour) -> Colour { + float alpha = topColour.getFloatAlpha(); + + float r = alpha * topColour.getFloatRed() + (1 - alpha) * bottomColour.getFloatRed(); + float g = alpha * topColour.getFloatGreen() + (1 - alpha) * bottomColour.getFloatGreen(); + float b = alpha * topColour.getFloatBlue() + (1 - alpha) * bottomColour.getFloatBlue(); + + return Colour::fromFloatRGBA(r, g, b, 1.0f); + }; + + auto blendedButtonColour = blendColours(findColour(PlugDataColour::panelBackgroundColourId), limiterButtonActiveColour); + + limiterButton.setColour(TextButton::textColourOffId, findColour(PlugDataColour::panelTextColourId)); + limiterButton.setColour(TextButton::textColourOnId, blendedButtonColour.contrasting()); } StatusbarSource::StatusbarSource() From e08c7291eaefbd53fada396e3e04020da3c2d4fc Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 30 May 2024 16:01:10 +0930 Subject: [PATCH 0816/1030] stop mouse events outside presentation mode plugin area registering --- Source/Canvas.cpp | 40 +++++++++++++++++++++++++++++++++------- Source/Canvas.h | 3 +++ Source/Object.cpp | 5 +++++ 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index d754f7d412..01a010efc4 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -297,10 +297,10 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) { auto backgroundColour = convertColour(findColour(PlugDataColour::canvasBackgroundColourId)); auto dotsColour = convertColour(findColour(PlugDataColour::canvasDotsColourId)); - - auto halfSize = infiniteCanvasSize / 2; - auto zoom = getValue(zoomScale); - auto hasViewport = viewport && !editor->pluginMode; + + const auto halfSize = infiniteCanvasSize / 2; + const auto zoom = getValue(zoomScale); + const auto hasViewport = viewport && !editor->pluginMode; // apply translation to the canvas nvg objects nvgSave(nvg); @@ -362,9 +362,9 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) if(hasViewport && (showOrigin || showBorder) && !::getValue(presentationMode)) { nvgBeginPath(nvg); - auto borderWidth = getValue(patchWidth); - auto borderHeight = getValue(patchHeight); - auto pos = Point(halfSize, halfSize); + const auto borderWidth = getValue(patchWidth); + const auto borderHeight = getValue(patchHeight); + const auto pos = Point(halfSize, halfSize); // Origin line paths. Draw both from {0, 0} so the strokes touch at the origin nvgMoveTo(nvg, pos.x, pos.y); @@ -1008,6 +1008,20 @@ void Canvas::mouseDown(MouseEvent const& e) } } +bool Canvas::hitTest(int x, int y) +{ + // allow panning to happen anywhere, even when in presentation mode + if (panningModifierDown()) + return true; + + // disregard mouse drag if outside of patch + if (::getValue(presentationMode)) { + if (isPointOutsidePluginArea(Point(x, y))) + return false; + } + return true; +} + void Canvas::mouseDrag(MouseEvent const& e) { if (canvasRateReducer.tooFast() || panningModifierDown()) @@ -2161,6 +2175,18 @@ bool Canvas::setPanDragMode(bool shouldPan) return false; } +bool Canvas::isPointOutsidePluginArea(Point point) +{ + const auto borderWidth = getValue(patchWidth); + const auto borderHeight = getValue(patchHeight); + const auto halfSize = infiniteCanvasSize / 2; + const auto pos = Point(halfSize, halfSize); + + auto pluginBounds = Rectangle(pos.x, pos.y, borderWidth, borderHeight); + + return !pluginBounds.contains(point); +} + void Canvas::findLassoItemsInArea(Array>& itemsFound, Rectangle const& area) { const auto lassoArea = area.withSize(jmax(area.getWidth(), 1), jmax(area.getHeight(), 1)); diff --git a/Source/Canvas.h b/Source/Canvas.h index 62d201e23f..38a7ffe746 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -72,6 +72,7 @@ class Canvas : public Component void mouseDown(MouseEvent const& e) override; void mouseDrag(MouseEvent const& e) override; void mouseUp(MouseEvent const& e) override; + bool hitTest(int x, int y) override; Point getLastMousePosition(); @@ -150,6 +151,8 @@ class Canvas : public Component bool checkPanDragMode(); bool setPanDragMode(bool shouldPan); + bool isPointOutsidePluginArea(Point point); + void findLassoItemsInArea(Array>& itemsFound, Rectangle const& area) override; void updateSidebarSelection(); diff --git a/Source/Object.cpp b/Source/Object.cpp index d6bc30966f..881e74a869 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -215,6 +215,11 @@ bool Object::checkIfHvccCompatible() const bool Object::hitTest(int x, int y) { + if (::getValue(presentationMode)) { + if (cnv->isPointOutsidePluginArea(cnv->getLocalPoint(this, Point(x, y)))) + return false; + } + if (cnv->panningModifierDown()) return false; From 0db50f8c61bd4b57d3121c47fccbef867adf4591 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 30 May 2024 15:59:29 +0200 Subject: [PATCH 0817/1030] Make toolbar a bit more compact, simplify colour IDs --- Libraries/CMakeLists.txt | 5 ++++ Source/Components/Buttons.h | 1 - Source/Components/PropertiesPanel.h | 4 +-- Source/Components/SuggestionComponent.h | 3 +-- Source/Constants.h | 8 ------ Source/Dialogs/AddObjectMenu.h | 3 +-- Source/Dialogs/AudioOutputSettings.h | 4 +-- Source/Dialogs/AudioSettingsPanel.h | 3 +-- Source/Dialogs/Dialogs.cpp | 3 +-- Source/Dialogs/MainMenu.h | 1 - Source/Dialogs/ObjectBrowserDialog.h | 13 ++++------ Source/Dialogs/PathsAndLibrariesPanel.h | 12 +++------ Source/Dialogs/TextEditorDialog.h | 2 +- Source/Heavy/HeavyExportDialog.cpp | 2 +- Source/LookAndFeel.cpp | 24 ++++++++--------- Source/LookAndFeel.h | 13 ++-------- Source/Objects/ArrayObject.h | 2 -- Source/Pd/Setup.cpp | 5 ++++ Source/PluginEditor.cpp | 34 ++++++++++++++----------- Source/PluginEditor.h | 2 +- Source/Sidebar/AutomationPanel.h | 2 -- Source/Sidebar/Console.h | 2 +- Source/Sidebar/Palettes.h | 2 -- Source/Statusbar.cpp | 2 +- Source/Tabbar/TabBarButtonComponent.cpp | 6 ++--- Source/Tabbar/Tabbar.cpp | 19 +------------- Source/Tabbar/Tabbar.h | 4 --- Source/Utility/ValueTreeViewer.h | 5 ++-- 28 files changed, 68 insertions(+), 118 deletions(-) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 9e5dee1758..a289b7ab8c 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -27,6 +27,9 @@ if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.21") unset(MESSAGE_QUIET) endif() +set(PD_DIR "pure-data/") +add_subdirectory(vstplugin) + # ------------------------------------------------------------------------------# set(CMAKE_MACOSX_RPATH ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -439,6 +442,8 @@ set(externals_libs fluidlite lua) if(ENABLE_SFIZZ) list(APPEND externals_libs sfizz) endif() +target_link_libraries(externals vstplugin_tilde) +target_link_libraries(externals-multi vstplugin_tilde_multi) if("${CMAKE_SYSTEM}" MATCHES "Linux") add_library(pd INTERFACE) diff --git a/Source/Components/Buttons.h b/Source/Components/Buttons.h index ca52bbffa9..eb907bf635 100644 --- a/Source/Components/Buttons.h +++ b/Source/Components/Buttons.h @@ -169,7 +169,6 @@ class WelcomePanelButton : public Component { if (isMouseOver()) { g.setColour(findColour(PlugDataColour::panelActiveBackgroundColourId)); PlugDataLook::fillSmoothedRectangle(g, Rectangle(1, 1, getWidth() - 2, getHeight() - 2), Corners::largeCornerRadius); - colour = findColour(PlugDataColour::panelActiveTextColourId); } Fonts::drawIcon(g, iconText, 20, 5, 40, colour, 24, false); diff --git a/Source/Components/PropertiesPanel.h b/Source/Components/PropertiesPanel.h index 5dd99ad3f9..f37888c4cd 100644 --- a/Source/Components/PropertiesPanel.h +++ b/Source/Components/PropertiesPanel.h @@ -511,7 +511,7 @@ class PropertiesPanel : public Component { g.fillPath(p); } - auto textColour = isDown ? findColour(PlugDataColour::panelActiveTextColourId) : findColour(PlugDataColour::panelTextColourId); + auto textColour = findColour(PlugDataColour::panelTextColourId); if (!isEnabled()) { textColour = findColour(PlugDataColour::panelTextColourId).withAlpha(0.5f); @@ -946,8 +946,6 @@ class PropertiesPanel : public Component { Path p; p.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), Corners::largeCornerRadius, Corners::largeCornerRadius, roundTop, roundTop, roundBottom, roundBottom); g.fillPath(p); - - colour = findColour(PlugDataColour::panelActiveTextColourId); } Fonts::drawIcon(g, icon, iconBounds, colour, 12); diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index e15a5086d4..90a3cba8ec 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -213,8 +213,7 @@ class SuggestionComponent : public Component g.setColour(backgroundColour); PlugDataLook::fillSmoothedRectangle(g, buttonArea, Corners::defaultCornerRadius); - auto colour = getToggleState() ? findColour(PlugDataColour::popupMenuActiveTextColourId) : findColour(PlugDataColour::popupMenuTextColourId); - + auto colour = findColour(PlugDataColour::popupMenuTextColourId); auto yIndent = jmin(4, proportionOfHeight(0.3f)); auto leftIndent = drawIcon ? 32 : 11; auto rightIndent = 14; diff --git a/Source/Constants.h b/Source/Constants.h index 1eb40d3b00..721a2e517f 100644 --- a/Source/Constants.h +++ b/Source/Constants.h @@ -248,11 +248,7 @@ enum PlugDataColour { toolbarActiveColourId, toolbarHoverColourId, toolbarOutlineColourId, - - tabBackgroundColourId, - tabTextColourId, activeTabBackgroundColourId, - activeTabTextColourId, canvasBackgroundColourId, canvasTextColourId, @@ -282,7 +278,6 @@ enum PlugDataColour { sidebarBackgroundColourId, sidebarTextColourId, sidebarActiveBackgroundColourId, - sidebarActiveTextColourId, levelMeterActiveColourId, levelMeterBackgroundColourId, @@ -292,14 +287,11 @@ enum PlugDataColour { panelForegroundColourId, panelTextColourId, panelActiveBackgroundColourId, - panelActiveTextColourId, popupMenuBackgroundColourId, popupMenuActiveBackgroundColourId, popupMenuTextColourId, - popupMenuActiveTextColourId, - sliderThumbColourId, scrollbarThumbColourId, graphAreaColourId, gridLineColourId, diff --git a/Source/Dialogs/AddObjectMenu.h b/Source/Dialogs/AddObjectMenu.h index 66df5cbd3f..86e42e527b 100644 --- a/Source/Dialogs/AddObjectMenu.h +++ b/Source/Dialogs/AddObjectMenu.h @@ -515,7 +515,7 @@ class ObjectCategoryView : public Component { button->setClickingTogglesState(true); button->setRadioGroupId(hash("add_menu_category")); button->setColour(TextButton::textColourOffId, findColour(PlugDataColour::popupMenuTextColourId)); - button->setColour(TextButton::textColourOnId, findColour(PlugDataColour::popupMenuActiveTextColourId)); + button->setColour(TextButton::textColourOnId, findColour(PlugDataColour::popupMenuTextColourId)); button->setColour(TextButton::buttonColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.035f)); button->setColour(TextButton::buttonOnColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.075f)); button->setColour(ComboBox::outlineColourId, Colours::transparentBlack); @@ -574,7 +574,6 @@ class AddObjectMenuButton : public Component { if (isMouseOver()) { g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId)); PlugDataLook::fillSmoothedRectangle(g, b.toFloat(), Corners::defaultCornerRadius); - colour = findColour(PlugDataColour::popupMenuActiveTextColourId); } if (toggleState) { diff --git a/Source/Dialogs/AudioOutputSettings.h b/Source/Dialogs/AudioOutputSettings.h index d70e9ea3e0..bf2f7f57d5 100644 --- a/Source/Dialogs/AudioOutputSettings.h +++ b/Source/Dialogs/AudioOutputSettings.h @@ -30,7 +30,7 @@ class OversampleSettings : public Component { }; button->setColour(TextButton::textColourOffId, findColour(PlugDataColour::popupMenuTextColourId)); - button->setColour(TextButton::textColourOnId, findColour(PlugDataColour::popupMenuActiveTextColourId)); + button->setColour(TextButton::textColourOnId, findColour(PlugDataColour::popupMenuTextColourId)); button->setColour(TextButton::buttonColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.04f)); button->setColour(TextButton::buttonOnColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.075f)); button->setColour(ComboBox::outlineColourId, Colours::transparentBlack); @@ -84,7 +84,7 @@ class LimiterSettings : public Component { }; button->setColour(TextButton::textColourOffId, findColour(PlugDataColour::popupMenuTextColourId)); - button->setColour(TextButton::textColourOnId, findColour(PlugDataColour::popupMenuActiveTextColourId)); + button->setColour(TextButton::textColourOnId, findColour(PlugDataColour::popupMenuTextColourId)); button->setColour(TextButton::buttonColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.04f)); button->setColour(TextButton::buttonOnColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.075f)); button->setColour(ComboBox::outlineColourId, Colours::transparentBlack); diff --git a/Source/Dialogs/AudioSettingsPanel.h b/Source/Dialogs/AudioSettingsPanel.h index a470333073..f1e04925d7 100644 --- a/Source/Dialogs/AudioSettingsPanel.h +++ b/Source/Dialogs/AudioSettingsPanel.h @@ -151,8 +151,7 @@ class ChannelToggleProperty : public PropertiesPanel::BoolComponent { g.fillPath(buttonShape); } - auto textColour = isDown ? findColour(PlugDataColour::panelActiveTextColourId) : findColour(PlugDataColour::panelTextColourId); - Fonts::drawText(g, textOptions[isDown], buttonBounds, textColour, 14.0f, Justification::centred); + Fonts::drawText(g, textOptions[isDown], buttonBounds, findColour(PlugDataColour::panelTextColourId), 14.0f, Justification::centred); // Paint label PropertiesPanelProperty::paint(g); diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index 3c134b7af9..115a583a97 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -445,7 +445,7 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId)); PlugDataLook::fillSmoothedRectangle(g, bounds, Corners::defaultCornerRadius); - textColour = findColour(PlugDataColour::sidebarActiveTextColourId); + textColour = findColour(PlugDataColour::sidebarTextColourId); } Fonts::drawIcon(g, getButtonText(), std::max(0, getWidth() - getHeight()) / 2, 0, getHeight(), textColour, 12.8f); @@ -648,7 +648,6 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId)); PlugDataLook::fillSmoothedRectangle(g, r.toFloat().reduced(0, 1), Corners::defaultCornerRadius); - colour = findColour(PlugDataColour::popupMenuActiveTextColourId); } g.setColour(colour); diff --git a/Source/Dialogs/MainMenu.h b/Source/Dialogs/MainMenu.h index d6d2378cb0..87cf7339c3 100644 --- a/Source/Dialogs/MainMenu.h +++ b/Source/Dialogs/MainMenu.h @@ -139,7 +139,6 @@ class MainMenu : public PopupMenu { g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId)); PlugDataLook::fillSmoothedRectangle(g, r.toFloat().reduced(0, 1), Corners::defaultCornerRadius); - colour = findColour(PlugDataColour::popupMenuActiveTextColourId); } g.setColour(colour); diff --git a/Source/Dialogs/ObjectBrowserDialog.h b/Source/Dialogs/ObjectBrowserDialog.h index 6a41893933..1f4732e309 100644 --- a/Source/Dialogs/ObjectBrowserDialog.h +++ b/Source/Dialogs/ObjectBrowserDialog.h @@ -53,10 +53,8 @@ class CategoriesListBox : public ListBox g.setColour(findColour(PlugDataColour::panelActiveBackgroundColourId)); g.fillRoundedRectangle({ 4.0f, 1.0f, width - 8.0f, height - 2.0f }, Corners::defaultCornerRadius); } - - auto colour = rowIsSelected ? findColour(PlugDataColour::panelActiveTextColourId) : findColour(PlugDataColour::panelTextColourId); - - Fonts::drawText(g, categories[rowNumber], 12, 0, width - 9, height, colour, 15); + + Fonts::drawText(g, categories[rowNumber], 12, 0, width - 9, height, findColour(PlugDataColour::panelTextColourId), 15); } void initialise(StringArray newCategories) @@ -97,7 +95,7 @@ class ObjectsListBox : public ListBox g.fillRoundedRectangle(getLocalBounds().reduced(4, 2).toFloat(), Corners::defaultCornerRadius); } - auto colour = rowIsSelected ? findColour(PlugDataColour::panelActiveTextColourId) : findColour(PlugDataColour::panelTextColourId); + auto colour = findColour(PlugDataColour::panelTextColourId); auto textBounds = Rectangle(0, 0, getWidth(), getHeight()).reduced(18, 6); @@ -687,11 +685,10 @@ class ObjectSearchComponent : public Component g.fillRoundedRectangle(4, 2, w - 8, h - 4, Corners::defaultCornerRadius); } - g.setColour(rowIsSelected ? findColour(PlugDataColour::panelActiveTextColourId) : findColour(ComboBox::textColourId)); + g.setColour(findColour(ComboBox::textColourId)); String const item = searchResult[rowNumber]; - auto colour = rowIsSelected ? findColour(PlugDataColour::popupMenuActiveTextColourId) : findColour(PlugDataColour::popupMenuTextColourId); - + auto colour = findColour(PlugDataColour::popupMenuTextColourId); auto yIndent = jmin(4, h * 0.3f); auto leftIndent = 34; auto rightIndent = 11; diff --git a/Source/Dialogs/PathsAndLibrariesPanel.h b/Source/Dialogs/PathsAndLibrariesPanel.h index 90d2cf9dae..8429b12b6d 100644 --- a/Source/Dialogs/PathsAndLibrariesPanel.h +++ b/Source/Dialogs/PathsAndLibrariesPanel.h @@ -37,8 +37,6 @@ class ActionButton : public Component { Path p; p.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), Corners::largeCornerRadius, Corners::largeCornerRadius, roundTop, roundTop, true, true); g.fillPath(p); - - colour = findColour(PlugDataColour::panelActiveTextColourId); } Fonts::drawIcon(g, icon, iconBounds, colour, 12); @@ -204,10 +202,8 @@ class SearchPathPanel : public Component g.setColour(findColour(PlugDataColour::toolbarOutlineColourId).withAlpha(0.5f)); g.drawHorizontalLine(height - 1.0f, x, x + newWidth); - - auto colour = rowIsSelected ? findColour(PlugDataColour::panelActiveTextColourId) : findColour(PlugDataColour::panelTextColourId); - - Fonts::drawText(g, paths[rowNumber], x + 12, 0, width - 9, height, colour, 15); + + Fonts::drawText(g, paths[rowNumber], x + 12, 0, width - 9, height, findColour(PlugDataColour::panelTextColourId), 15); } void deleteKeyPressed(int row) override @@ -552,9 +548,7 @@ class LibraryLoadPanel : public Component g.setColour(findColour(PlugDataColour::toolbarOutlineColourId).withAlpha(0.5f)); g.drawHorizontalLine(height - 1.0f, x, x + newWidth); - auto colour = rowIsSelected ? findColour(PlugDataColour::panelActiveTextColourId) : findColour(PlugDataColour::panelTextColourId); - - Fonts::drawText(g, librariesToLoad[rowNumber], x + 12, 0, width - 9, height, colour, 15); + Fonts::drawText(g, librariesToLoad[rowNumber], x + 12, 0, width - 9, height, findColour(PlugDataColour::panelTextColourId), 15); } void deleteKeyPressed(int row) override diff --git a/Source/Dialogs/TextEditorDialog.h b/Source/Dialogs/TextEditorDialog.h index 3337e5fde2..f8707b551c 100644 --- a/Source/Dialogs/TextEditorDialog.h +++ b/Source/Dialogs/TextEditorDialog.h @@ -756,7 +756,7 @@ void GutterComponent::paint(Graphics& g) } for (auto const& r : rowData) { - g.setColour(getParentComponent()->findColour(r.isRowSelected ? PlugDataColour::panelActiveTextColourId : PlugDataColour::panelTextColourId)); + g.setColour(getParentComponent()->findColour(PlugDataColour::panelTextColourId)); memoizedGlyphArrangements(r.rowNumber).draw(g, verticalTransform); } } diff --git a/Source/Heavy/HeavyExportDialog.cpp b/Source/Heavy/HeavyExportDialog.cpp index be6f8c7693..2fc036a74f 100644 --- a/Source/Heavy/HeavyExportDialog.cpp +++ b/Source/Heavy/HeavyExportDialog.cpp @@ -169,7 +169,7 @@ class ExporterSettingsPanel : public Component PlugDataLook::fillSmoothedRectangle(g, Rectangle(3, 3, width - 6, height - 6), Corners::defaultCornerRadius); } - auto const textColour = findColour(rowIsSelected ? PlugDataColour::sidebarActiveTextColourId : PlugDataColour::sidebarTextColourId); + auto const textColour = findColour(PlugDataColour::sidebarTextColourId); Fonts::drawText(g, items[row], Rectangle(15, 0, width - 30, height), textColour, 15); } diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index c5da6246f3..cf546f0770 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -537,9 +537,9 @@ void PlugDataLook::drawTabButton(TabBarButton& button, Graphics& g, bool isMouse if (isActive) { g.setColour(findColour(PlugDataColour::activeTabBackgroundColourId)); } else if (isMouseOver) { - g.setColour(findColour(PlugDataColour::activeTabBackgroundColourId).interpolatedWith(findColour(PlugDataColour::tabBackgroundColourId), 0.4f)); + g.setColour(findColour(PlugDataColour::activeTabBackgroundColourId).interpolatedWith(findColour(PlugDataColour::toolbarBackgroundColourId), 0.4f)); } else { - g.setColour(findColour(PlugDataColour::tabBackgroundColourId)); + g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); } fillSmoothedRectangle(g, button.getLocalBounds().reduced(4).toFloat(), Corners::defaultCornerRadius); @@ -625,11 +625,11 @@ Button* PlugDataLook::createTabBarExtrasButton() g.setColour(findColour(PlugDataColour::toolbarHoverColourId)); fillSmoothedRectangle(g, getLocalBounds().reduced(3).toFloat(), Corners::defaultCornerRadius); } else { - g.setColour(findColour(PlugDataColour::tabBackgroundColourId)); + g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); } g.setFont(Fonts::getIconFont().withHeight(15)); - g.setColour(findColour(PlugDataColour::tabTextColourId)); + g.setColour(findColour(PlugDataColour::toolbarTextColourId)); g.drawText(getButtonText(), getLocalBounds().reduced(3), Justification::centred); } @@ -858,8 +858,6 @@ void PlugDataLook::drawPopupMenuItem(Graphics& g, Rectangle const& area, if (isHighlighted && isActive) { g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId)); fillSmoothedRectangle(g, r.toFloat().reduced(4, 0), Corners::defaultCornerRadius); - // g.fillRoundedRectangle(r.toFloat().reduced(4, 0), Corners::defaultCornerRadius); - colour = findColour(PlugDataColour::popupMenuActiveTextColourId); } g.setColour(colour); @@ -1285,7 +1283,7 @@ void PlugDataLook::setColours(std::map colours) setColour(TextButton::textColourOnId, colours.at(PlugDataColour::toolbarTextColourId)); setColour(Slider::thumbColourId, - colours.at(PlugDataColour::sliderThumbColourId)); + colours.at(PlugDataColour::levelMeterThumbColourId)); setColour(ScrollBar::thumbColourId, colours.at(PlugDataColour::scrollbarThumbColourId)); setColour(DirectoryContentsDisplayComponent::highlightColourId, @@ -1320,7 +1318,7 @@ void PlugDataLook::setColours(std::map colours) setColour(Slider::backgroundColourId, colours.at(PlugDataColour::canvasBackgroundColourId)); setColour(Slider::trackColourId, - colours.at(PlugDataColour::sliderThumbColourId)); + colours.at(PlugDataColour::levelMeterBackgroundColourId)); setColour(TextEditor::backgroundColourId, colours.at(PlugDataColour::canvasBackgroundColourId)); setColour(FileBrowserComponent::currentPathBoxBackgroundColourId, @@ -1351,9 +1349,9 @@ void PlugDataLook::setColours(std::map colours) setColour(KeyMappingEditorComponent::textColourId, colours.at(PlugDataColour::panelTextColourId)); setColour(TabbedButtonBar::frontTextColourId, - colours.at(PlugDataColour::activeTabTextColourId)); + colours.at(PlugDataColour::toolbarTextColourId)); setColour(TabbedButtonBar::tabTextColourId, - colours.at(PlugDataColour::tabTextColourId)); + colours.at(PlugDataColour::toolbarTextColourId)); setColour(ToggleButton::textColourId, colours.at(PlugDataColour::panelTextColourId)); setColour(ToggleButton::tickColourId, @@ -1367,16 +1365,16 @@ void PlugDataLook::setColours(std::map colours) setColour(Slider::textBoxTextColourId, colours.at(PlugDataColour::panelTextColourId)); setColour(FileBrowserComponent::currentPathBoxTextColourId, - colours.at(PlugDataColour::panelActiveTextColourId)); + colours.at(PlugDataColour::panelTextColourId)); setColour(FileBrowserComponent::currentPathBoxArrowColourId, - colours.at(PlugDataColour::panelActiveTextColourId)); + colours.at(PlugDataColour::panelTextColourId)); setColour(FileBrowserComponent::filenameBoxTextColourId, colours.at(PlugDataColour::panelTextColourId)); setColour(FileChooserDialogBox::titleTextColourId, colours.at(PlugDataColour::panelTextColourId)); setColour(DirectoryContentsDisplayComponent::highlightedTextColourId, - colours.at(PlugDataColour::panelActiveTextColourId)); + colours.at(PlugDataColour::panelTextColourId)); setColour(TooltipWindow::outlineColourId, colours.at(PlugDataColour::outlineColourId)); diff --git a/Source/LookAndFeel.h b/Source/LookAndFeel.h index f18093740b..284b0d1d50 100644 --- a/Source/LookAndFeel.h +++ b/Source/LookAndFeel.h @@ -16,12 +16,7 @@ inline std::map> const PlugDa { toolbarTextColourId, { "Toolbar text", "toolbar_text", "Toolbar" } }, { toolbarHoverColourId, { "Toolbar hover", "toolbar_hover", "Toolbar" } }, { toolbarActiveColourId, { "Toolbar active text", "toolbar_active", "Toolbar" } }, - - { tabBackgroundColourId, { "Tab background", "tabbar_background", "Tabbar" } }, - - { tabTextColourId, { "Tab text", "tab_text", "Tabbar" } }, { activeTabBackgroundColourId, { "Selected tab background", "selected_tab_background", "Tabbar" } }, - { activeTabTextColourId, { "Selected tab text", "selected_tab_text", "Tabbar" } }, { canvasBackgroundColourId, { "Canvas background", "canvas_background", "Canvas" } }, { canvasTextColourId, { "Canvas text", "canvas_text", "Canvas" } }, @@ -30,7 +25,7 @@ inline std::map> const PlugDa { presentationBackgroundColourId, { "Presentation background", "presentation_background", "Canvas" } }, { guiObjectBackgroundColourId, { "GUI object background", "default_object_background", "Object" } }, - { guiObjectInternalOutlineColour, { "GUI Object internal outline colour", "gui_internal_outline_colour", "Object" } }, + { guiObjectInternalOutlineColour, { "GUI object internal outline colour", "gui_internal_outline_colour", "Object" } }, { textObjectBackgroundColourId, { "Object background", "text_object_background", "Object" } }, { commentTextColourId, { "Comment text", "comment_text_colour", "Object" } }, { objectOutlineColourId, { "Object outline", "object_outline_colour", "Object" } }, @@ -49,7 +44,6 @@ inline std::map> const PlugDa { popupMenuBackgroundColourId, { "Popup menu background", "popup_background", "Popup Menu" } }, { popupMenuActiveBackgroundColourId, { "Popup menu background active", "popup_background_active", "Popup Menu" } }, { popupMenuTextColourId, { "Popup menu text", "popup_text", "Popup Menu" } }, - { popupMenuActiveTextColourId, { "Popup menu active text", "popup_active_text", "Popup Menu" } }, { outlineColourId, { "Popup menu outline", "outline_colour", "Popup Menu" } }, { dialogBackgroundColourId, { "Dialog background", "dialog_background", "Other" } }, @@ -60,19 +54,16 @@ inline std::map> const PlugDa { levelMeterBackgroundColourId, { "Level meter track", "levelmeter_background", "Level Meter" } }, { levelMeterThumbColourId, { "Level meter thumb", "levelmeter_thumb", "Level Meter" } }, - { sliderThumbColourId, { "Slider thumb", "slider_thumb", "Other" } }, { scrollbarThumbColourId, { "Scrollbar thumb", "scrollbar_thumb", "Other" } }, { panelBackgroundColourId, { "Panel background", "panel_background", "Panel" } }, { panelForegroundColourId, { "Panel foreground", "panel_foreground", "Panel" } }, { panelTextColourId, { "Panel text", "panel_text", "Panel" } }, { panelActiveBackgroundColourId, { "Panel background active", "panel_background_active", "Panel" } }, - { panelActiveTextColourId, { "Panel active text", "panel_active_text", "Panel" } }, - + { sidebarBackgroundColourId, { "Sidebar background", "sidebar_colour", "Sidebar" } }, { sidebarTextColourId, { "Sidebar text", "sidebar_text", "Sidebar" } }, { sidebarActiveBackgroundColourId, { "Sidebar background active", "sidebar_background_active", "Sidebar" } }, - { sidebarActiveTextColourId, { "Sidebar active text", "sidebar_active_text", "Sidebar" } }, }; struct PlugDataLook : public LookAndFeel_V4 { diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index 98c2d684f6..3e80bf0f2d 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -763,8 +763,6 @@ struct ArrayPropertiesPanel : public PropertiesPanelProperty, public Value::List if (mouseIsOver) { g.setColour(findColour(PlugDataColour::sidebarActiveBackgroundColourId)); PlugDataLook::fillSmoothedRectangle(g, bounds.toFloat(), Corners::defaultCornerRadius); - - colour = findColour(PlugDataColour::sidebarActiveTextColourId); } Fonts::drawIcon(g, Icons::Add, iconBounds, colour, 12); diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index 98f6a24ab9..98eee4082b 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -1249,6 +1249,9 @@ void var_setup(); void conv_tilde_setup(); void fm_tilde_setup(); + +void vstplugin_tilde_setup(); + #ifdef ENABLE_SFIZZ void sfz_tilde_setup(); #endif @@ -1724,6 +1727,8 @@ void Setup::initialiseELSE() var_setup(); conv_tilde_setup(); fm_tilde_setup(); + + vstplugin_tilde_setup(); } void Setup::initialiseGem(std::string const& gemPluginPath) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index c977539b13..29137effa5 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -351,12 +351,15 @@ void PluginEditor::paintOverChildren(Graphics& g) g.drawRoundedRectangle(getLocalBounds().reduced(1).toFloat(), Corners::windowCornerRadius, 2.0f); } + auto tabbarDepth = welcomePanel->isVisible() ? toolbarHeight + 5.5f : toolbarHeight + 30.0f; + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); + g.drawLine(palettes->isExpanded() ? palettes->getRight() : 29.0f, tabbarDepth, sidebar->getX() + 1.0f, tabbarDepth); + // Draw extra lines in case tabbar is not visible. Otherwise some outlines will stop too soon if(!getCurrentCanvas()) { - g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); - g.drawLine(palettes->isExpanded() ? palettes->getRight() : 29.5f, toolbarHeight, palettes->isExpanded() ? palettes->getRight() : 29.5f, toolbarHeight + 30); - g.drawLine(sidebar->getX() + 0.5f, toolbarHeight, sidebar->getX() + 0.5f, toolbarHeight + 30); - g.drawLine(palettes->isExpanded() ? palettes->getRight() : 29.0f, toolbarHeight - 0.5f, sidebar->getX() + 1.0f, toolbarHeight - 0.5f); + auto toolbarDepth = welcomePanel->isVisible() ? toolbarHeight + 6 : toolbarHeight; + g.drawLine(palettes->isExpanded() ? palettes->getRight() : 29.5f, toolbarDepth, palettes->isExpanded() ? palettes->getRight() : 29.5f, toolbarDepth + 30); + g.drawLine(sidebar->getX() + 0.5f, toolbarDepth, sidebar->getX() + 0.5f, toolbarHeight + 30); } } @@ -424,8 +427,8 @@ void PluginEditor::resized() auto workArea = Rectangle(paletteWidth, toolbarHeight, (getWidth() - sidebar->getWidth() - paletteWidth), workAreaHeight); splitView.setBounds(workArea); - welcomePanel->setBounds(workArea); - nvgSurface.updateBounds(welcomePanel->isVisible() ? workArea : workArea.withTrimmedTop(31)); + welcomePanel->setBounds(workArea.withTrimmedTop(4)); + nvgSurface.updateBounds(welcomePanel->isVisible() ? workArea.withTrimmedTop(6) : workArea.withTrimmedTop(31)); sidebar->setBounds(getWidth() - sidebar->getWidth(), toolbarHeight, sidebar->getWidth(), workAreaHeight); @@ -440,17 +443,18 @@ void PluginEditor::resized() offset += 22; #endif - int buttonDisctance = 56; - mainMenuButton.setBounds(offset, 0, toolbarHeight, toolbarHeight); - undoButton.setBounds(buttonDisctance + offset, 0, toolbarHeight, toolbarHeight); - redoButton.setBounds((2 * buttonDisctance) + offset, 0, toolbarHeight, toolbarHeight); - addObjectMenuButton.setBounds((3 * buttonDisctance) + offset, 0, toolbarHeight, toolbarHeight); + auto const buttonDistance = 56; + auto const buttonSize = toolbarHeight + 5; + mainMenuButton.setBounds(offset, 0, buttonSize, buttonSize); + undoButton.setBounds(buttonDistance + offset, 0, buttonSize, buttonSize); + redoButton.setBounds((2 * buttonDistance) + offset, 0, buttonSize, buttonSize); + addObjectMenuButton.setBounds((3 * buttonDistance) + offset, 0, buttonSize, buttonSize); auto startX = (getWidth() / 2) - (toolbarHeight * 1.5); - editButton.setBounds(startX, 1, toolbarHeight, toolbarHeight - 2); - runButton.setBounds(startX + toolbarHeight - 1, 1, toolbarHeight, toolbarHeight - 2); - presentButton.setBounds(startX + (2 * toolbarHeight) - 2, 1, toolbarHeight, toolbarHeight - 2); + editButton.setBounds(startX, 1, buttonSize, buttonSize - 2); + runButton.setBounds(startX + buttonSize - 1, 1, buttonSize, buttonSize - 2); + presentButton.setBounds(startX + (2 * buttonSize) - 2, 1, buttonSize, buttonSize - 2); auto windowControlsOffset = (useNonNativeTitlebar && !useLeftButtons) ? 140.0f : 50.0f; @@ -463,7 +467,7 @@ void PluginEditor::resized() resizerSize, resizerSize); } - pluginModeButton.setBounds(getWidth() - windowControlsOffset, 0, toolbarHeight, toolbarHeight); + pluginModeButton.setBounds(getWidth() - windowControlsOffset, 0, buttonSize, buttonSize); pd->lastUIWidth = getWidth(); pd->lastUIHeight = getHeight(); diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 6c6e3e33e0..243afe3e02 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -202,7 +202,7 @@ class PluginEditor : public AudioProcessorEditor // Used by standalone to handle dragging the window WindowDragger windowDragger; - int const toolbarHeight = ProjectInfo::isStandalone ? 38 : 35; + int const toolbarHeight = 32; MainToolbarButton mainMenuButton, undoButton, redoButton, addObjectMenuButton, pluginModeButton; ToolbarRadioButton editButton, runButton, presentButton; diff --git a/Source/Sidebar/AutomationPanel.h b/Source/Sidebar/AutomationPanel.h index d645baffe3..0fb827dc72 100644 --- a/Source/Sidebar/AutomationPanel.h +++ b/Source/Sidebar/AutomationPanel.h @@ -529,8 +529,6 @@ class AutomationComponent : public Component { if (mouseIsOver) { g.setColour(findColour(PlugDataColour::sidebarActiveBackgroundColourId)); PlugDataLook::fillSmoothedRectangle(g, bounds.toFloat(), Corners::defaultCornerRadius); - - colour = findColour(PlugDataColour::sidebarActiveTextColourId); } Fonts::drawIcon(g, Icons::Add, iconBounds, colour, 12); diff --git a/Source/Sidebar/Console.h b/Source/Sidebar/Console.h index 2e8a0e5fdb..e7cd71ae9b 100644 --- a/Source/Sidebar/Console.h +++ b/Source/Sidebar/Console.h @@ -235,7 +235,7 @@ class Console : public Component auto totalLength = length + calculateRepeatOffset(repeats); auto numLines = Console::calculateNumLines(message, totalLength, console.getWidth()); - auto textColour = findColour(isSelected ? PlugDataColour::sidebarActiveTextColourId : PlugDataColour::sidebarTextColourId); + auto textColour = findColour(PlugDataColour::sidebarTextColourId); if (type == 1) textColour = Colours::orange; diff --git a/Source/Sidebar/Palettes.h b/Source/Sidebar/Palettes.h index 8d164900d7..ae0d5ff55c 100644 --- a/Source/Sidebar/Palettes.h +++ b/Source/Sidebar/Palettes.h @@ -39,8 +39,6 @@ class AddItemButton : public Component { if (mouseIsOver) { g.setColour(findColour(PlugDataColour::sidebarActiveBackgroundColourId)); g.fillRoundedRectangle(bounds.toFloat(), Corners::defaultCornerRadius); - - colour = findColour(PlugDataColour::sidebarActiveTextColourId); } Fonts::drawIcon(g, Icons::Add, iconBounds, colour, 12); diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 30106e7ac5..25c1f77340 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -475,7 +475,7 @@ class CPUMeterPopup : public Component { cpuGraphLongHistory->updateMapping(i); }; button->setColour(TextButton::textColourOffId, findColour(PlugDataColour::popupMenuTextColourId)); - button->setColour(TextButton::textColourOnId, findColour(PlugDataColour::popupMenuActiveTextColourId)); + button->setColour(TextButton::textColourOnId, findColour(PlugDataColour::popupMenuTextColourId)); button->setColour(TextButton::buttonColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.04f)); button->setColour(TextButton::buttonOnColourId, findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.075f)); button->setColour(ComboBox::outlineColourId, Colours::transparentBlack); diff --git a/Source/Tabbar/TabBarButtonComponent.cpp b/Source/Tabbar/TabBarButtonComponent.cpp index 691d242640..2cf6d58727 100644 --- a/Source/Tabbar/TabBarButtonComponent.cpp +++ b/Source/Tabbar/TabBarButtonComponent.cpp @@ -331,9 +331,9 @@ void TabBarButtonComponent::drawTabButton(Graphics& g, Rectangle customBoun if (isActive) { g.setColour(findColour(PlugDataColour::activeTabBackgroundColourId)); } else if (isMouseOver(true)) { - g.setColour(findColour(PlugDataColour::activeTabBackgroundColourId).interpolatedWith(findColour(PlugDataColour::tabBackgroundColourId), 0.4f)); + g.setColour(findColour(PlugDataColour::activeTabBackgroundColourId).interpolatedWith(findColour(PlugDataColour::toolbarBackgroundColourId), 0.4f)); } else { - g.setColour(findColour(PlugDataColour::tabBackgroundColourId)); + g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); } auto bounds = getLocalBounds(); @@ -358,7 +358,7 @@ void TabBarButtonComponent::drawTabButtonText(Graphics& g, Rectangle custom AffineTransform t = AffineTransform::translation(area.getX(), area.getY()); - g.setColour(findColour(PlugDataColour::tabTextColourId)); + g.setColour(findColour(PlugDataColour::toolbarTextColourId)); g.setFont(font); g.addTransform(t); diff --git a/Source/Tabbar/Tabbar.cpp b/Source/Tabbar/Tabbar.cpp index f2659c7765..17d8ce6093 100644 --- a/Source/Tabbar/Tabbar.cpp +++ b/Source/Tabbar/Tabbar.cpp @@ -463,7 +463,7 @@ void TabComponent::resized() auto content = getLocalBounds(); newButton.setBounds(3, 0, tabDepth, tabDepth); // slighly offset to make it centred next to the tabs - auto tabBounds = content.removeFromTop(tabDepth).withTrimmedLeft(tabDepth); + auto tabBounds = content.removeFromTop(tabDepth).withTrimmedLeft(tabDepth).translated(0, -1); tabs->setBounds(tabBounds); for (int c = 0; c < tabs->getNumTabs(); c++) { @@ -482,23 +482,6 @@ Component* TabComponent::getTabContentComponent(int tabIndex) const noexcept return contentComponents[tabIndex].get(); } -void TabComponent::paint(Graphics& g) -{ - auto backgroundColour = findColour(PlugDataColour::tabBackgroundColourId); - - if (ProjectInfo::isStandalone && !editor->isActiveWindow()) { - backgroundColour = backgroundColour.brighter(backgroundColour.getBrightness() / 2.5f); - } - - g.fillAll(backgroundColour); -} - -void TabComponent::paintOverChildren(Graphics& g) -{ - g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); - g.drawLine(0, tabDepth, getWidth(), tabDepth); -} - int TabComponent::getIndexOfCanvas(Canvas* cnv) { if (!cnv->viewport || !cnv->editor) diff --git a/Source/Tabbar/Tabbar.h b/Source/Tabbar/Tabbar.h index acb9150a6e..c4d2761a6b 100644 --- a/Source/Tabbar/Tabbar.h +++ b/Source/Tabbar/Tabbar.h @@ -84,10 +84,6 @@ class TabComponent : public Component void handleAsyncUpdate() override; void resized() override; - void paint(Graphics& g) override; - - void paintOverChildren(Graphics& g) override; - int getIndexOfCanvas(Canvas* cnv); void setTabText(int tabIndex, String const& newName); diff --git a/Source/Utility/ValueTreeViewer.h b/Source/Utility/ValueTreeViewer.h index 99e987ff74..58c19f4bca 100644 --- a/Source/Utility/ValueTreeViewer.h +++ b/Source/Utility/ValueTreeViewer.h @@ -89,7 +89,7 @@ class ValueTreeNodeComponent : public Component p.lineTo(0.5f, 0.5f); p.lineTo(isOpen() ? 1.0f : 0.0f, isOpen() ? 0.0f : 1.0f); - g.setColour(isSelected() ? findColour(PlugDataColour::sidebarActiveTextColourId) : getOwnerView()->findColour(PlugDataColour::sidebarTextColourId)); + g.setColour(getOwnerView()->findColour(PlugDataColour::sidebarTextColourId)); g.strokePath(p, PathStrokeType(1.5f, PathStrokeType::curved, PathStrokeType::rounded), p.getTransformToScaleToFit(arrowArea, true)); } @@ -171,8 +171,7 @@ class ValueTreeNodeComponent : public Component paintOpenCloseButton(g, arrowBounds); } - auto& owner = *getOwnerView(); - auto colour = isSelected() ? owner.findColour(PlugDataColour::sidebarActiveTextColourId) : getOwnerView()->findColour(PlugDataColour::sidebarTextColourId); + auto colour = getOwnerView()->findColour(PlugDataColour::sidebarTextColourId); if(valueTreeNode.hasProperty("Icon")) { From 112676f274bc84d0ad9b6c75e860ff99fda9ab0d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 30 May 2024 16:04:53 +0200 Subject: [PATCH 0818/1030] Remove old colour ids from preset themes --- Source/LookAndFeel.cpp | 44 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index 8ac68ef30b..58a3619dcd 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -1496,11 +1496,11 @@ const String PlugDataLook::defaultThemesXml = " " " toolbar_outline_colour=\"ff393939\" outline_colour=\"ff393939\" data_colour=\"ff72aedf\"\n" " connection_colour=\"ffb3b3b3\" signal_colour=\"ffe1ef00\" gem_colour=\"ff01be00\" dialog_background=\"ff3b3b3b\"\n" " sidebar_colour=\"ff3e3e3e\" sidebar_text=\"ffe4e4e4\" sidebar_background_active=\"ff4f4f4f\"\n" -" sidebar_active_text=\"ffe4e4e4\" levelmeter_active=\"ff72aedf\" levelmeter_background=\"ff494949\"\n" +" levelmeter_active=\"ff72aedf\" levelmeter_background=\"ff494949\"\n" " levelmeter_thumb=\"ffe4e4e4\" panel_background=\"ff4f4f4f\" panel_foreground=\"ff3f3f3f\"\n" -" panel_text=\"ffe4e4e4\" panel_background_active=\"ff525252\" panel_active_text=\"ffe4e4e4\"\n" +" panel_text=\"ffe4e4e4\" panel_background_active=\"ff525252\"\n" " popup_background=\"ff333333\" popup_background_active=\"ff72aedf\"\n" -" popup_text=\"ffe4e4e4\" popup_active_text=\"ffe4e4e4\" slider_thumb=\"ff72aedf\"\n" +" popup_text=\"ffe4e4e4\"\n" " scrollbar_thumb=\"ffa9a9a9\" graph_area=\"ffff0000\" grid_colour=\"ff72aedf\"\n" " caret_colour=\"ff72aedf\" iolet_area_colour=\"ff808080\" iolet_outline_colour=\"ff696969\"\n" " text_object_background=\"ff333333\" comment_text_colour=\"ff111111\"\n" @@ -1516,12 +1516,12 @@ const String PlugDataLook::defaultThemesXml = " " " outline_colour=\"ff000000\" iolet_area_colour=\"ffffffff\" iolet_outline_colour=\"ff000000\"\n" " data_colour=\"ff000000\" connection_colour=\"ff000000\" signal_colour=\"ff000000\" gem_colour=\"ff000000\"\n" " dialog_background=\"ffffffff\" sidebar_colour=\"ffefefef\" sidebar_text=\"ff000000\"\n" -" sidebar_background_active=\"ffa0a0a0\" sidebar_active_text=\"ff000000\"\n" +" sidebar_background_active=\"ffa0a0a0\"\n" " levelmeter_active=\"ff000000\" levelmeter_background=\"ffededed\"\n" " levelmeter_thumb=\"ff000000\" panel_background=\"ffffffff\" panel_foreground=\"ffffffff\"\n" -" panel_text=\"ff000000\" panel_background_active=\"ff000000\" panel_active_text=\"ffffffff\"\n" +" panel_text=\"ff000000\" panel_background_active=\"ff000000\"\n" " popup_background=\"ffffffff\" popup_background_active=\"ff000000\"\n" -" popup_text=\"ff000000\" popup_active_text=\"ffffffff\" slider_thumb=\"ff000000\"\n" +" popup_text=\"ff000000\"\n" " scrollbar_thumb=\"ffa9a9a9\" graph_area=\"ffff0000\" grid_colour=\"ff000000\"\n" " caret_colour=\"ff000000\" comment_text_colour=\"ff000000\"\n" " dashed_signal_connections=\"0\" straight_connections=\"1\" thin_connections=\"1\"\n" @@ -1535,11 +1535,11 @@ const String PlugDataLook::defaultThemesXml = " " " toolbar_outline_colour=\"ffffffff\" outline_colour=\"ffffffff\" data_colour=\"ffffffff\"\n" " connection_colour=\"ffffffff\" signal_colour=\"ffffffff\" gem_colour=\"ffffffff\" dialog_background=\"ff000000\"\n" " sidebar_colour=\"ff000000\" sidebar_text=\"ffffffff\" sidebar_background_active=\"ffa0a0a0\"\n" -" sidebar_active_text=\"ffffffff\" levelmeter_active=\"ffffffff\" levelmeter_background=\"ff808080\"\n" +" levelmeter_active=\"ffffffff\" levelmeter_background=\"ff808080\"\n" " levelmeter_thumb=\"ffffffff\" panel_background=\"ff0e0e0e\" panel_foreground=\"ff000000\"\n" -" panel_text=\"ffffffff\" panel_background_active=\"ffffffff\" panel_active_text=\"ff000000\"\n" +" panel_text=\"ffffffff\" panel_background_active=\"ffffffff\"\n" " popup_background=\"ff000000\" popup_background_active=\"ffffffff\"\n" -" popup_text=\"ffffffff\" popup_active_text=\"ff000000\" slider_thumb=\"ffffffff\"\n" +" popup_text=\"ffffffff\"\n" " scrollbar_thumb=\"ff7f7f7f\" graph_area=\"ffff0000\" grid_colour=\"ffffffff\"\n" " caret_colour=\"ffffffff\" iolet_area_colour=\"ff000000\" iolet_outline_colour=\"ffffffff\"\n" " text_object_background=\"ff000000\" comment_text_colour=\"ffffffff\"\n" @@ -1554,11 +1554,11 @@ const String PlugDataLook::defaultThemesXml = " " " toolbar_outline_colour=\"ff2f2f2f\" outline_colour=\"ff393939\" data_colour=\"ff42a2c8\"\n" " connection_colour=\"ffe1e1e1\" signal_colour=\"ffff8500\" gem_colour=\"ff01be00\" dialog_background=\"ff191919\"\n" " sidebar_colour=\"ff191919\" sidebar_text=\"ffe1e1e1\" sidebar_background_active=\"ff282828\"\n" -" sidebar_active_text=\"ffe1e1e1\" levelmeter_active=\"ff42a2c8\" levelmeter_background=\"ff2e2e2e\"\n" +" levelmeter_active=\"ff42a2c8\" levelmeter_background=\"ff2e2e2e\"\n" " levelmeter_thumb=\"ffe3e3e3\" panel_background=\"ff2c2c2c\" panel_foreground=\"ff1f1f1f\"\n" -" panel_text=\"ffe1e1e1\" panel_background_active=\"ff373737\" panel_active_text=\"ffe1e1e1\"\n" +" panel_text=\"ffe1e1e1\" panel_background_active=\"ff373737\"\n" " popup_background=\"ff191919\" popup_background_active=\"ff282828\"\n" -" popup_text=\"ffe1e1e1\" popup_active_text=\"ffe1e1e1\" slider_thumb=\"ff42a2c8\"\n" +" popup_text=\"ffe1e1e1\"\n" " scrollbar_thumb=\"ff7f7f7f\" graph_area=\"ffff0000\" grid_colour=\"ff42a2c8\"\n" " caret_colour=\"ff42a2c8\" text_object_background=\"ff232323\" iolet_area_colour=\"ff232323\"\n" " iolet_outline_colour=\"ff696969\" comment_text_colour=\"ffe1e1e1\"\n" @@ -1573,11 +1573,11 @@ const String PlugDataLook::defaultThemesXml = " " " toolbar_outline_colour=\"ffdfdfdf\" outline_colour=\"ffd0d0d0\" data_colour=\"ff007aff\"\n" " connection_colour=\"ffb3b3b3\" signal_colour=\"ffff8500\" gem_colour=\"ff01de00\" dialog_background=\"ffebebeb\"\n" " sidebar_colour=\"ffefefef\" sidebar_text=\"ff373737\" sidebar_background_active=\"ffe4e4e4\"\n" -" sidebar_active_text=\"ff373737\" levelmeter_active=\"ff007aff\" levelmeter_background=\"ffe1e1e1\"\n" +" levelmeter_active=\"ff007aff\" levelmeter_background=\"ffe1e1e1\"\n" " levelmeter_thumb=\"ff9a9a9a\" panel_background=\"fff7f7f7\" panel_foreground=\"fffdfdfd\"\n" -" panel_text=\"ff373737\" panel_background_active=\"ffececec\" panel_active_text=\"ff373737\"\n" +" panel_text=\"ff373737\" panel_background_active=\"ffececec\"\n" " popup_background=\"ffe8e8e8\" popup_background_active=\"ffdcdcdc\"\n" -" popup_text=\"ff373737\" popup_active_text=\"ff373737\" slider_thumb=\"ff007aff\"\n" +" popup_text=\"ff373737\"\n" " scrollbar_thumb=\"ffa9a9a9\" graph_area=\"ffff0000\" grid_colour=\"ff007aff\"\n" " caret_colour=\"ff007aff\" square_object_corners=\"0\" text_object_background=\"fffafafa\"\n" " iolet_area_colour=\"fffafafa\" iolet_outline_colour=\"ffc2c2c2\"\n" @@ -1592,11 +1592,11 @@ const String PlugDataLook::defaultThemesXml = " " " toolbar_outline_colour=\"ffbdb3a4\" outline_colour=\"ff968e82\" data_colour=\"ff5da0c4\"\n" " connection_colour=\"ffb3b3b3\" signal_colour=\"ffff8502\" gem_colour=\"ff01be00\" dialog_background=\"ffd2cdc4\"\n" " sidebar_colour=\"ffdedad3\" sidebar_text=\"ff5a5a5a\" sidebar_background_active=\"ffefefef\"\n" -" sidebar_active_text=\"ff5a5a5a\" levelmeter_active=\"ff5da0c4\" levelmeter_background=\"ffc0bbb2\"\n" +" levelmeter_active=\"ff5da0c4\" levelmeter_background=\"ffc0bbb2\"\n" " levelmeter_thumb=\"ff7a7a7a\" panel_background=\"ffd2cdc4\" panel_foreground=\"ffe7e2d8\"\n" -" panel_text=\"ff5a5a5a\" panel_background_active=\"ffebebeb\" panel_active_text=\"ff5a5a5a\"\n" +" panel_text=\"ff5a5a5a\" panel_background_active=\"ffebebeb\"\n" " popup_background=\"ffd2cdc4\" popup_background_active=\"ffc0bbb2\"\n" -" popup_text=\"ff5a5a5a\" popup_active_text=\"ff5a5a5a\" slider_thumb=\"ff5da0c4\"\n" +" popup_text=\"ff5a5a5a\"\n" " scrollbar_thumb=\"ffa9a9a9\" graph_area=\"ffff0000\" grid_colour=\"ff5da0c4\"\n" " caret_colour=\"ff5da0c4\" iolet_area_colour=\"ffe3dfd9\" iolet_outline_colour=\"ff968e82\"\n" " text_object_background=\"ffe3dfd9\" comment_text_colour=\"ff5a5a5a\"\n" @@ -1611,14 +1611,14 @@ const String PlugDataLook::defaultThemesXml = " " " toolbar_outline_colour=\"ff343434\" outline_colour=\"ff383838\" data_colour=\"ff5bcefa\"\n" " connection_colour=\"ffa0a0a0\" signal_colour=\"ffffacab\" gem_colour=\"ff01de00\" dialog_background=\"ff191919\"\n" " sidebar_colour=\"ff232323\" sidebar_text=\"ffffffff\" sidebar_background_active=\"ff383838\"\n" -" sidebar_active_text=\"ffffffff\" levelmeter_active=\"ff5bcefa\" levelmeter_background=\"ff3a3a3a\"\n" +" levelmeter_active=\"ff5bcefa\" levelmeter_background=\"ff3a3a3a\"\n" " levelmeter_thumb=\"fff5f5f5\" panel_background=\"ff2c2c2c\" panel_foreground=\"ff1f1f1f\"\n" -" panel_text=\"ffffffff\" panel_background_active=\"ff232323\" panel_active_text=\"ffffffff\"\n" +" panel_text=\"ffffffff\" panel_background_active=\"ff232323\"\n" " popup_background=\"ff232323\" popup_background_active=\"ff383838\"\n" -" popup_text=\"ffffffff\" popup_active_text=\"ffffffff\" scrollbar_thumb=\"ff8e8e8e\"\n" +" popup_text=\"ffffffff\" scrollbar_thumb=\"ff8e8e8e\"\n" " graph_area=\"ff5bcefa\" grid_colour=\"ffff0000\" caret_colour=\"ffffacab\"\n" " text_object_background=\"ff232323\" iolet_area_colour=\"ff232323\"\n" -" iolet_outline_colour=\"ff696969\" slider_thumb=\"ff8e8e8e\" comment_text_colour=\"ffffffff\"\n" +" iolet_outline_colour=\"ff696969\" comment_text_colour=\"ffffffff\"\n" " dashed_signal_connections=\"1\" straight_connections=\"0\"\n" " thin_connections=\"1\" square_iolets=\"1\" square_object_corners=\"0\"/>\n" " "; From ea39a8644bfd374bf3732df95e10c239a4fad2dd Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 30 May 2024 16:07:03 +0200 Subject: [PATCH 0819/1030] Reorganise theme categories --- Source/LookAndFeel.h | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/Source/LookAndFeel.h b/Source/LookAndFeel.h index 284b0d1d50..a1eb90454c 100644 --- a/Source/LookAndFeel.h +++ b/Source/LookAndFeel.h @@ -16,30 +16,27 @@ inline std::map> const PlugDa { toolbarTextColourId, { "Toolbar text", "toolbar_text", "Toolbar" } }, { toolbarHoverColourId, { "Toolbar hover", "toolbar_hover", "Toolbar" } }, { toolbarActiveColourId, { "Toolbar active text", "toolbar_active", "Toolbar" } }, - { activeTabBackgroundColourId, { "Selected tab background", "selected_tab_background", "Tabbar" } }, + { activeTabBackgroundColourId, { "Selected tab background", "selected_tab_background", "Toolbar" } }, { canvasBackgroundColourId, { "Canvas background", "canvas_background", "Canvas" } }, { canvasTextColourId, { "Canvas text", "canvas_text", "Canvas" } }, { canvasDotsColourId, { "Canvas dots colour", "canvas_dots", "Canvas" } }, - { presentationBackgroundColourId, { "Presentation background", "presentation_background", "Canvas" } }, - - { guiObjectBackgroundColourId, { "GUI object background", "default_object_background", "Object" } }, - { guiObjectInternalOutlineColour, { "GUI object internal outline colour", "gui_internal_outline_colour", "Object" } }, - { textObjectBackgroundColourId, { "Object background", "text_object_background", "Object" } }, - { commentTextColourId, { "Comment text", "comment_text_colour", "Object" } }, - { objectOutlineColourId, { "Object outline", "object_outline_colour", "Object" } }, - { objectSelectedOutlineColourId, { "Selected object outline", "selected_object_outline_colour", "Object" } }, - - { ioletAreaColourId, { "Inlet/Outlet area", "iolet_area_colour", "Inlet/Outlet" } }, - { ioletOutlineColourId, { "Inlet/Outlet outline", "iolet_outline_colour", "Inlet/Outlet" } }, - { dataColourId, { "Data colour", "data_colour", "Canvas" } }, { connectionColourId, { "Connection", "connection_colour", "Canvas" } }, { signalColourId, { "Signal", "signal_colour", "Canvas" } }, { gemColourId, { "Gem", "gem_colour", "Canvas" } }, { graphAreaColourId, { "Graph resizer", "graph_area", "Canvas" } }, { gridLineColourId, { "Grid line", "grid_colour", "Canvas" } }, + + { guiObjectBackgroundColourId, { "GUI object background", "default_object_background", "Object" } }, + { guiObjectInternalOutlineColour, { "GUI object internal outline colour", "gui_internal_outline_colour", "Object" } }, + { textObjectBackgroundColourId, { "Object background", "text_object_background", "Object" } }, + { commentTextColourId, { "Comment text", "comment_text_colour", "Object" } }, + { objectOutlineColourId, { "Object outline", "object_outline_colour", "Object" } }, + { objectSelectedOutlineColourId, { "Selected object outline", "selected_object_outline_colour", "Object" } }, + { ioletAreaColourId, { "Inlet/Outlet area", "iolet_area_colour", "Object" } }, + { ioletOutlineColourId, { "Inlet/Outlet outline", "iolet_outline_colour", "Object" } }, { popupMenuBackgroundColourId, { "Popup menu background", "popup_background", "Popup Menu" } }, { popupMenuActiveBackgroundColourId, { "Popup menu background active", "popup_background_active", "Popup Menu" } }, @@ -49,17 +46,16 @@ inline std::map> const PlugDa { dialogBackgroundColourId, { "Dialog background", "dialog_background", "Other" } }, { caretColourId, { "Text editor caret", "caret_colour", "Other" } }, { toolbarOutlineColourId, { "Outline", "toolbar_outline_colour", "Other" } }, - + { scrollbarThumbColourId, { "Scrollbar thumb", "scrollbar_thumb", "Other" } }, + { levelMeterActiveColourId, { "Level meter active", "levelmeter_active", "Level Meter" } }, { levelMeterBackgroundColourId, { "Level meter track", "levelmeter_background", "Level Meter" } }, { levelMeterThumbColourId, { "Level meter thumb", "levelmeter_thumb", "Level Meter" } }, - { scrollbarThumbColourId, { "Scrollbar thumb", "scrollbar_thumb", "Other" } }, - - { panelBackgroundColourId, { "Panel background", "panel_background", "Panel" } }, - { panelForegroundColourId, { "Panel foreground", "panel_foreground", "Panel" } }, - { panelTextColourId, { "Panel text", "panel_text", "Panel" } }, - { panelActiveBackgroundColourId, { "Panel background active", "panel_background_active", "Panel" } }, + { panelBackgroundColourId, { "Panel background", "panel_background", "Properties Panel" } }, + { panelForegroundColourId, { "Panel foreground", "panel_foreground", "Properties Panel" } }, + { panelTextColourId, { "Panel text", "panel_text", "Properties Panel" } }, + { panelActiveBackgroundColourId, { "Panel background active", "panel_background_active", "Properties Panel" } }, { sidebarBackgroundColourId, { "Sidebar background", "sidebar_colour", "Sidebar" } }, { sidebarTextColourId, { "Sidebar text", "sidebar_text", "Sidebar" } }, From 2cde70061f240f880846f35995106391dc7eb230 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 30 May 2024 16:16:02 +0200 Subject: [PATCH 0820/1030] Fixed audio problem --- Source/LookAndFeel.h | 2 +- Source/Standalone/InternalSynth.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/LookAndFeel.h b/Source/LookAndFeel.h index a1eb90454c..94615748ff 100644 --- a/Source/LookAndFeel.h +++ b/Source/LookAndFeel.h @@ -53,7 +53,7 @@ inline std::map> const PlugDa { levelMeterThumbColourId, { "Level meter thumb", "levelmeter_thumb", "Level Meter" } }, { panelBackgroundColourId, { "Panel background", "panel_background", "Properties Panel" } }, - { panelForegroundColourId, { "Panel foreground", "panel_foreground", "Properties Panel" } }, + { panelForegroundColourId, { "Panel foreground", "panel_foreground", "Properties Panel" } }, { panelTextColourId, { "Panel text", "panel_text", "Properties Panel" } }, { panelActiveBackgroundColourId, { "Panel background active", "panel_background_active", "Properties Panel" } }, diff --git a/Source/Standalone/InternalSynth.cpp b/Source/Standalone/InternalSynth.cpp index 81040614e8..5453c97703 100644 --- a/Source/Standalone/InternalSynth.cpp +++ b/Source/Standalone/InternalSynth.cpp @@ -176,7 +176,6 @@ void InternalSynth::process(AudioBuffer& buffer, MidiBuffer& midiMessages } } - buffer.clear(); internalBuffer.clear(); // Run audio through fluidsynth From 6300d8223fe67424637646b520bcf9e9acb94675 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 30 May 2024 16:21:00 +0200 Subject: [PATCH 0821/1030] Make sure autosave can never be faster than 15 seconds, default dark theme improvement --- Source/LookAndFeel.cpp | 2 +- Source/Utility/Autosave.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index 58a3619dcd..642c20d2a8 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -1549,7 +1549,7 @@ const String PlugDataLook::defaultThemesXml = " " " toolbar_active=\"ff42a2c8\" toolbar_hover=\"ff282828\" tabbar_background=\"ff191919\"\n" " tab_text=\"ffe1e1e1\" selected_tab_background=\"ff2e2e2e\" selected_tab_text=\"ffe1e1e1\"\n" " canvas_background=\"ff232323\" canvas_text=\"ffe1e1e1\" canvas_dots=\"ff7f7f7f\" presentation_background=\"ff2e2e2e\"\n" -" default_object_background=\"ff191919\" object_outline_colour=\"ff696969\"\n" +" default_object_background=\"ff191919\" object_outline_colour=\"ff555555\"\n" " selected_object_outline_colour=\"ff42a2c8\" gui_internal_outline_colour=\"ff696969\"\n" " toolbar_outline_colour=\"ff2f2f2f\" outline_colour=\"ff393939\" data_colour=\"ff42a2c8\"\n" " connection_colour=\"ffe1e1e1\" signal_colour=\"ffff8500\" gem_colour=\"ff01be00\" dialog_background=\"ff191919\"\n" diff --git a/Source/Utility/Autosave.h b/Source/Utility/Autosave.h index a0c20fc030..439f9f1ff2 100644 --- a/Source/Utility/Autosave.h +++ b/Source/Utility/Autosave.h @@ -33,7 +33,7 @@ class Autosave : public Timer // autosave timer trigger autosaveInterval.referTo(SettingsFile::getInstance()->getPropertyAsValue("autosave_interval")); autosaveInterval.addListener(this); - startTimer(1000 * getValue(autosaveInterval)); + startTimer(1000 * std::max(getValue(autosaveInterval), 15)); } // Call this whenever we load a file From 3c0aa2f1150eb5df9e0a2db77966ef4b877c3f85 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 30 May 2024 16:28:59 +0200 Subject: [PATCH 0822/1030] Small layouting fixes --- Source/LookAndFeel.cpp | 2 +- Source/PluginEditor.cpp | 2 +- Source/Statusbar.cpp | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index 642c20d2a8..00ddf4a4ef 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -354,7 +354,7 @@ void PlugDataLook::drawButtonText(Graphics& g, TextButton& button, bool isMouseO Font font(getTextButtonFont(button, button.getHeight())); if (static_cast(button.getProperties().getVarPointer("bold_text"))) { - font = Font(Fonts::getCurrentFont()).withHeight(button.getHeight() * 0.7f).withStyle(FontStyle::RegularBoldened); + font = Font(Fonts::getSemiBoldFont()).withHeight(button.getHeight() * 0.65f); } g.setFont(font); diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 29137effa5..68949e98ff 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -456,7 +456,7 @@ void PluginEditor::resized() runButton.setBounds(startX + buttonSize - 1, 1, buttonSize, buttonSize - 2); presentButton.setBounds(startX + (2 * buttonSize) - 2, 1, buttonSize, buttonSize - 2); - auto windowControlsOffset = (useNonNativeTitlebar && !useLeftButtons) ? 140.0f : 50.0f; + auto windowControlsOffset = (useNonNativeTitlebar && !useLeftButtons) ? 135.0f : 45.0f; if (borderResizer && ProjectInfo::isStandalone) { borderResizer->setBounds(getLocalBounds()); diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index c7f89a0860..9dc312e9f9 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -801,7 +801,6 @@ Statusbar::Statusbar(PluginProcessor* processor) }; addAndMakeVisible(overlaySettingsButton); - limiterButton.setLookAndFeel(&LookAndFeel::getDefaultLookAndFeel()); limiterButton.getProperties().set("bold_text", true); limiterButton.setClickingTogglesState(true); limiterButton.setToggleState(SettingsFile::getInstance()->getProperty("protected"), dontSendNotification); From a40eb1b1b24678e45a9dbeb6a6b79ba215d9b61d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 30 May 2024 16:51:38 +0200 Subject: [PATCH 0823/1030] Compilation fix --- Source/Pd/Setup.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Source/Pd/Setup.cpp b/Source/Pd/Setup.cpp index 98eee4082b..04d22599d3 100644 --- a/Source/Pd/Setup.cpp +++ b/Source/Pd/Setup.cpp @@ -1250,8 +1250,6 @@ void conv_tilde_setup(); void fm_tilde_setup(); -void vstplugin_tilde_setup(); - #ifdef ENABLE_SFIZZ void sfz_tilde_setup(); #endif @@ -1727,8 +1725,6 @@ void Setup::initialiseELSE() var_setup(); conv_tilde_setup(); fm_tilde_setup(); - - vstplugin_tilde_setup(); } void Setup::initialiseGem(std::string const& gemPluginPath) From acad3e5c0a772d166b34998eca4dcdf3381cc50d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 30 May 2024 16:52:05 +0200 Subject: [PATCH 0824/1030] Undo accidental changes --- Libraries/CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index a289b7ab8c..cac0e5467e 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -28,7 +28,6 @@ if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.21") endif() set(PD_DIR "pure-data/") -add_subdirectory(vstplugin) # ------------------------------------------------------------------------------# set(CMAKE_MACOSX_RPATH ON) @@ -442,8 +441,6 @@ set(externals_libs fluidlite lua) if(ENABLE_SFIZZ) list(APPEND externals_libs sfizz) endif() -target_link_libraries(externals vstplugin_tilde) -target_link_libraries(externals-multi vstplugin_tilde_multi) if("${CMAKE_SYSTEM}" MATCHES "Linux") add_library(pd INTERFACE) From 2136efbf7e6de0c78c4ca61239c994a350ea04ba Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 30 May 2024 16:54:12 +0200 Subject: [PATCH 0825/1030] iOS fix --- Libraries/JUCE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/JUCE b/Libraries/JUCE index 391015a7bd..d9d358251f 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit 391015a7bd458827a0c380109d2887ba891851d6 +Subproject commit d9d358251fdd17e0796f062bb35b2415d5ea5250 From e877ee1ca3698c2c2d6ede374700c3b50084f626 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 30 May 2024 22:08:32 +0200 Subject: [PATCH 0826/1030] Small layout improvement --- Source/LookAndFeel.cpp | 2 +- Source/PluginEditor.h | 2 +- Source/Tabbar/TabBarButtonComponent.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index 00ddf4a4ef..01a903370d 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -547,7 +547,7 @@ void PlugDataLook::drawTabButton(TabBarButton& button, Graphics& g, bool isMouse g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); } - fillSmoothedRectangle(g, button.getLocalBounds().reduced(4).toFloat(), Corners::defaultCornerRadius); + fillSmoothedRectangle(g, button.getLocalBounds().toFloat().reduced(4.5f), Corners::defaultCornerRadius); drawTabButtonText(button, g, isMouseOver, isMouseDown); } diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 243afe3e02..945a8c4989 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -202,7 +202,7 @@ class PluginEditor : public AudioProcessorEditor // Used by standalone to handle dragging the window WindowDragger windowDragger; - int const toolbarHeight = 32; + int const toolbarHeight = 34; MainToolbarButton mainMenuButton, undoButton, redoButton, addObjectMenuButton, pluginModeButton; ToolbarRadioButton editButton, runButton, presentButton; diff --git a/Source/Tabbar/TabBarButtonComponent.cpp b/Source/Tabbar/TabBarButtonComponent.cpp index 2cf6d58727..c68ec0b992 100644 --- a/Source/Tabbar/TabBarButtonComponent.cpp +++ b/Source/Tabbar/TabBarButtonComponent.cpp @@ -341,7 +341,7 @@ void TabBarButtonComponent::drawTabButton(Graphics& g, Rectangle customBoun if (!customBounds.isEmpty()) bounds = customBounds; - g.fillRoundedRectangle(bounds.reduced(4).toFloat(), Corners::defaultCornerRadius); + g.fillRoundedRectangle(bounds.toFloat().reduced(4.5f), Corners::defaultCornerRadius); } // FIXME: we are only using this to draw the DnD tab image From 4190ff7f9eebbbae6adb695e5cba1027d90134e7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 30 May 2024 23:29:10 +0200 Subject: [PATCH 0827/1030] More iOS fixes --- Source/NVGSurface.cpp | 5 +---- Source/Statusbar.cpp | 10 +++++----- Source/Utility/OSUtils.h | 2 -- Source/Utility/OSUtils.mm | 6 ------ 4 files changed, 6 insertions(+), 17 deletions(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 3e44bd2836..307463e5d4 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -254,10 +254,7 @@ void NVGSurface::resized() auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); auto renderScale = OSUtils::MTLGetPixelScale(view); // TODO: we can simplify with getRenderScale() function, but needs testing on iOS auto* topLevel = getTopLevelComponent(); - auto bounds = topLevel->getLocalArea(this, getLocalBounds()) * desktopScale; -#if JUCE_IOS - OSUtils::MTLResizeView(view, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); -#endif + auto bounds = topLevel->getLocalArea(this, getLocalBounds()).toFloat() * desktopScale; mnvgSetViewBounds(view, (renderScale * bounds.getWidth()), (renderScale * bounds.getHeight())); } #endif diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 9dc312e9f9..c2df247bd6 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -875,16 +875,16 @@ void Statusbar::resized() auto spacing = getHeight(); - zoomLabel->setBounds(position(34), 0, 34, getHeight()); - zoomComboButton.setBounds(position(8) - 11, 0, getHeight(), getHeight()); - - firstSeparatorPosition = position(4) + 3.5f; // fifth seperator - // Some newer iPhone models have a very large corner radius #if JUCE_IOS position(22); #endif + zoomLabel->setBounds(position(34), 0, 34, getHeight()); + zoomComboButton.setBounds(position(8) - 11, 0, getHeight(), getHeight()); + + firstSeparatorPosition = position(4) + 3.5f; // fifth seperator + centreButton.setBounds(position(spacing), 0, getHeight(), getHeight()); secondSeparatorPosition = position(4) + 1.f; // Second seperator diff --git a/Source/Utility/OSUtils.h b/Source/Utility/OSUtils.h index bd279194d8..3bfc2353b5 100644 --- a/Source/Utility/OSUtils.h +++ b/Source/Utility/OSUtils.h @@ -92,8 +92,6 @@ struct OSUtils { static bool isIPad(); static void showMobileMainMenu(juce::ComponentPeer* peer, std::function callback); static void showMobileCanvasMenu(juce::ComponentPeer* peer, std::function callback); - - static void MTLResizeView(void* view, int x, int y, int width, int height); #endif }; diff --git a/Source/Utility/OSUtils.mm b/Source/Utility/OSUtils.mm index d55167cdab..d6dac2abc4 100644 --- a/Source/Utility/OSUtils.mm +++ b/Source/Utility/OSUtils.mm @@ -590,12 +590,6 @@ - (void)commonInit { return reinterpret_cast(view).window.screen.scale; } -void OSUtils::MTLResizeView(void* view, int x, int y, int width, int height) -{ - CGRect bounds = CGRectMake(x, y, width, height); - [(__bridge UIView*)view setFrame:bounds]; -} - void* OSUtils::MTLCreateView(void* parent, int x, int y, int width, int height) { // Create child view From a1ebad2556f946267e23c924a0d4e88fd991fcba Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 31 May 2024 00:16:29 +0200 Subject: [PATCH 0828/1030] iOS fixes, cleaned up --- Source/Components/DraggableNumber.h | 2 +- Source/Components/SuggestionComponent.h | 2 +- Source/Components/TouchSelectionHelper.h | 14 +++++-- Source/NVGSurface.cpp | 41 +++------------------ Source/NVGSurface.h | 4 +- Source/Objects/KeyboardObject.h | 2 +- Source/Objects/NumboxTildeObject.h | 2 +- Source/PluginEditor.cpp | 47 +++++++++++++++++++++--- Source/PluginEditor.h | 2 + Source/Tabbar/WelcomePanel.h | 2 +- Source/Utility/NVGComponent.h | 12 ++---- 11 files changed, 69 insertions(+), 61 deletions(-) diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index b1634ddc4a..3098397495 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -281,7 +281,7 @@ class DraggableNumber : public Label void render(NVGcontext* nvg) { - if(!nvgCtx || nvgCtx->getContext() != nvg) nvgCtx = std::make_unique(nvg); + if(!nvgCtx) nvgCtx = std::make_unique(nvg); nvgCtx->setPhysicalPixelScaleFactor(2.0f); Graphics g(*nvgCtx); { diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index 90a3cba8ec..1cd69a8ac2 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -108,7 +108,7 @@ class AutoCompleteComponent { nvgSave(nvg); nvgTranslate(nvg, getX(), getY()); - if(!nvgCtx || nvgCtx->getContext() != nvg) nvgCtx = std::make_unique(nvg); + if(!nvgCtx) nvgCtx = std::make_unique(nvg); Graphics g(*nvgCtx); { paintEntireComponent(g, true); diff --git a/Source/Components/TouchSelectionHelper.h b/Source/Components/TouchSelectionHelper.h index d4c594f04b..1dd805c84c 100644 --- a/Source/Components/TouchSelectionHelper.h +++ b/Source/Components/TouchSelectionHelper.h @@ -12,18 +12,20 @@ #include "PluginEditor.h" #include "Objects/ObjectBase.h" -class TouchSelectionHelper : public Component { +class TouchSelectionHelper : public Component, public NVGComponent { PluginEditor* editor; public: TouchSelectionHelper(PluginEditor* e) - : editor(e) + : NVGComponent(this), editor(e) { addAndMakeVisible(actionButtons.add(new MainToolbarButton(Icons::ExportState))); // This icon doubles as a "open" icon in the mobile app addAndMakeVisible(actionButtons.add(new MainToolbarButton(Icons::Help))); addAndMakeVisible(actionButtons.add(new MainToolbarButton(Icons::Trash))); addAndMakeVisible(actionButtons.add(new MainToolbarButton(Icons::More))); + + setCachedComponentImage(new NVGSurface::InvalidationListener(e->nvgSurface, this)); actionButtons[0]->onClick = [this]() { auto* cnv = editor->getCurrentCanvas(); @@ -95,8 +97,8 @@ class TouchSelectionHelper : public Component { { actionButtons[1]->setEnabled(editor->isCommandActive(CommandIDs::ShowHelp)); actionButtons[2]->setEnabled(editor->isCommandActive(CommandIDs::Delete)); - setVisible(true); + toFront(false); } void resized() override @@ -108,6 +110,11 @@ class TouchSelectionHelper : public Component { } } + void render(NVGcontext* nvg) override + { + imageRenderer.renderComponentFromImage(nvg, *this, 2.0f); + } + private: void paint(Graphics& g) override { @@ -124,5 +131,6 @@ class TouchSelectionHelper : public Component { g.drawRoundedRectangle(b.toFloat(), Corners::largeCornerRadius, 1.0f); } + NVGImageRenderer imageRenderer; OwnedArray actionButtons; }; diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 307463e5d4..e61d6dbe62 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -148,7 +148,7 @@ void NVGSurface::initialise() nvgCreateFontMem(nvg, "Inter-Bold", (unsigned char*)BinaryData::InterBold_ttf, BinaryData::InterBold_ttfSize, 0); nvgCreateFontMem(nvg, "Inter-SemiBold", (unsigned char*)BinaryData::InterSemiBold_ttf, BinaryData::InterSemiBold_ttfSize, 0); nvgCreateFontMem(nvg, "Inter-Tabular", (unsigned char*)BinaryData::InterTabular_ttf, BinaryData::InterTabular_ttfSize, 0); - nvgCreateFontMem(nvg, "Icon", (unsigned char*)BinaryData::IconFont_ttf, BinaryData::IconFont_ttfSize, 0); + nvgCreateFontMem(nvg, "icon_font-Regular", (unsigned char*)BinaryData::IconFont_ttf, BinaryData::IconFont_ttfSize, 0); } void NVGSurface::updateBufferSize() @@ -280,42 +280,11 @@ void NVGSurface::invalidateArea(Rectangle area) invalidArea = invalidArea.getUnion(area); } -void NVGSurface::renderArea(Rectangle area) -{ - if(editor->pluginMode) { - editor->pluginMode->render(nvg); - } - else { - bool hasCanvas = false; - for(auto* split : editor->splitView.splits) - { - if(auto* cnv = split->getTabComponent()->getCurrentCanvas()) - { - nvgSave(nvg); - nvgTranslate(nvg, split->getX(), 0); - nvgScissor(nvg, 0, 0, split->getWidth(), split->getHeight()); - cnv->performRender(nvg, area.translated(-split->getX(), 0)); - nvgRestore(nvg); - hasCanvas = true; - } - } - if(!hasCanvas) - { - nvgSave(nvg); - editor->welcomePanel->render(nvg); - nvgRestore(nvg); - } - } -} - void NVGSurface::render() { auto startTime = Time::getMillisecondCounter(); if(!isAttached() && isVisible()) initialise(); - updateBufferSize(); - - auto pixelScale = getRenderScale(); bool hasCanvas = false; for(auto* split : editor->splitView.splits) @@ -325,19 +294,19 @@ void NVGSurface::render() hasCanvas = true; } } - // Manage showing/hiding welcome panel if(hasCanvas && editor->welcomePanel->isVisible()) { editor->welcomePanel->hide(); editor->resized(); - updateBufferSize(); } else if(!hasCanvas && !editor->welcomePanel->isVisible()) { editor->welcomePanel->show(); editor->resized(); - updateBufferSize(); } + updateBufferSize(); + + auto pixelScale = getRenderScale(); if(!invalidArea.isEmpty() && makeContextActive()) { auto invalidated = invalidArea.expanded(1); @@ -350,7 +319,7 @@ void NVGSurface::render() nvgBeginFrame(nvg, getWidth(), getHeight(), pixelScale); nvgScissor (nvg, invalidated.getX(), invalidated.getY(), invalidated.getWidth(), invalidated.getHeight()); - renderArea(invalidated); + editor->renderArea(nvg, invalidated); nvgEndFrame(nvg); nvgBindFramebuffer(mainFBO); diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 84bde43d32..704ed9b25d 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -102,9 +102,7 @@ public Component, public Timer NVGcontext* getRawContext() { return nvg; } private: - - void renderArea(Rectangle area); - + bool isAttached() const; void resized() override; diff --git a/Source/Objects/KeyboardObject.h b/Source/Objects/KeyboardObject.h index c9820832d3..e7c88ab000 100644 --- a/Source/Objects/KeyboardObject.h +++ b/Source/Objects/KeyboardObject.h @@ -297,7 +297,7 @@ class KeyboardObject final : public ObjectBase void render(NVGcontext* nvg) override { - if(!nvgCtx || nvgCtx->getContext() != nvg) nvgCtx = std::make_unique(nvg); + if(!nvgCtx) nvgCtx = std::make_unique(nvg); Graphics g(*nvgCtx); { paintEntireComponent(g, true); diff --git a/Source/Objects/NumboxTildeObject.h b/Source/Objects/NumboxTildeObject.h index d5bbe030b4..9834f73134 100644 --- a/Source/Objects/NumboxTildeObject.h +++ b/Source/Objects/NumboxTildeObject.h @@ -260,7 +260,7 @@ class NumboxTildeObject final : public ObjectBase auto icon = mode ? Icons::ThinDown : Icons::Sine; auto iconBounds = Rectangle(7, 3, getHeight(), getHeight()); - nvgFontFace(nvg, "Icon"); + nvgFontFace(nvg, "icon_font-Regular"); nvgFontSize(nvg, 12.0f); nvgFillColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::dataColourId))); nvgTextAlign(nvg, NVG_ALIGN_TOP | NVG_ALIGN_LEFT); diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 68949e98ff..9e23294584 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -263,13 +263,14 @@ PluginEditor::PluginEditor(PluginProcessor& p) addChildComponent(&objectManager); objectManager.lookAndFeelChanged(); - -#if JUCE_IOS - addAndMakeVisible(touchSelectionHelper.get()); -#endif addChildComponent(nvgSurface); nvgSurface.toBehind(&splitView); + +#if JUCE_IOS + addAndMakeVisible(touchSelectionHelper.get()); + touchSelectionHelper->setAlwaysOnTop(true); +#endif } PluginEditor::~PluginEditor() @@ -363,6 +364,42 @@ void PluginEditor::paintOverChildren(Graphics& g) } } +void PluginEditor::renderArea(NVGcontext* nvg, Rectangle area) +{ + if(pluginMode) { + pluginMode->render(nvg); + } + else { + bool hasCanvas = false; + for(auto* split : splitView.splits) + { + if(auto* cnv = split->getTabComponent()->getCurrentCanvas()) + { + nvgSave(nvg); + nvgTranslate(nvg, split->getX(), 0); + nvgScissor(nvg, 0, 0, split->getWidth(), split->getHeight()); + cnv->performRender(nvg, area.translated(-split->getX(), 0)); + nvgRestore(nvg); + hasCanvas = true; + } + } + if(!hasCanvas) + { + nvgSave(nvg); + welcomePanel->render(nvg); + nvgRestore(nvg); + } + else { + if(touchSelectionHelper && touchSelectionHelper->isVisible() && area.intersects(touchSelectionHelper->getBounds() - nvgSurface.getPosition())) { + nvgSave(nvg); + nvgTranslate(nvg, touchSelectionHelper->getX() - nvgSurface.getX(), touchSelectionHelper->getY() - nvgSurface.getY()); + touchSelectionHelper->render(nvg); + nvgRestore(nvg); + } + } + } +} + CallOutBox& PluginEditor::showCalloutBox(std::unique_ptr content, Rectangle screenBounds) { class CalloutDeletionListener : public ComponentListener @@ -1823,7 +1860,7 @@ void PluginEditor::quit(bool askToSave) void PluginEditor::showTouchSelectionHelper(bool shouldBeShown) { - touchSelectionHelper->setVisible(shouldBeShown); + touchSelectionHelper->show(); if (shouldBeShown) { auto touchHelperBounds = getLocalBounds().removeFromBottom(48).withSizeKeepingCentre(192, 48).translated(0, -54); touchSelectionHelper->setBounds(touchHelperBounds); diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 945a8c4989..0bef449442 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -81,6 +81,8 @@ class PluginEditor : public AudioProcessorEditor void paint(Graphics& g) override; void paintOverChildren(Graphics& g) override; + + void renderArea(NVGcontext* nvg, Rectangle area); void initialiseCanvasRenderer(); diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Tabbar/WelcomePanel.h index 9a17f84c44..3b5804055e 100644 --- a/Source/Tabbar/WelcomePanel.h +++ b/Source/Tabbar/WelcomePanel.h @@ -74,7 +74,7 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater if(onFavourite) { auto favouriteIconBounds = getHeartIconBounds(); - nvgFontFace(nvg, "Icon"); + nvgFontFace(nvg, "icon_font-Regular"); if(isFavourited) { nvgFillColor(nvg, nvgRGBA(250, 50, 40, 200)); diff --git a/Source/Utility/NVGComponent.h b/Source/Utility/NVGComponent.h index f6d1690c1d..bcacc6ec3f 100644 --- a/Source/Utility/NVGComponent.h +++ b/Source/Utility/NVGComponent.h @@ -26,14 +26,7 @@ class NVGComponent class NVGImageRenderer { public: - NVGImageRenderer() - { - } - - ~NVGImageRenderer() - { - } - + static int convertImage(NVGcontext* nvg, Image& image, int imageToUpdate = -1) { Image::BitmapData imageData(image, Image::BitmapData::readOnly); @@ -72,7 +65,8 @@ class NVGImageRenderer void renderComponentFromImage(NVGcontext* nvg, Component& component, float scale) { Image componentImage = component.createComponentSnapshot(Rectangle(0, 0, component.getWidth() + 1, component.getHeight()), false, scale); - + if(componentImage.isNull()) return; + if(imageId && lastWidth == componentImage.getWidth() && lastHeight == componentImage.getHeight()) { imageId = convertImage(nvg, componentImage, imageId); } From e7690f75b28d541378f49dda2365073b820ab8dd Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 31 May 2024 00:18:57 +0200 Subject: [PATCH 0829/1030] Allow transparency in iOS --- Source/Utility/Config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Utility/Config.cpp b/Source/Utility/Config.cpp index 60f3c564cd..5360854e6f 100644 --- a/Source/Utility/Config.cpp +++ b/Source/Utility/Config.cpp @@ -72,7 +72,7 @@ void ProjectInfo::closeWindow(PlugDataWindow* window) bool ProjectInfo::canUseSemiTransparentWindows() { #if JUCE_IOS - return false; + return true; #endif #if !JUCE_MAC || PLUGDATA_STANDALONE return Desktop::canUseSemiTransparentWindows(); From ec0564f1063b8f61a27e8d0634ff733a78705f8d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 31 May 2024 01:19:49 +0200 Subject: [PATCH 0830/1030] Fixed jitter when scrolling --- Source/Object.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index 881e74a869..7526ae42ff 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -106,13 +106,13 @@ class InvalidationListener : public CachedComponentImage bool invalidate(const Rectangle& rect) override { object->fbDirty = true; - return !object->cnv->isScrolling; + return true; } bool invalidateAll() override { object->fbDirty = true; - return !object->cnv->isScrolling; + return true; } void releaseResources() override {} From ead81560b74409b799b96372c48060bab11e95cf Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 31 May 2024 03:28:05 +0200 Subject: [PATCH 0831/1030] Fixed fluidsynth bug --- Source/Standalone/InternalSynth.cpp | 2 +- Source/Utility/NanoVGGraphicsContext.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Standalone/InternalSynth.cpp b/Source/Standalone/InternalSynth.cpp index 5453c97703..53f2a0ede4 100644 --- a/Source/Standalone/InternalSynth.cpp +++ b/Source/Standalone/InternalSynth.cpp @@ -179,7 +179,7 @@ void InternalSynth::process(AudioBuffer& buffer, MidiBuffer& midiMessages internalBuffer.clear(); // Run audio through fluidsynth - fluid_synth_process(synth, buffer.getNumSamples(), buffer.getNumChannels(), const_cast(internalBuffer.getArrayOfReadPointers()), buffer.getNumChannels(), const_cast(internalBuffer.getArrayOfWritePointers())); + fluid_synth_process(synth, buffer.getNumSamples(), std::max(2, buffer.getNumChannels()), const_cast(internalBuffer.getArrayOfReadPointers()), std::max(2, buffer.getNumChannels()), const_cast(internalBuffer.getArrayOfWritePointers())); for (int ch = 0; ch < buffer.getNumChannels(); ch++) { buffer.addFrom(ch, 0, internalBuffer, ch, 0, buffer.getNumSamples()); diff --git a/Source/Utility/NanoVGGraphicsContext.cpp b/Source/Utility/NanoVGGraphicsContext.cpp index df72fa6d97..c30cd5489f 100644 --- a/Source/Utility/NanoVGGraphicsContext.cpp +++ b/Source/Utility/NanoVGGraphicsContext.cpp @@ -152,8 +152,9 @@ juce::Rectangle NanoVGGraphicsContext::getClipBounds() const bool NanoVGGraphicsContext::isClipEmpty() const { - //auto scissorBounds = nvgCurrentScissor(nvg); TODO: fix this! - return false; + float x, y, w, h; + nvgCurrentScissor(nvg, &x, &y, &w, &h); + return w <= 0 || h <= 0; } void NanoVGGraphicsContext::saveState() From 93c8952be1fbe67fa10517cd43fbb7ce7b936333 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 31 May 2024 03:41:04 +0200 Subject: [PATCH 0832/1030] Implement triggerize feature from pd-vanilla --- Source/Canvas.cpp | 20 +++++++++++++++++++- Source/Canvas.h | 1 + Source/Constants.h | 1 + Source/Pd/Interface.h | 15 +++++++++++++++ Source/PluginEditor.cpp | 11 +++++++++++ 5 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 01a010efc4..3a66fff8cb 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -1622,9 +1622,27 @@ void Canvas::removeSelectedConnections() synchroniseSplitCanvas(); } -void Canvas::encapsulateSelection() +void Canvas::triggerizeSelection() { + auto selectedBoxes = getSelectionOfType(); + + std::vector objects; + for (auto* object : getSelectionOfType()) { + if (auto* ptr = object->getPointer()) { + objects.push_back(ptr); + } + } + + if(auto patchPtr = patch.getPointer()) + { + pd::Interface::triggerize(patchPtr.get(), objects); + } + + synchronise(); +} +void Canvas::encapsulateSelection() +{ auto selectedBoxes = getSelectionOfType(); // Sort by index in pd patch diff --git a/Source/Canvas.h b/Source/Canvas.h index 38a7ffe746..7141a62e09 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -120,6 +120,7 @@ class Canvas : public Component void duplicateSelection(); void encapsulateSelection(); + void triggerizeSelection(); bool canConnectSelectedObjects(); bool connectSelectedObjects(); diff --git a/Source/Constants.h b/Source/Constants.h index 721a2e517f..508a40cbdc 100644 --- a/Source/Constants.h +++ b/Source/Constants.h @@ -325,6 +325,7 @@ enum CommandIDs { Delete, Duplicate, Encapsulate, + Triggerize, CreateConnection, RemoveConnections, SelectAll, diff --git a/Source/Pd/Interface.h b/Source/Pd/Interface.h index 7634581acf..cccc61ce00 100644 --- a/Source/Pd/Interface.h +++ b/Source/Pd/Interface.h @@ -233,6 +233,21 @@ struct Interface { return gensym(arraybuf); } + + static void triggerize(t_canvas* cnv,std::vector const& objects) + { + glist_noselect(cnv); + + for (auto* obj : objects) { + glist_select(cnv, obj); + } + + canvas_setcurrent(cnv); + pd_typedmess((t_pd*)cnv, gensym("triggerize"), 0, nullptr); + canvas_unsetcurrent(cnv); + + glist_noselect(cnv); + } static void paste(t_canvas* cnv, char const* buf) { diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 9e23294584..fadfa02cb8 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1236,6 +1236,12 @@ void PluginEditor::getCommandInfo(CommandID const commandID, ApplicationCommandI result.setActive(hasCanvas && !isDragging && !locked && hasSelection); break; } + case CommandIDs::Triggerize: { + result.setInfo("Triggerize", "Triggerize objects", "Edit", 0); + result.addDefaultKeypress(84, ModifierKeys::commandModifier); + result.setActive(hasCanvas && !isDragging && !locked && hasSelection); + break; + } case CommandIDs::Duplicate: { result.setInfo("Duplicate", "Duplicate selection", "Edit", 0); @@ -1560,6 +1566,11 @@ bool PluginEditor::perform(InvocationInfo const& info) cnv->encapsulateSelection(); return true; } + case CommandIDs::Triggerize: { + cnv = getCurrentCanvas(); + cnv->triggerizeSelection(); + return true; + } case CommandIDs::CreateConnection: { cnv = getCurrentCanvas(); return cnv->connectSelectedObjects(); From a4f2ecfbe43a199bc60c038e90f00c95f25dad2a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 31 May 2024 03:48:14 +0200 Subject: [PATCH 0833/1030] Fixed sidebar settings button not hiding --- Source/Sidebar/Sidebar.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/Sidebar/Sidebar.cpp b/Source/Sidebar/Sidebar.cpp index 33e615dbfc..4de8250a3f 100644 --- a/Source/Sidebar/Sidebar.cpp +++ b/Source/Sidebar/Sidebar.cpp @@ -345,10 +345,9 @@ void Sidebar::updateExtraSettingsButton() return; } - if(!extraSettingsButton->isVisible()) { - addAndMakeVisible(*extraSettingsButton); - resized(); - } + addChildComponent(extraSettingsButton.get()); + extraSettingsButton->setVisible(!isHidden()); + resized(); } void Sidebar::hideParameters() From 537455fb1aec0f9902147d6d2f9b119bdf7cac76 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 31 May 2024 14:04:21 +0930 Subject: [PATCH 0834/1030] fix overpainting of array object in Curve mode, make Curve drawing rounded edges & joins --- Source/Objects/ArrayObject.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index 3e80bf0f2d..e9a01bd7ee 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -196,6 +196,11 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess std::vector points = vec; if (!points.empty()) { + + nvgSave(nvg); + const auto arrB = Rectangle(0,0,w,h).reduced(1); + nvgRoundedScissor(nvg, arrB.getX(), arrB.getY(), arrB.getWidth(), arrB.getHeight(), Corners::objectCornerRadius); + std::array scale = getScale(); bool invert = false; @@ -234,6 +239,8 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess nvgTranslate(nvg, 0.0f, -getHeight()); } + nvgLineCap(nvg, NVG_ROUND); + nvgLineJoin(nvg, NVG_ROUND); nvgStrokeColor(nvg, nvgRGBAf(getContentColour().getFloatRed(), getContentColour().getFloatGreen(), getContentColour().getFloatBlue(), getContentColour().getFloatAlpha())); nvgStrokeWidth(nvg, getLineWidth()); nvgStroke(nvg); @@ -283,6 +290,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess default: break; } + nvgRestore(nvg); } } From 8fcd90cdb579072216f97f167d5376b67eb71e0d Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 31 May 2024 14:32:26 +0930 Subject: [PATCH 0835/1030] Actually use correct background colour for blending limiter button on state colour for contrasting text --- Source/Statusbar.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index c2df247bd6..5e16559561 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -954,7 +954,9 @@ void Statusbar::lookAndFeelChanged() return Colour::fromFloatRGBA(r, g, b, 1.0f); }; - auto blendedButtonColour = blendColours(findColour(PlugDataColour::panelBackgroundColourId), limiterButtonActiveColour); + // Blend the button colour & toolbar background colour to make sure that the button's 'on' text is visible + // as we are using the active colour with alpha to reduce how distracting the limiter button active state is. + auto blendedButtonColour = blendColours(findColour(PlugDataColour::toolbarBackgroundColourId), limiterButtonActiveColour); limiterButton.setColour(TextButton::textColourOffId, findColour(PlugDataColour::panelTextColourId)); limiterButton.setColour(TextButton::textColourOnId, blendedButtonColour.contrasting()); From 1048b02ac9140a0d63041074127daad1aec28cb1 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 31 May 2024 15:11:45 +0930 Subject: [PATCH 0836/1030] delete the previous presentation dropshadow image on GPU when bounds have changed --- Source/Canvas.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 3a66fff8cb..e9b03698a6 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -454,8 +454,11 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) const int shadowSize = 40; auto borderArea = Rectangle(0, 0, borderWidth, borderHeight); auto expanededBorder = borderArea.expanded(shadowSize); - if (lastPresentationBounds != borderArea || presentationShadowImage == -1) { + if (lastPresentationBounds != borderArea || presentationShadowImage == -1) + { lastPresentationBounds = borderArea; + if (presentationShadowImage) nvgDeleteImage(nvg, presentationShadowImage); + Image shadow(Image::ARGB, expanededBorder.getWidth(), expanededBorder.getHeight(), true); Graphics g(shadow); auto shadowPath = Path(); From 2348b2305ec2098691ca8e8b9382277621a0f10a Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 31 May 2024 16:18:19 +0930 Subject: [PATCH 0837/1030] refactor overlays that are part of canvas into canvas, not objects/connections objects / connections should ask cnv what state it is in --- Source/Canvas.cpp | 32 ++++++++++++++++++++++++-------- Source/Canvas.h | 10 ++++++++++ Source/Connection.cpp | 17 +++-------------- Source/Connection.h | 6 ------ Source/Object.cpp | 24 ++++-------------------- Source/Object.h | 4 ---- 6 files changed, 41 insertions(+), 52 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 3a66fff8cb..6eac57405f 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -638,6 +638,26 @@ void Canvas::propertyChanged(String const& name, var const& value) } } +bool Canvas::shouldShowObjectActivity() +{ + return showObjectActivity && !presentationMode.getValue(); +} + +bool Canvas::shouldShowIndex() +{ + return showIndex && !presentationMode.getValue(); +} + +bool Canvas::shouldShowConnectionDirection() +{ + return showConnectionDirection; +} + +bool Canvas::shouldShowConnectionActivity() +{ + return showConnectionActivity; +} + int Canvas::getOverlays() const { int overlayState = 0; @@ -670,17 +690,13 @@ void Canvas::updateOverlays() showOrigin = overlayState & Origin; showConnectionOrder = overlayState & Order; connectionsBehind = overlayState & Behind; + showObjectActivity = overlayState & ActivationState; + showIndex = overlayState & Index; + showConnectionDirection = overlayState & Direction; + showConnectionActivity = overlayState & ConnectionActivity; orderConnections(); - for (auto* object : objects) { - object->updateOverlays(overlayState); - } - - for (auto* connection : connections) { - connection->updateOverlays(overlayState); - } - repaint(); } diff --git a/Source/Canvas.h b/Source/Canvas.h index 7141a62e09..77228a4021 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -96,6 +96,11 @@ class Canvas : public Component int getOverlays() const; void updateOverlays(); + bool shouldShowObjectActivity(); + bool shouldShowIndex(); + bool shouldShowConnectionDirection(); + bool shouldShowConnectionActivity(); + void synchroniseSplitCanvas(); void synchronise(); void performSynchronise(); @@ -210,6 +215,11 @@ class Canvas : public Component bool showBorder = false; bool showConnectionOrder = false; bool connectionsBehind = true; + bool showObjectActivity = false; + bool showIndex = false; + + bool showConnectionDirection = false; + bool showConnectionActivity = false; bool isScrolling = false; diff --git a/Source/Connection.cpp b/Source/Connection.cpp index 48232812af..1ee7d26035 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -92,7 +92,6 @@ Connection::Connection(Canvas* parent, Iolet* s, Iolet* e, t_outconnect* oc) cnv->connectionLayer.addAndMakeVisible(this); - updateOverlays(cnv->getOverlays()); setAccessible(false); // TODO: implement accessibility. We disable default, since it makes stuff slow on macOS lookAndFeelChanged(); } @@ -175,7 +174,7 @@ void Connection::render(NVGcontext* nvg) nvgSave(nvg); nvgTranslate(nvg, getX(), getY()); - if (cableType == DataCable && showActivity) { + if (cableType == DataCable && cnv->shouldShowConnectionActivity()) { auto dashColor = connectionColour; dashColor.a = 1.0f; dashColor.r *= 0.8f; @@ -301,7 +300,7 @@ void Connection::render(NVGcontext* nvg) }; //TODO: refactor this outside of the render function - if (showDirection) { + if (cnv->shouldShowConnectionDirection()) { if (isSegmented()) { for (int i = 1; i < currentPlan.size(); i++) { const auto pathLine = Line(currentPlan[i - 1], currentPlan[i]); @@ -495,16 +494,6 @@ bool Connection::intersects(Rectangle toCheck, int accuracy) const return false; } -void Connection::updateOverlays(int overlay) -{ - if (!inlet || !outlet) - return; - - showDirection = overlay & Overlay::Direction; - showActivity = overlay & Overlay::ConnectionActivity; - repaint(); -} - void Connection::forceUpdate() { updatePath(); @@ -1304,7 +1293,7 @@ void ConnectionPathUpdater::timerCallback() void Connection::receiveMessage(t_symbol* symbol, pd::Atom const atoms[8], int numAtoms) { - if (showActivity) { + if (cnv->shouldShowConnectionActivity()) { startTimer(StopAnimation, 1000 / 8.0f); if (!isTimerRunning(Animation)) { startTimer(Animation, 1000 / 60.0f); diff --git a/Source/Connection.h b/Source/Connection.h index e05207a492..d37ee839e1 100644 --- a/Source/Connection.h +++ b/Source/Connection.h @@ -46,8 +46,6 @@ class Connection : public DrawablePath Connection(Canvas* parent, Iolet* start, Iolet* end, t_outconnect* oc); ~Connection() override; - void updateOverlays(int overlay); - static void renderConnectionPath(Graphics& g, Canvas* cnv, Path const& connectionPath, @@ -149,10 +147,6 @@ class Connection : public DrawablePath Value locked; Value presentationMode; - - bool showDirection = false; - bool showConnectionOrder = false; - bool showActivity = false; NVGcolor baseColour; NVGcolor dataColour; diff --git a/Source/Object.cpp b/Source/Object.cpp index 7526ae42ff..82fa3f6626 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -139,8 +139,6 @@ void Object::initialise() hvccMode.addListener(this); originalBounds.setBounds(0, 0, 0, 0); - - updateOverlays(cnv->getOverlays()); setAccessible(false); // TODO: implement accessibility. We disable default, since it makes stuff slow on macOS @@ -480,7 +478,7 @@ String Object::getType(bool withOriginPrefix) const void Object::triggerOverlayActiveState() { - if (!showActiveState) + if (!cnv->shouldShowObjectActivity()) return; if (rateReducer.tooFast()) @@ -1209,7 +1207,7 @@ void Object::updateFramebuffer(NVGcontext* nvg) void Object::render(NVGcontext* nvg) { - if(showActiveState && (!activityOverlayImage || activityOverlayDirty)) + if(cnv->shouldShowObjectActivity() && (!activityOverlayImage || activityOverlayDirty)) { if(activityOverlayImage) nvgDeleteImage(nvg, activityOverlayImage); Path objectShadow; @@ -1262,7 +1260,7 @@ void Object::performRender(NVGcontext* nvg) } } - if(showActiveState && !approximatelyEqual(activeStateAlpha, 0.0f) && activityOverlayImage) + if(cnv->shouldShowObjectActivity() && !approximatelyEqual(activeStateAlpha, 0.0f) && activityOverlayImage) { nvgBeginPath(nvg); nvgFillPaint(nvg, nvgImagePattern(nvg, lb.getX(), lb.getY(), lb.getWidth(), lb.getHeight(), 0, activityOverlayImage, activeStateAlpha)); @@ -1325,7 +1323,7 @@ void Object::performRender(NVGcontext* nvg) nvgStroke(nvg); nvgRestore(nvg); - } else if (indexShown) { + } else if (cnv->shouldShowIndex()) { int halfHeight = 5; auto text = std::to_string(cnv->objects.indexOf(this)); @@ -1533,20 +1531,6 @@ bool Object::keyPressed(KeyPress const& key, Component* component) return false; } -void Object::updateOverlays(int overlay) -{ - if (cnv->isGraph) - return; - - bool indexWasShown = indexShown; - indexShown = overlay & Overlay::Index; - showActiveState = overlay & Overlay::ActivationState; - - if (indexWasShown != indexShown) { - repaint(); - } -} - // For resize-while-typing behaviour void Object::textEditorTextChanged(TextEditor& ed) { diff --git a/Source/Object.h b/Source/Object.h index 366966185c..83e8a94378 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -94,8 +94,6 @@ class Object : public Component void lookAndFeelChanged() override; - void updateOverlays(int overlay); - void textEditorReturnKeyPressed(TextEditor& ed) override; void textEditorTextChanged(TextEditor& ed) override; @@ -148,11 +146,9 @@ class Object : public Component bool selectionStateChanged = false; bool wasLockedOnMouseDown = false; - bool indexShown = false; bool isHvccCompatible = true; bool isGemObject = false; - bool showActiveState = false; float activeStateAlpha = 0.0f; int activityOverlayImage = 0; From 3e5f8c18a740ce34910cf9705e25ddc16f08439f Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sat, 1 Jun 2024 00:10:11 +0930 Subject: [PATCH 0838/1030] fix previous PR not hiding activity inside gop objects --- Source/Canvas.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 8caa582592..bdbed49e2a 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -643,7 +643,7 @@ void Canvas::propertyChanged(String const& name, var const& value) bool Canvas::shouldShowObjectActivity() { - return showObjectActivity && !presentationMode.getValue(); + return showObjectActivity && !presentationMode.getValue() && !isGraph; } bool Canvas::shouldShowIndex() From 3a3508280532539293c0913af7789fa98181132f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 31 May 2024 17:32:01 +0200 Subject: [PATCH 0839/1030] Better way of managing images and framebuffers --- Source/Canvas.cpp | 176 ++++++++-------- Source/Canvas.h | 9 +- Source/Components/DraggableNumber.h | 2 +- Source/Components/SuggestionComponent.h | 2 +- Source/Components/TouchSelectionHelper.h | 4 +- Source/Iolet.cpp | 7 +- Source/NVGSurface.cpp | 111 +++++----- Source/NVGSurface.h | 255 ++++++++++++++++++++++- Source/Object.cpp | 94 ++++----- Source/Object.h | 8 +- Source/Objects/CommentObject.h | 2 +- Source/Objects/KeyboardObject.h | 2 +- Source/Objects/ListObject.h | 2 +- Source/Objects/LuaObject.h | 41 +--- Source/Objects/MessageObject.h | 2 +- Source/Objects/ObjectBase.cpp | 2 +- Source/Objects/ObjectBase.h | 22 +- Source/Objects/PictureObject.h | 37 +--- Source/Objects/SymbolAtomObject.h | 2 +- Source/Objects/TextObject.h | 2 +- Source/PluginEditor.cpp | 2 +- Source/PluginMode.h | 12 +- Source/Standalone/PlugDataApp.cpp | 2 - Source/Standalone/PlugDataWindow.h | 2 +- Source/Tabbar/WelcomePanel.h | 8 +- Source/Utility/CachedTextRender.h | 44 +--- Source/Utility/NVGComponent.cpp | 1 - Source/Utility/NVGComponent.h | 70 +------ Source/Utility/NanoVGGraphicsContext.cpp | 1 + Source/Utility/StackShadow.cpp | 6 +- Source/Utility/StackShadow.h | 2 +- 31 files changed, 481 insertions(+), 451 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 8caa582592..cbe151be30 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -174,7 +174,6 @@ Canvas::~Canvas() zoomScale.removeListener(this); editor->removeModifierKeyListener(this); pd->unregisterMessageListener(patch.getPointer().get(), this); - if(ioletBuffer) nvgDeleteFramebuffer(ioletBuffer); } bool Canvas::updateFramebuffers(NVGcontext* nvg, Rectangle invalidRegion, int maxUpdateTimeMs) @@ -183,93 +182,90 @@ bool Canvas::updateFramebuffers(NVGcontext* nvg, Rectangle invalidRegion, i auto pixelScale = getRenderScale(); auto zoom = isScrolling ? 2.0f : getValue(zoomScale); + int const logicalIoletsSize = 16 * 4; + int const ioletBufferSize = logicalIoletsSize * pixelScale * zoom; + // First, check if we need to update our iolet buffer - if(!ioletBuffer || !approximatelyEqual(zoom, bufferScale) || needsFramebufferUpdate) + if(ioletBuffer.needsUpdate(ioletBufferSize, ioletBufferSize)) { - int const logicalSize = 16 * 4; - int const pixelSize = logicalSize * pixelScale * zoom; - if(ioletBuffer) nvgDeleteFramebuffer(ioletBuffer); - ioletBuffer = nvgCreateFramebuffer(nvg, pixelSize, pixelSize, NVG_IMAGE_PREMULTIPLIED); - - nvgBindFramebuffer(ioletBuffer); - nvgViewport(0, 0, pixelSize, pixelSize); - nvgClear(nvg); - - nvgBeginFrame(nvg, logicalSize * zoom, logicalSize * zoom, pixelScale); - nvgScale(nvg, zoom, zoom); - - auto renderIolet = [](NVGcontext* nvg, Rectangle bounds, NVGcolor background, NVGcolor outline){ - if (PlugDataLook::getUseSquareIolets()) { - nvgBeginPath(nvg); - nvgFillColor(nvg, background); - nvgRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); - nvgFill(nvg); - - nvgStrokeColor(nvg, outline); - nvgStroke(nvg); - } - else { - nvgBeginPath(nvg); - nvgFillColor(nvg, background); - nvgCircle(nvg, bounds.getCentreX(), bounds.getCentreY(), bounds.getWidth() / 2.0f); - nvgFill(nvg); - - nvgStrokeColor(nvg, outline); - nvgStroke(nvg); + ioletBuffer.renderToFramebuffer(nvg, ioletBufferSize, ioletBufferSize, [this, zoom, ioletBufferSize](NVGcontext* nvg){ + nvgViewport(0, 0, ioletBufferSize, ioletBufferSize); + nvgClear(nvg); + + nvgBeginFrame(nvg, logicalIoletsSize * zoom, logicalIoletsSize * zoom, ioletBufferSize); + nvgScale(nvg, zoom, zoom); + + auto renderIolet = [](NVGcontext* nvg, Rectangle bounds, NVGcolor background, NVGcolor outline){ + if (PlugDataLook::getUseSquareIolets()) { + nvgBeginPath(nvg); + nvgFillColor(nvg, background); + nvgRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); + nvgFill(nvg); + + nvgStrokeColor(nvg, outline); + nvgStroke(nvg); + } + else { + nvgBeginPath(nvg); + nvgFillColor(nvg, background); + nvgCircle(nvg, bounds.getCentreX(), bounds.getCentreY(), bounds.getWidth() / 2.0f); + nvgFill(nvg); + + nvgStrokeColor(nvg, outline); + nvgStroke(nvg); + } + }; + + auto ioletColours = std::vector{ + findColour(PlugDataColour::dataColourId), + findColour(PlugDataColour::signalColourId), + findColour(PlugDataColour::gemColourId), + findColour(PlugDataColour::canvasBackgroundColourId).contrasting(0.5f)}; + + auto outlineColour = findNVGColour(PlugDataColour::objectOutlineColourId); + for(int i = 0; i < 4; i++) + { + auto backgroundColour = convertColour(ioletColours[i]); + auto ioletRow = Rectangle(0, i * 16 + 0.5f, logicalIoletsSize, 12.5f); + renderIolet(nvg, ioletRow.removeFromLeft(16).reduced(4.0f), backgroundColour, outlineColour); // normal + renderIolet(nvg, ioletRow.removeFromLeft(16).reduced(2.5f), backgroundColour, outlineColour); // hovered } - }; - - auto ioletColours = std::vector{ - findColour(PlugDataColour::dataColourId), - findColour(PlugDataColour::signalColourId), - findColour(PlugDataColour::gemColourId), - findColour(PlugDataColour::canvasBackgroundColourId).contrasting(0.5f)}; - - auto outlineColour = findNVGColour(PlugDataColour::objectOutlineColourId); - for(int i = 0; i < 4; i++) - { - auto backgroundColour = convertColour(ioletColours[i]); - auto ioletRow = Rectangle(0, i * 16 + 0.5f, logicalSize, 12.5f); - renderIolet(nvg, ioletRow.removeFromLeft(16).reduced(4.0f), backgroundColour, outlineColour); // normal - renderIolet(nvg, ioletRow.removeFromLeft(16).reduced(2.5f), backgroundColour, outlineColour); // hovered - } + + nvgEndFrame(nvg); + }); - nvgEndFrame(nvg); - nvgBindFramebuffer(0); editor->nvgSurface.invalidateAll(); } - if(!resizeHandleImage || !approximatelyEqual(zoom, bufferScale) || needsFramebufferUpdate) + int const resizerLogicalSize = 9; + int const resizerBufferSize = resizerLogicalSize * pixelScale * zoom; + + if(resizeHandleImage.needsUpdate(resizerBufferSize, resizerBufferSize)) { - int const logicalSize = 9; - int const pixelSize = logicalSize * pixelScale * zoom; - Image resizerImage = Image(Image::ARGB, pixelSize, pixelSize, true); - Graphics g(resizerImage); // Render resize handles with JUCE, since rounded rect exclusion is hard with nanovg - g.addTransform(AffineTransform::scale(pixelScale * zoom, pixelScale * zoom)); - - auto b = Rectangle(0, 0, 9, 9); - // use the path with a hole in it to exclude the inner rounded rect from painting - Path outerArea; - outerArea.addRectangle(b); - outerArea.setUsingNonZeroWinding(false); - - Path innerArea; - - auto innerRect = b.translated(Object::margin / 2, Object::margin / 2); - innerArea.addRoundedRectangle(innerRect, Corners::objectCornerRadius); - outerArea.addPath(innerArea); - g.reduceClipRegion(outerArea); - - g.setColour(findColour(PlugDataColour::objectSelectedOutlineColourId)); - g.fillRoundedRectangle(0.0f, 0.0f, 9.0f, 9.0f, Corners::objectCornerRadius); + resizeHandleImage = NVGImage(nvg, resizerBufferSize, resizerBufferSize, [this, pixelScale, zoom](Graphics& g){ + g.addTransform(AffineTransform::scale(pixelScale * zoom, pixelScale * zoom)); + + auto b = Rectangle(0, 0, 9, 9); + // use the path with a hole in it to exclude the inner rounded rect from painting + Path outerArea; + outerArea.addRectangle(b); + outerArea.setUsingNonZeroWinding(false); + + Path innerArea; + + auto innerRect = b.translated(Object::margin / 2, Object::margin / 2); + innerArea.addRoundedRectangle(innerRect, Corners::objectCornerRadius); + outerArea.addPath(innerArea); + g.reduceClipRegion(outerArea); + + g.setColour(findColour(PlugDataColour::objectSelectedOutlineColourId)); + g.fillRoundedRectangle(0.0f, 0.0f, 9.0f, 9.0f, Corners::objectCornerRadius); + }); - if(resizeHandleImage) nvgDeleteImage(nvg, resizeHandleImage); - resizeHandleImage = NVGImageRenderer::convertImage(nvg, resizerImage); + editor->nvgSurface.invalidateAll(); } - bufferScale = zoom; - // Then, check if object framebuffers need to be updated if(isScrolling) { if(viewport) invalidRegion = (invalidRegion + viewport->getViewPosition()) / zoom; @@ -286,9 +282,7 @@ bool Canvas::updateFramebuffers(NVGcontext* nvg, Rectangle invalidRegion, i } } } - - needsFramebufferUpdate = false; - + return true; } @@ -452,21 +446,16 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) nvgRoundedRect(nvg, pos.getX(), pos.getY(), borderWidth, borderHeight, Corners::windowCornerRadius); const int shadowSize = 40; - auto borderArea = Rectangle(0, 0, borderWidth, borderHeight); - auto expanededBorder = borderArea.expanded(shadowSize); - if (lastPresentationBounds != borderArea || presentationShadowImage == -1) + auto borderArea = Rectangle(0, 0, borderWidth, borderHeight).expanded(shadowSize); + if (presentationShadowImage.needsUpdate(borderArea.getWidth(), borderArea.getHeight())) { - lastPresentationBounds = borderArea; - if (presentationShadowImage) nvgDeleteImage(nvg, presentationShadowImage); - - Image shadow(Image::ARGB, expanededBorder.getWidth(), expanededBorder.getHeight(), true); - Graphics g(shadow); - auto shadowPath = Path(); - shadowPath.addRoundedRectangle(expanededBorder.reduced(shadowSize).withPosition(shadowSize, shadowSize), Corners::windowCornerRadius); - StackShadow::renderDropShadow(g, shadowPath, Colours::black, shadowSize); - presentationShadowImage = NVGImageRenderer::convertImage(nvg, shadow); + presentationShadowImage = NVGImage(nvg, borderArea.getWidth(), borderArea.getHeight(), [borderArea](Graphics& g){ + auto shadowPath = Path(); + shadowPath.addRoundedRectangle(borderArea.reduced(shadowSize).withPosition(shadowSize, shadowSize), Corners::windowCornerRadius); + StackShadow::renderDropShadow(g, shadowPath, Colours::black, shadowSize); + }); } - auto shadowImage = nvgImagePattern(nvg, pos.getX() - shadowSize, pos.getY() - shadowSize, expanededBorder.getWidth(), expanededBorder.getHeight(), 0, presentationShadowImage, 0.33f); + auto shadowImage = nvgImagePattern(nvg, pos.getX() - shadowSize, pos.getY() - shadowSize, borderArea.getWidth(), borderArea.getHeight(), 0, presentationShadowImage.getImageId(), 0.33f); nvgFillPaint(nvg, shadowImage); nvgFill(nvg); @@ -535,8 +524,7 @@ float Canvas::getRenderScale() const void Canvas::lookAndFeelChanged() { - needsFramebufferUpdate = true; - presentationShadowImage = -1; + //presentationShadowImage = -1; } void Canvas::updatePatchSnapshot() diff --git a/Source/Canvas.h b/Source/Canvas.h index 77228a4021..c3269ff0cd 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -259,12 +259,9 @@ class Canvas : public Component Component objectLayer; Component connectionLayer; - NVGframebuffer* ioletBuffer = nullptr; - int resizeHandleImage = 0; - float bufferScale; - - int presentationShadowImage = -1; - bool needsFramebufferUpdate = false; + NVGFramebuffer ioletBuffer; + NVGImage resizeHandleImage; + NVGImage presentationShadowImage; Rectangle lastPresentationBounds; Array> drawables; diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index 3098397495..b1634ddc4a 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -281,7 +281,7 @@ class DraggableNumber : public Label void render(NVGcontext* nvg) { - if(!nvgCtx) nvgCtx = std::make_unique(nvg); + if(!nvgCtx || nvgCtx->getContext() != nvg) nvgCtx = std::make_unique(nvg); nvgCtx->setPhysicalPixelScaleFactor(2.0f); Graphics g(*nvgCtx); { diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index 1cd69a8ac2..90a3cba8ec 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -108,7 +108,7 @@ class AutoCompleteComponent { nvgSave(nvg); nvgTranslate(nvg, getX(), getY()); - if(!nvgCtx) nvgCtx = std::make_unique(nvg); + if(!nvgCtx || nvgCtx->getContext() != nvg) nvgCtx = std::make_unique(nvg); Graphics g(*nvgCtx); { paintEntireComponent(g, true); diff --git a/Source/Components/TouchSelectionHelper.h b/Source/Components/TouchSelectionHelper.h index 1dd805c84c..b4fc14c11e 100644 --- a/Source/Components/TouchSelectionHelper.h +++ b/Source/Components/TouchSelectionHelper.h @@ -112,7 +112,7 @@ class TouchSelectionHelper : public Component, public NVGComponent { void render(NVGcontext* nvg) override { - imageRenderer.renderComponentFromImage(nvg, *this, 2.0f); + componentImage.renderJUCEComponent(nvg, *this, 2.0f); } private: @@ -131,6 +131,6 @@ class TouchSelectionHelper : public Component, public NVGComponent { g.drawRoundedRectangle(b.toFloat(), Corners::largeCornerRadius, 1.0f); } - NVGImageRenderer imageRenderer; + NVGImage componentImage; OwnedArray actionButtons; }; diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index 5e85b41cc6..b22230705f 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -59,8 +59,8 @@ void Iolet::render(NVGcontext* nvg) if (!isVisible()) return; - auto* fb = cnv->ioletBuffer; - if(!fb) return; + auto fb = cnv->ioletBuffer; + if(!fb.isValid()) return; bool isLocked = getValue(locked) || getValue(commandLocked); bool overObject = object->drawIoletExpanded; @@ -79,10 +79,9 @@ void Iolet::render(NVGcontext* nvg) auto scale = getWidth() / 13.0f; nvgScale(nvg, scale, scale); // If the iolet is shrunk because there is little space, we scale it down - nvgBeginPath(nvg); nvgRect(nvg, 0, 0, 13, 13); - nvgFillPaint(nvg, nvgImagePattern(nvg, isHovering * -16 - 1.5f, type * -16 - 0.5f, 16 * 4, 16 * 4, 0, fb->image, 1)); + nvgFillPaint(nvg, nvgImagePattern(nvg, isHovering * -16 - 1.5f, type * -16 - 0.5f, 16 * 4, 16 * 4, 0, fb.getImage(), 1)); nvgFill(nvg); nvgRestore(nvg); diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index e61d6dbe62..a777d2254b 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -17,12 +17,10 @@ using namespace juce::gl; #include "NVGSurface.h" #include "PluginEditor.h" -#include "PluginMode.h" -#include "Canvas.h" +#include "PluginProcessor.h" #include "Tabbar/WelcomePanel.h" -#include "Sidebar/Sidebar.h" // meh... -#define ENABLE_FPS_COUNT 0 +#define ENABLE_FPS_COUNT 1 class FrameTimer { @@ -102,7 +100,6 @@ NVGSurface::NVGSurface(PluginEditor* e) : editor(e) MessageManager::callAsync([_this = SafePointer(this)](){ if(_this) { _this->initialise(); - _this->updateBufferSize(); // Render on vblank _this->vBlankAttachment = std::make_unique(_this.getComponent(), [_this](){ @@ -123,15 +120,14 @@ NVGSurface::~NVGSurface() void NVGSurface::initialise() { #ifdef NANOVG_METAL_IMPLEMENTATION - auto renderScale = getRenderScale(); auto* peer = getPeer()->getNativeHandle(); auto* view = OSUtils::MTLCreateView(peer, 0, 0, getWidth(), getHeight()); setView(view); - nvg = nvgCreateContext(view, NVG_ANTIALIAS | NVG_TRIPLE_BUFFER, getWidth() * renderScale, getHeight() * renderScale); setVisible(true); -#if JUCE_IOS + + auto renderScale = getRenderScale(); + nvg = nvgCreateContext(view, NVG_ANTIALIAS | NVG_TRIPLE_BUFFER, getWidth() * renderScale, getHeight() * renderScale); resized(); -#endif #else setVisible(true); glContext->attachTo(*this); @@ -151,6 +147,36 @@ void NVGSurface::initialise() nvgCreateFontMem(nvg, "icon_font-Regular", (unsigned char*)BinaryData::IconFont_ttf, BinaryData::IconFont_ttfSize, 0); } +void NVGSurface::detachContext() +{ + editor->welcomePanel->clearBuffers(); + NVGFramebuffer::clearAll(); + NVGImage::clearAll(); + + if(invalidFBO) { + nvgDeleteFramebuffer(invalidFBO); + invalidFBO = nullptr; + } + if(mainFBO) { + nvgDeleteFramebuffer(mainFBO); + mainFBO = nullptr; + } + if(nvg) + { + nvgDeleteContext(nvg); + nvg = nullptr; + } + +#ifdef NANOVG_METAL_IMPLEMENTATION + if(auto* view = getView()) { + OSUtils::MTLDeleteView(view); + setView(nullptr); + } +#else + if(glContext) glContext->detach(); +#endif +} + void NVGSurface::updateBufferSize() { float pixelScale = getRenderScale(); @@ -165,10 +191,7 @@ void NVGSurface::updateBufferSize() fbWidth = scaledWidth; fbHeight = scaledHeight; invalidArea = getLocalBounds(); - lastScaleFactor = pixelScale; } - - //scaleChanged = !approximatelyEqual(lastScaleFactor, pixelScale); } @@ -191,44 +214,21 @@ bool NVGSurface::makeContextActive() { #ifdef NANOVG_METAL_IMPLEMENTATION // No need to make context active with Metal, so just check if we have initialised and return that - return isAttached(); + return getView() != nullptr && nvg != nullptr; #else if(glContext) return glContext->makeActive(); -#endif - return false; -} - -void NVGSurface::detachContext() -{ -#ifdef NANOVG_METAL_IMPLEMENTATION - if(auto* view = getView()) { - OSUtils::MTLDeleteView(view); - setView(nullptr); - } -#else - if(glContext) glContext->detach(); #endif } -void NVGSurface::propertyChanged(String const& name, var const& value) { - if(name == "global_scale") - { - // TODO: handle this? - //sendContextDeleteMessage(); - } -} - float NVGSurface::getRenderScale() const { - auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); #ifdef NANOVG_METAL_IMPLEMENTATION - if(!isAttached()) return 2.0f * desktopScale; + auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); return OSUtils::MTLGetPixelScale(getView()) * desktopScale; #else - if(!isAttached()) return desktopScale; - return glContext->getRenderingScale();// * desktopScale; + return glContext->getRenderingScale(); #endif } @@ -251,25 +251,14 @@ void NVGSurface::resized() { #ifdef NANOVG_METAL_IMPLEMENTATION if(auto* view = getView()) { - auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - auto renderScale = OSUtils::MTLGetPixelScale(view); // TODO: we can simplify with getRenderScale() function, but needs testing on iOS + auto renderScale = getRenderScale(); auto* topLevel = getTopLevelComponent(); - auto bounds = topLevel->getLocalArea(this, getLocalBounds()).toFloat() * desktopScale; - mnvgSetViewBounds(view, (renderScale * bounds.getWidth()), (renderScale * bounds.getHeight())); + auto bounds = topLevel->getLocalArea(this, getLocalBounds()).toFloat() * renderScale; + mnvgSetViewBounds(view, bounds.getWidth(), bounds.getHeight()); } #endif } -bool NVGSurface::isAttached() const -{ -#ifdef NANOVG_METAL_IMPLEMENTATION - return getView() != nullptr && nvg != nullptr; -#else - return glContext->isAttached() && nvg != nullptr; -#endif -} - - void NVGSurface::invalidateAll() { invalidArea = getLocalBounds(); @@ -282,9 +271,16 @@ void NVGSurface::invalidateArea(Rectangle area) void NVGSurface::render() { - auto startTime = Time::getMillisecondCounter(); +#if ENABLE_FPS_COUNT + frameTimer->addFrameTime(); +#endif - if(!isAttached() && isVisible()) initialise(); + auto startTime = Time::getMillisecondCounter(); + + if(!nvg) { + initialise(); + return; // Render on next frame + } bool hasCanvas = false; for(auto* split : editor->splitView.splits) @@ -292,6 +288,7 @@ void NVGSurface::render() if(auto* cnv = split->getTabComponent()->getCurrentCanvas()) { hasCanvas = true; + break; } } // Manage showing/hiding welcome panel @@ -305,7 +302,7 @@ void NVGSurface::render() } updateBufferSize(); - + auto pixelScale = getRenderScale(); if(!invalidArea.isEmpty() && makeContextActive()) { auto invalidated = invalidArea.expanded(1); @@ -344,10 +341,6 @@ void NVGSurface::render() nvgBindFramebuffer(nullptr); needsBufferSwap = true; invalidArea = Rectangle(0, 0, 0, 0); - -#if ENABLE_FPS_COUNT - frameTimer->addFrameTime(); -#endif } if(needsBufferSwap && makeContextActive()) { diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 704ed9b25d..10da79b953 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -32,7 +32,6 @@ public UIViewComponent #else public Component, public Timer #endif -, public SettingsFileListener { public: NVGSurface(PluginEditor* editor); @@ -44,15 +43,13 @@ public Component, public Timer void render(); void triggerRepaint(); - - void detachContext(); bool makeContextActive(); + + void detachContext(); #ifdef NANOVG_GL_IMPLEMENTATION void timerCallback() override; #endif - - void propertyChanged(String const& name, var const& value) override; Rectangle getInvalidArea() { return invalidArea; } @@ -103,8 +100,6 @@ public Component, public Timer private: - bool isAttached() const; - void resized() override; void renderPerfMeter(NVGcontext* nvg); @@ -118,7 +113,6 @@ public Component, public Timer NVGframebuffer* mainFBO = nullptr; NVGframebuffer* invalidFBO = nullptr; int fbWidth = 0, fbHeight = 0; - float lastScaleFactor = 0.0f; bool hresize = false; bool resizing = false; @@ -130,3 +124,248 @@ public Component, public Timer std::unique_ptr frameTimer; }; + + +class NVGImage +{ +public: + + NVGImage(NVGcontext* ctx, int width, int height, int image) + : nvg(ctx) + , imageId(image) + , imageWidth(width) + , imageHeight(height) + { + allImages.insert(this); + } + + NVGImage(NVGcontext* nvg, int width, int height, std::function renderCall) + { + Image image = Image(Image::ARGB, width, height, true); + Graphics g(image); // Render resize handles with JUCE, since rounded rect exclusion is hard with nanovg + renderCall(g); + loadJUCEImage(nvg, image); + allImages.insert(this); + } + + NVGImage(NVGcontext* nvg, Image image) + { + loadJUCEImage(nvg, image); + allImages.insert(this); + } + + NVGImage() + { + allImages.insert(this); + } + + NVGImage(NVGImage& other) + { + // Check for self-assignment + if (this != &other) { + nvg = other.nvg; + imageId = other.imageId; + imageWidth = other.imageWidth; + imageHeight = other.imageHeight; + + other.imageId = 0; + allImages.insert(this); + } + } + + NVGImage& operator=(NVGImage&& other) noexcept + { + // Check for self-assignment + if (this != &other) { + nvg = other.nvg; + imageId = other.imageId; + imageWidth = other.imageWidth; + imageHeight = other.imageHeight; + + other.imageId = 0; // Important, makes sure the old buffer can't delete this buffer + allImages.insert(this); + } + + return *this; + } + + ~NVGImage() + { + if(imageId && nvg) nvgDeleteImage(nvg, imageId); + allImages.erase(this); + } + + static void clearAll() + { + for(auto* image : allImages) + { + if(image->isValid() && image->nvg) nvgDeleteImage(image->nvg, image->imageId); + image->imageId = 0; + } + } + + bool isValid() + { + return imageId != 0; + } + + void renderJUCEComponent(NVGcontext* nvg, Component& component, float scale) + { + Image componentImage = component.createComponentSnapshot(Rectangle(0, 0, component.getWidth() + 1, component.getHeight()), false, scale); + if(componentImage.isNull()) return; + + loadJUCEImage(nvg, componentImage); + + nvgBeginPath(nvg); + nvgRect(nvg, 0, 0, component.getWidth() + 1, component.getHeight()); + nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, component.getWidth() + 1, component.getHeight(), 0, imageId, 1.0f)); + nvgFill(nvg); + } + + void loadJUCEImage(NVGcontext* context, Image& image) + { + Image::BitmapData imageData(image, Image::BitmapData::readOnly); + + int width = imageData.width; + int height = imageData.height; + uint8* pixelData = imageData.data; + + for (int y = 0; y < height; ++y) + { + auto* scanLine = (uint32*) imageData.getLinePointer(y); + + for (int x = 0; x < width; ++x) + { + uint32 argb = scanLine[x]; + + uint8 a = argb >> 24; + uint8 r = argb >> 16; + uint8 g = argb >> 8; + uint8 b = argb; + + // order bytes as abgr + scanLine[x] = (a << 24) | (b << 16) | (g << 8) | r; + } + } + + if(imageId && imageWidth == width && imageHeight == height && nvg == context) + { + update(pixelData); + } + else { + nvg = context; + imageId = nvgCreateImageRGBA(nvg, width, height, NVG_IMAGE_PREMULTIPLIED, pixelData); + imageWidth = width; + imageHeight = height; + } + } + + void update(uint8* pixelData) + { + nvgUpdateImage(nvg, imageId, pixelData); + } + + bool needsUpdate(int width, int height) + { + return imageId == 0 || width != imageWidth || height != imageHeight; + } + + int getImageId() + { + return imageId; + } + + NVGcontext* nvg = nullptr; + int imageId = 0; + int imageWidth = 0, imageHeight = 0; + + static inline std::set allImages; +}; + +class NVGFramebuffer +{ +public: + NVGFramebuffer() + { + allFramebuffers.insert(this); + } + + ~NVGFramebuffer() + { + + allFramebuffers.erase(this); + } + + static void clearAll() + { + for(auto* buffer : allFramebuffers) + { + if(buffer->fb) nvgDeleteFramebuffer(buffer->fb); + buffer->fb = nullptr; + } + } + + bool needsUpdate(int width, int height) + { + return fb == nullptr || width != fbWidth || height != fbHeight || fbDirty; + } + + bool isValid() + { + return fb != nullptr; + } + + void setDirty() + { + fbDirty = true; + } + + void bind(NVGcontext* nvg, int width, int height) + { + if(!fb || fbWidth != width || fbHeight != height) { + if(fb) nvgDeleteFramebuffer(fb); + fb = nvgCreateFramebuffer(nvg, width, height, NVG_IMAGE_PREMULTIPLIED); + fbWidth = width; + fbHeight = height; + } + + nvgBindFramebuffer(fb); + } + + void unbind() + { + nvgBindFramebuffer(nullptr); + } + + void renderToFramebuffer(NVGcontext* nvg, int width, int height, std::function renderCallback) + { + bind(nvg, width, height); + renderCallback(nvg); + unbind(); + fbDirty = false; + } + + void render(NVGcontext* nvg, Rectangle b) + { + if(fb) { + nvgBeginPath(nvg); + nvgRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight()); + nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, b.getWidth(), b.getHeight(), 0, fb->image, 1)); + nvgFill(nvg); + } + } + + int getImage() + { + if(!fb) return -1; + + return fb->image; + } + +private: + static inline std::set allFramebuffers; + + NVGframebuffer* fb = nullptr; + int fbWidth, fbHeight; + bool fbDirty = false; +}; diff --git a/Source/Object.cpp b/Source/Object.cpp index 82fa3f6626..e50392a6d7 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -67,8 +67,6 @@ Object::Object(pd::WeakReference object, Canvas* parent) Object::~Object() { - if(fb) nvgDeleteFramebuffer(fb); - hideEditor(); // Make sure the editor is not still open, that could lead to issues with listeners attached to the editor (i.e. suggestioncomponent) cnv->selectedComponents.removeChangeListener(this); } @@ -105,13 +103,13 @@ class InvalidationListener : public CachedComponentImage bool invalidate(const Rectangle& rect) override { - object->fbDirty = true; + object->scrollBuffer.setDirty(); return true; } bool invalidateAll() override { - object->fbDirty = true; + object->scrollBuffer.setDirty(); return true; } @@ -501,7 +499,7 @@ void Object::lookAndFeelChanged() void Object::resized() { - fbDirty = true; + scrollBuffer.setDirty(); activityOverlayDirty = true; setVisible(!((cnv->isGraph || cnv->presentationMode == var(true)) && gui && gui->hideInGraph())); @@ -1165,69 +1163,51 @@ void Object::updateFramebuffer(NVGcontext* nvg) if(getWidth() * 3 * cnv->getRenderScale() > 8192 || getHeight() * 3 * cnv->getRenderScale() > 8192) return; auto b = getLocalBounds(); - bool boundsChanged = b.getWidth() != fbWidth || b.getHeight() != fbHeight; - if(fbDirty || boundsChanged) + auto maxScale = 3.0f; + int scaledWidth = b.getWidth() * maxScale * cnv->getRenderScale(); + int scaledHeight = b.getHeight() * maxScale * cnv->getRenderScale(); + + if(scrollBuffer.needsUpdate(scaledWidth, scaledHeight)) { - auto maxScale = 3.0f; - int scaledWidth = b.getWidth() * maxScale * cnv->getRenderScale(); - int scaledHeight = b.getHeight() * maxScale * cnv->getRenderScale(); - - if(!fb || boundsChanged) - { - fbWidth = b.getWidth(); - fbHeight = b.getHeight(); - - if(fb) nvgDeleteFramebuffer(fb); - fb = nvgCreateFramebuffer(nvg, scaledWidth, scaledHeight, NVG_IMAGE_PREMULTIPLIED); - } - - nvgBindFramebuffer(fb); - nvgViewport(0, 0, scaledWidth, scaledHeight); - nvgClear(nvg); + scrollBuffer.renderToFramebuffer(nvg, scaledWidth, scaledHeight, [this, scaledWidth, scaledHeight, maxScale, b](NVGcontext* nvg){ + nvgViewport(0, 0, scaledWidth, scaledHeight); + nvgClear(nvg); - nvgBeginFrame(nvg, b.getWidth() * maxScale, b.getHeight() * maxScale, cnv->getRenderScale()); - nvgScale(nvg, maxScale, maxScale); - nvgScissor (nvg, 0, 0, b.getWidth(), b.getHeight()); - - performRender(nvg); - -#if ENABLE_OBJECT_FB_DEBUGGING - static Random rng; - nvgBeginPath(nvg); - nvgFillColor(nvg, nvgRGBA(rng.nextInt(255), rng.nextInt(255), rng.nextInt(255), 0x50)); - nvgRect(nvg, 0, 0, b.getWidth(), b.getHeight()); - nvgFill(nvg); -#endif - nvgEndFrame(nvg); - nvgBindFramebuffer(nullptr); - fbDirty = false; - + nvgBeginFrame(nvg, b.getWidth() * maxScale, b.getHeight() * maxScale, cnv->getRenderScale()); + nvgScale(nvg, maxScale, maxScale); + nvgScissor (nvg, 0, 0, b.getWidth(), b.getHeight()); + + performRender(nvg); + + #if ENABLE_OBJECT_FB_DEBUGGING + static Random rng; + nvgBeginPath(nvg); + nvgFillColor(nvg, nvgRGBA(rng.nextInt(255), rng.nextInt(255), rng.nextInt(255), 0x50)); + nvgRect(nvg, 0, 0, b.getWidth(), b.getHeight()); + nvgFill(nvg); + #endif + nvgEndFrame(nvg); + }); } } void Object::render(NVGcontext* nvg) { - if(cnv->shouldShowObjectActivity() && (!activityOverlayImage || activityOverlayDirty)) + if(cnv->shouldShowObjectActivity() && (!activityOverlayImage.isValid() || activityOverlayDirty)) { - if(activityOverlayImage) nvgDeleteImage(nvg, activityOverlayImage); Path objectShadow; objectShadow.addRoundedRectangle(getLocalBounds().reduced(Object::margin - 1), Corners::objectCornerRadius); activityOverlayImage = StackShadow::createActivityDropShadowImage(nvg, getLocalBounds(), objectShadow, getLookAndFeel().findColour(PlugDataColour::dataColourId), 5.5f, { 0, 0 }, 0, gui && (gui->getCanvas() || gui->isTransparent())); activityOverlayDirty = false; } - if(fb && cnv->isScrolling) + if(cnv->isScrolling && scrollBuffer.needsUpdate(getWidth(), getHeight())) { - if(fbDirty) { // If framebuffer is dirty, draw normally now and draw from buffer again on next render - performRender(nvg); - return; - } - - auto b = getLocalBounds(); - nvgBeginPath(nvg); - nvgRect(nvg, 0, 0, b.getWidth(), b.getHeight()); - nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, b.getWidth(), b.getHeight(), 0, fb->image, 1)); - nvgFill(nvg); + performRender(nvg); + } + else if(cnv->isScrolling && scrollBuffer.isValid()) + { + scrollBuffer.render(nvg, Rectangle(0, 0, getWidth(), getHeight())); } else { performRender(nvg); @@ -1253,17 +1233,17 @@ void Object::performRender(NVGcontext* nvg) nvgBeginPath(nvg); nvgRect(nvg, 0, 0, 9, 9); - nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, 9, 9, 0, resizeHandleImage, 1)); + nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, 9, 9, 0, resizeHandleImage.getImageId(), 1)); nvgFill(nvg); nvgRestore(nvg); angle -= 90; } } - if(cnv->shouldShowObjectActivity() && !approximatelyEqual(activeStateAlpha, 0.0f) && activityOverlayImage) + if(cnv->shouldShowObjectActivity() && !approximatelyEqual(activeStateAlpha, 0.0f) && activityOverlayImage.isValid()) { nvgBeginPath(nvg); - nvgFillPaint(nvg, nvgImagePattern(nvg, lb.getX(), lb.getY(), lb.getWidth(), lb.getHeight(), 0, activityOverlayImage, activeStateAlpha)); + nvgFillPaint(nvg, nvgImagePattern(nvg, lb.getX(), lb.getY(), lb.getWidth(), lb.getHeight(), 0, activityOverlayImage.getImageId(), activeStateAlpha)); nvgRect(nvg, lb.getX(), lb.getY(), lb.getWidth(), lb.getHeight()); nvgFill(nvg); } @@ -1296,7 +1276,7 @@ void Object::performRender(NVGcontext* nvg) nvgStroke(nvg); nvgTranslate(nvg, margin, margin); - textEditorRenderer.renderComponentFromImage(nvg, *newObjectEditor, getValue(cnv->zoomScale) * cnv->getRenderScale()); + textEditorRenderer.renderJUCEComponent(nvg, *newObjectEditor, getValue(cnv->zoomScale) * cnv->getRenderScale()); } // If autoconnect is about to happen, draw a fake inlet with a dotted outline diff --git a/Source/Object.h b/Source/Object.h index 83e8a94378..c8bd4d7b75 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -151,12 +151,10 @@ class Object : public Component float activeStateAlpha = 0.0f; - int activityOverlayImage = 0; + NVGImage activityOverlayImage; bool activityOverlayDirty = false; - NVGframebuffer* fb = nullptr; - bool fbDirty = true; - int fbWidth, fbHeight; + NVGFramebuffer scrollBuffer; NVGpaint glow; bool glowDirty = true; @@ -164,7 +162,7 @@ class Object : public Component bool isObjectMouseActive = false; bool isInsideUndoSequence = false; - NVGImageRenderer textEditorRenderer; + NVGImage textEditorRenderer; ObjectDragState& ds; diff --git a/Source/Objects/CommentObject.h b/Source/Objects/CommentObject.h index e1e000a83d..2c30341e24 100644 --- a/Source/Objects/CommentObject.h +++ b/Source/Objects/CommentObject.h @@ -51,7 +51,7 @@ class CommentObject final : public ObjectBase textRenderer.renderText(nvg, textArea, getImageScale()); } else { - imageRenderer.renderComponentFromImage(nvg, *editor, getImageScale()); + imageRenderer.renderJUCEComponent(nvg, *editor, getImageScale()); } } diff --git a/Source/Objects/KeyboardObject.h b/Source/Objects/KeyboardObject.h index e7c88ab000..c9820832d3 100644 --- a/Source/Objects/KeyboardObject.h +++ b/Source/Objects/KeyboardObject.h @@ -297,7 +297,7 @@ class KeyboardObject final : public ObjectBase void render(NVGcontext* nvg) override { - if(!nvgCtx) nvgCtx = std::make_unique(nvg); + if(!nvgCtx || nvgCtx->getContext() != nvg) nvgCtx = std::make_unique(nvg); Graphics g(*nvgCtx); { paintEntireComponent(g, true); diff --git a/Source/Objects/ListObject.h b/Source/Objects/ListObject.h index b65bc8c8f9..6edc2d1551 100644 --- a/Source/Objects/ListObject.h +++ b/Source/Objects/ListObject.h @@ -172,7 +172,7 @@ class ListObject final : public ObjectBase { nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), backgroundColour, object->isSelected() ? selectedOutlineColour : outlineColour, Corners::objectCornerRadius); - imageRenderer.renderComponentFromImage(nvg, listLabel, getImageScale()); + imageRenderer.renderJUCEComponent(nvg, listLabel, getImageScale()); nvgSave(nvg); nvgIntersectRoundedScissor(nvg, b.getX() + 0.25f, b.getY() + 0.25f, b.getWidth() - 0.5f, b.getHeight() - 0.5f, Corners::objectCornerRadius); diff --git a/Source/Objects/LuaObject.h b/Source/Objects/LuaObject.h index fbe108dd8b..ff0d2b7f67 100644 --- a/Source/Objects/LuaObject.h +++ b/Source/Objects/LuaObject.h @@ -30,9 +30,7 @@ class LuaObject : public ObjectBase, public Timer { std::unique_ptr textEditor; std::unique_ptr saveDialog; - NVGframebuffer* framebuffer = nullptr; - bool imageNeedsRefresh = false; - int framebufferWidth = 0, framebufferHeight = 0; + NVGFramebuffer framebuffer; struct LuaGuiMessage { t_symbol* symbol; @@ -199,12 +197,7 @@ class LuaObject : public ObjectBase, public Timer { void render(NVGcontext* nvg) override { - if(framebuffer) { - nvgBeginPath(nvg); - nvgRect(nvg, 0, 0, getWidth() + 1, getHeight()); - nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, getWidth() + 1, getHeight(), 0, framebuffer->image, 1.0f)); - nvgFill(nvg); - } + framebuffer.render(nvg, Rectangle(getWidth() + 1, getHeight())); } void valueChanged(Value& v) override @@ -227,19 +220,8 @@ class LuaObject : public ObjectBase, public Timer { int imageWidth = std::ceil(getWidth() * scale); int imageHeight = std::ceil(getHeight() * scale); if(!imageWidth || !imageHeight) return; - - if(!framebuffer || framebufferWidth != imageWidth || framebufferHeight != imageHeight) - { - nvgBindFramebuffer(nullptr); - if(framebuffer) nvgDeleteFramebuffer(framebuffer); - framebuffer = nvgCreateFramebuffer(nvg, imageWidth, imageHeight, NVG_IMAGE_PREMULTIPLIED); - nvgBindFramebuffer(framebuffer); - framebufferWidth = imageWidth; - framebufferHeight = imageHeight; - } - else { - nvgBindFramebuffer(framebuffer); - } + + framebuffer.bind(nvg, imageWidth, imageHeight); nvgViewport(0, 0, getWidth() * scale, getHeight() * scale); nvgClear(nvg); @@ -248,10 +230,10 @@ class LuaObject : public ObjectBase, public Timer { return; } case hash("lua_end_paint"): { - if(!framebuffer) return; + if(!framebuffer.isValid()) return; nvgEndFrame(nvg); - nvgBindFramebuffer(nullptr); + framebuffer.unbind(); repaint(); return; } @@ -269,7 +251,7 @@ class LuaObject : public ObjectBase, public Timer { } } - if(!framebuffer) return; // If there is no active framebuffer at this point, return + if(!framebuffer.isValid()) return; // If there is no active framebuffer at this point, return switch (hashsym) { case hash("lua_set_color"): { @@ -501,12 +483,6 @@ class LuaObject : public ObjectBase, public Timer { void timerCallback() override { - if(imageNeedsRefresh && cnv->editor->nvgSurface.getRawContext()) - { - sendRepaintMessage(); - imageNeedsRefresh = false; - } - LuaGuiMessage guiMessage; while(guiMessageQueue.try_dequeue(guiMessage)) { @@ -539,11 +515,12 @@ class LuaObject : public ObjectBase, public Timer { guiCommandBuffer.erase(guiCommandBuffer.begin(), guiCommandBuffer.begin() + endIdx); } - if(isSelected != object->isSelected()) + if(isSelected != object->isSelected() || !framebuffer.isValid()) { isSelected = object->isSelected(); sendRepaintMessage(); } + } static void drawCallback(void* target, t_symbol* sym, int argc, t_atom* argv) diff --git a/Source/Objects/MessageObject.h b/Source/Objects/MessageObject.h index 0db387f870..8b2076f115 100644 --- a/Source/Objects/MessageObject.h +++ b/Source/Objects/MessageObject.h @@ -186,7 +186,7 @@ class MessageObject final : public ObjectBase if(editor) { - imageRenderer.renderComponentFromImage(nvg, *editor, getImageScale()); + imageRenderer.renderJUCEComponent(nvg, *editor, getImageScale()); } else { auto text = getText(); diff --git a/Source/Objects/ObjectBase.cpp b/Source/Objects/ObjectBase.cpp index b51a603499..0ebf7a9428 100644 --- a/Source/Objects/ObjectBase.cpp +++ b/Source/Objects/ObjectBase.cpp @@ -431,7 +431,7 @@ void ObjectBase::moveToBack() void ObjectBase::render(NVGcontext* nvg) { - imageRenderer.renderComponentFromImage(nvg, *this, getImageScale()); + imageRenderer.renderJUCEComponent(nvg, *this, getImageScale()); } void ObjectBase::paint(Graphics& g) diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index 089446ea58..8658ba4a8d 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -28,8 +28,7 @@ class Object; class ObjectLabel : public Label, public NVGComponent { hash32 lastTextHash = 0; - int imageId = 0; - int lastWidth = 0, lastHeight = 0; + NVGImage image; float lastScale = 1.0f; bool updateColour = false; Colour lastColour; @@ -47,19 +46,17 @@ class ObjectLabel : public Label, public NVGComponent { void renderLabel(NVGcontext* nvg, float scale) { auto textHash = hash(getText()); - if(!imageId || updateColour || lastTextHash != textHash || lastScale != scale || lastWidth != getWidth() || lastHeight != getHeight()) + if(image.needsUpdate(getWidth(), getHeight()) || updateColour || lastTextHash != textHash || lastScale != scale) { updateImage(nvg, scale); lastTextHash = textHash; lastScale = scale; - lastWidth = getWidth(); - lastHeight = getHeight(); updateColour = false; } nvgBeginPath(nvg); nvgRect(nvg, 0, 0, getWidth() + 1, getHeight()); - nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, getWidth() + 1, getHeight(), 0, imageId, 1.0f)); + nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, getWidth() + 1, getHeight(), 0, image.getImageId(), 1.0f)); nvgFill(nvg); } @@ -75,16 +72,7 @@ class ObjectLabel : public Label, public NVGComponent { void updateImage(NVGcontext* nvg, float scale) { auto componentImage = createComponentSnapshot(Rectangle(0, 0, getWidth() + 1, getHeight()), false, scale); - - if(!componentImage.isNull()) { - if(imageId && lastWidth == getWidth() && lastHeight == getHeight()) { - imageId = NVGImageRenderer::convertImage(nvg, componentImage, imageId); - } - else { - if(imageId) nvgDeleteImage(nvg, imageId); - imageId = NVGImageRenderer::convertImage(nvg, componentImage); - } - } + image.loadJUCEImage(nvg, componentImage); } private: @@ -402,7 +390,7 @@ class ObjectBase : public Component protected: PropertyUndoListener propertyUndoListener; - NVGImageRenderer imageRenderer; + NVGImage imageRenderer; std::function onConstrainerCreate = []() {}; diff --git a/Source/Objects/PictureObject.h b/Source/Objects/PictureObject.h index 4e5a22d806..acff61e0d3 100644 --- a/Source/Objects/PictureObject.h +++ b/Source/Objects/PictureObject.h @@ -17,7 +17,7 @@ class PictureObject final : public ObjectBase { File imageFile; Image img; - std::vector>> imageBuffers; + std::vector, Rectangle>> imageBuffers; bool imageNeedsReload = false; uint8 pixelDataBuffer[8192 * 8192 * 4]; @@ -127,37 +127,9 @@ class PictureObject final : public ObjectBase { } } } - - int convertImage(NVGcontext* nvg, Image& image, Rectangle bounds) - { - const auto argbImage = image.convertedToFormat(Image::ARGB); - const Image::BitmapData imageData(argbImage, Image::BitmapData::readOnly); - - for (int y = 0; y < bounds.getHeight(); y++) - { - auto* scanLine = (uint32*) imageData.getLinePointer(y + bounds.getY()); - - for (int x = 0; x < bounds.getWidth(); x++) - { - uint32 argb = scanLine[x + bounds.getX()]; - int bufferPos = (y * bounds.getWidth() + x) * 4; - - pixelDataBuffer[bufferPos + 0] = (argb >> 16) & 0xFF; // Red - pixelDataBuffer[bufferPos + 1] = (argb >> 8) & 0xFF; // Green - pixelDataBuffer[bufferPos + 2] = argb & 0xFF; // Blue - pixelDataBuffer[bufferPos + 3] = (argb >> 24) & 0xFF; // Alpha - } - } - - return nvgCreateImageRGBA(nvg, bounds.getWidth(), bounds.getHeight(), NVG_IMAGE_PREMULTIPLIED, pixelDataBuffer); - } void updateImage(NVGcontext* nvg) { - for(auto& [image, bounds] : imageBuffers) - { - nvgDeleteImage(nvg, image); - } imageBuffers.clear(); int imageWidth = img.getWidth(); @@ -171,7 +143,8 @@ class PictureObject final : public ObjectBase { { int height = std::min(8192, imageHeight - y); auto bounds = Rectangle(x, y, width, height); - imageBuffers.emplace_back(convertImage(nvg, img, bounds), bounds); + auto clip = img.getClippedImage(bounds).createCopy(); + imageBuffers.emplace_back(std::make_unique(nvg, clip), bounds); y += 8192; } @@ -202,7 +175,7 @@ class PictureObject final : public ObjectBase { { nvgBeginPath(nvg); nvgRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); - nvgFillPaint(nvg, nvgImagePattern(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), 0, image, 1.0f)); + nvgFillPaint(nvg, nvgImagePattern(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), 0, image->getImageId(), 1.0f)); nvgFill(nvg); } } @@ -340,7 +313,7 @@ class PictureObject final : public ObjectBase { auto* rawFileName = fileNameString.toRawUTF8(); auto* rawPath = pathString.toRawUTF8(); - img = ImageFileFormat::loadFrom(imageFile); + img = ImageFileFormat::loadFrom(imageFile).convertedToFormat(Image::ARGB); imageNeedsReload = true; if (auto pic = ptr.get()) { diff --git a/Source/Objects/SymbolAtomObject.h b/Source/Objects/SymbolAtomObject.h index f1f609debb..2d38e77836 100644 --- a/Source/Objects/SymbolAtomObject.h +++ b/Source/Objects/SymbolAtomObject.h @@ -153,7 +153,7 @@ class SymbolAtomObject final : public ObjectBase nvgRestore(nvg); - imageRenderer.renderComponentFromImage(nvg, input, getImageScale()); + imageRenderer.renderJUCEComponent(nvg, input, getImageScale()); if(object->isSelected()) // If object is selected, draw outline over top too, so the flag doesn't poke into the selected outline { diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index 7371caa9f7..cf3f23f934 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -261,7 +261,7 @@ class TextBase : public ObjectBase if(editor && editor->isVisible()) { - imageRenderer.renderComponentFromImage(nvg, *editor, getImageScale()); + imageRenderer.renderJUCEComponent(nvg, *editor, getImageScale()); } else { auto text = getText(); diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index fadfa02cb8..bb37a6fcc8 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -400,6 +400,7 @@ void PluginEditor::renderArea(NVGcontext* nvg, Rectangle area) } } + CallOutBox& PluginEditor::showCalloutBox(std::unique_ptr content, Rectangle screenBounds) { class CalloutDeletionListener : public ComponentListener @@ -1864,7 +1865,6 @@ void PluginEditor::quit(bool askToSave) auto* window = dynamic_cast(getTopLevelComponent()); window->closeButtonPressed(); } else { - nvgSurface.detachContext(); JUCEApplication::quit(); } } diff --git a/Source/PluginMode.h b/Source/PluginMode.h index fc5a6be028..f23a532a12 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -39,12 +39,12 @@ class PluginMode : public Component, public NVGComponent { if (auto* mainWindow = dynamic_cast(editor->getTopLevelComponent())) { mainWindow->setUsingNativeTitleBar(false); - editor->nvgSurface.detachContext(); #if JUCE_WINDOWS mainWindow->setOpaque(true); #else mainWindow->setOpaque(false); #endif + editor->nvgSurface.detachContext(); } desktopWindow = editor->getPeer(); @@ -55,10 +55,7 @@ class PluginMode : public Component, public NVGComponent { originalLockedMode = getValue(cnv->locked); originalPresentationMode = getValue(cnv->presentationMode); -#if JUCE_LINUX - editor->sendLookAndFeelChange(); // TODO: this is just a hacky way to make sure all framebuffers area cleared. could be cleaner. - editor->nvgSurface.detachContext(); -#endif + editor->nvgSurface.invalidateAll(); cnv->setCachedComponentImage(new NVGSurface::InvalidationListener(editor->nvgSurface, cnv.get())); originalCanvas->patch.openInPluginMode = true; @@ -204,10 +201,7 @@ class PluginMode : public Component, public NVGComponent { cnv->connectionLayer.setVisible(true); -#if JUCE_LINUX - editor->sendLookAndFeelChange(); // TODO: this is just a hacky way to make sure all framebuffers area cleared. could be cleaner. - editor->nvgSurface.detachContext(); -#endif + editor->nvgSurface.invalidateAll(); // Destroy this view editor->pluginMode.reset(nullptr); } diff --git a/Source/Standalone/PlugDataApp.cpp b/Source/Standalone/PlugDataApp.cpp index edfc6012b8..5e888af2b2 100644 --- a/Source/Standalone/PlugDataApp.cpp +++ b/Source/Standalone/PlugDataApp.cpp @@ -263,13 +263,11 @@ void PlugDataWindow::closeAllPatches() if (openedEditors.size() == 1) { editor->closeAllTabs(true, nullptr, [this, editor, &openedEditors]() { - editor->nvgSurface.detachContext(); removeFromDesktop(); openedEditors.removeObject(editor); }); } else { editor->closeAllTabs(false, nullptr, [this, editor, &openedEditors]() { - editor->nvgSurface.detachContext(); removeFromDesktop(); openedEditors.removeObject(editor); }); diff --git a/Source/Standalone/PlugDataWindow.h b/Source/Standalone/PlugDataWindow.h index 9394ce909d..ee464f0393 100644 --- a/Source/Standalone/PlugDataWindow.h +++ b/Source/Standalone/PlugDataWindow.h @@ -470,7 +470,7 @@ class PlugDataWindow : public DocumentWindow setUsingNativeTitleBar(nativeWindow); - //pdEditor->nvgSurface.initialise(); + pdEditor->nvgSurface.detachContext(); if (!nativeWindow) { #if JUCE_WINDOWS diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Tabbar/WelcomePanel.h index 3b5804055e..60dac9db63 100644 --- a/Source/Tabbar/WelcomePanel.h +++ b/Source/Tabbar/WelcomePanel.h @@ -270,9 +270,15 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater setVisible(false); } + void clearBuffers() + { + nvgContext.reset(nullptr); + handleAsyncUpdate(); + } + void render(NVGcontext* nvg) override { - if(!nvgContext) nvgContext = std::make_unique(nvg); + if(!nvgContext || nvgContext->getContext() != nvg) nvgContext = std::make_unique(nvg); nvgBeginPath(nvg); nvgRect(nvg, 0, 0, getWidth(), getHeight()); diff --git a/Source/Utility/CachedTextRender.h b/Source/Utility/CachedTextRender.h index 5eee0133b0..ca694d8db5 100644 --- a/Source/Utility/CachedTextRender.h +++ b/Source/Utility/CachedTextRender.h @@ -8,7 +8,7 @@ class CachedTextRender void renderText(NVGcontext* nvg, Rectangle const& bounds, float scale) { - if(updateImage || imageId <= 0 || lastRenderBounds != bounds || lastScale != scale) + if(updateImage || !image.isValid() || lastRenderBounds != bounds || lastScale != scale) { renderTextToImage(nvg, Rectangle(bounds.getX(), bounds.getY(), bounds.getWidth() + 3, bounds.getHeight()), scale); lastRenderBounds = bounds; @@ -20,7 +20,7 @@ class CachedTextRender nvgSave(nvg); nvgIntersectScissor(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); nvgRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth() + 3, bounds.getHeight()); - nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, bounds.getWidth() + 3, bounds.getHeight(), 0, imageId, 1.0f)); + nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, bounds.getWidth() + 3, bounds.getHeight(), 0, image.getImageId(), 1.0f)); nvgFill(nvg); nvgRestore(nvg); } @@ -55,44 +55,11 @@ class CachedTextRender int width = std::floor(bounds.getWidth() * scale); int height = std::floor(bounds.getHeight() * scale); - Image textImage = Image(Image::ARGB, width, height, true); - { - Graphics g(textImage); + image = NVGImage(nvg, width, height, [this, bounds, scale](Graphics& g){ g.addTransform(AffineTransform::scale(scale, scale)); g.reduceClipRegion(bounds.withTrimmedRight(4)); // If it touches the edges of the image, it'll look bad layout.draw(g, bounds.toFloat()); - } - - Image::BitmapData imageData(textImage, juce::Image::BitmapData::readOnly); - - uint8* pixelData = imageData.data; - for (int y = 0; y < height; ++y) - { - auto* scanLine = (uint32*) imageData.getLinePointer(y); - - for (int x = 0; x < width; ++x) - { - juce::uint32 argb = scanLine[x]; - - juce::uint8 a = argb >> 24; - juce::uint8 r = argb >> 16; - juce::uint8 g = argb >> 8; - juce::uint8 b = argb; - - // order bytes as abgr - scanLine[x] = (a << 24) | (b << 16) | (g << 8) | r; - } - } - - if(imageId && imageWidth == width && imageHeight == height) { - nvgUpdateImage(nvg, imageId, pixelData); - } - else { - if(imageId) nvgDeleteImage(nvg, imageId); - imageId = nvgCreateImageRGBA(nvg, width, height, NVG_IMAGE_PREMULTIPLIED, pixelData); - imageWidth = width; - imageHeight = height; - } + }); } Rectangle getTextBounds() @@ -102,8 +69,7 @@ class CachedTextRender private: - int imageId = 0; - int imageWidth = 0, imageHeight = 0; + NVGImage image; hash32 lastTextHash; float lastScale = 1.0f; Colour lastColour; diff --git a/Source/Utility/NVGComponent.cpp b/Source/Utility/NVGComponent.cpp index faeed841ca..bc279ac12a 100644 --- a/Source/Utility/NVGComponent.cpp +++ b/Source/Utility/NVGComponent.cpp @@ -2,7 +2,6 @@ #include "Utility/Config.h" #include "NVGComponent.h" -#include "NanoVGGraphicsContext.h" #include "nanovg.h" diff --git a/Source/Utility/NVGComponent.h b/Source/Utility/NVGComponent.h index bcacc6ec3f..80508f9e04 100644 --- a/Source/Utility/NVGComponent.h +++ b/Source/Utility/NVGComponent.h @@ -15,76 +15,8 @@ class NVGComponent virtual void render(NVGcontext*) {}; -private: - std::unique_ptr nvgLLGC; - +private: Component& component; JUCE_DECLARE_WEAK_REFERENCEABLE(NVGComponent) }; - -class NVGImageRenderer -{ -public: - - static int convertImage(NVGcontext* nvg, Image& image, int imageToUpdate = -1) - { - Image::BitmapData imageData(image, Image::BitmapData::readOnly); - - int width = imageData.width; - int height = imageData.height; - uint8* pixelData = imageData.data; - - for (int y = 0; y < height; ++y) - { - auto* scanLine = (uint32*) imageData.getLinePointer(y); - - for (int x = 0; x < width; ++x) - { - uint32 argb = scanLine[x]; - - uint8 a = argb >> 24; - uint8 r = argb >> 16; - uint8 g = argb >> 8; - uint8 b = argb; - - // order bytes as abgr - scanLine[x] = (a << 24) | (b << 16) | (g << 8) | r; - } - } - - if(imageToUpdate >= 0) - { - nvgUpdateImage(nvg, imageToUpdate, pixelData); - return imageToUpdate; - } - - return nvgCreateImageRGBA(nvg, image.getWidth(), image.getHeight(), NVG_IMAGE_PREMULTIPLIED, pixelData); - } - - void renderComponentFromImage(NVGcontext* nvg, Component& component, float scale) - { - Image componentImage = component.createComponentSnapshot(Rectangle(0, 0, component.getWidth() + 1, component.getHeight()), false, scale); - if(componentImage.isNull()) return; - - if(imageId && lastWidth == componentImage.getWidth() && lastHeight == componentImage.getHeight()) { - imageId = convertImage(nvg, componentImage, imageId); - } - else { - if(imageId) nvgDeleteImage(nvg, imageId); - imageId = convertImage(nvg, componentImage); - lastWidth = componentImage.getWidth(); - lastHeight = componentImage.getHeight(); - } - - nvgBeginPath(nvg); - nvgRect(nvg, 0, 0, component.getWidth() + 1, component.getHeight()); - nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, component.getWidth() + 1, component.getHeight(), 0, imageId, 1.0f)); - nvgFill(nvg); - } - -private: - int imageId = 0; - int lastHeight = 0; - int lastWidth = 0; -}; diff --git a/Source/Utility/NanoVGGraphicsContext.cpp b/Source/Utility/NanoVGGraphicsContext.cpp index c30cd5489f..6bd25e832a 100644 --- a/Source/Utility/NanoVGGraphicsContext.cpp +++ b/Source/Utility/NanoVGGraphicsContext.cpp @@ -134,6 +134,7 @@ void NanoVGGraphicsContext::clipToImageAlpha (const juce::Image& sourceImage, co // Restore the original transformations nvgRestore(nvg); + nvgDeleteImage(nvg, image); } } diff --git a/Source/Utility/StackShadow.cpp b/Source/Utility/StackShadow.cpp index 77ddbc91d9..d5996e7cf2 100644 --- a/Source/Utility/StackShadow.cpp +++ b/Source/Utility/StackShadow.cpp @@ -24,7 +24,7 @@ void StackShadow::renderDropShadow(juce::Graphics& g, juce::Path const& path, ju } -int StackShadow::createActivityDropShadowImage(NVGcontext* nvg, juce::Rectangle bounds, juce::Path const& path, juce::Colour color, int radius, juce::Point offset, int spread, bool isCanvas) +NVGImage StackShadow::createActivityDropShadowImage(NVGcontext* nvg, juce::Rectangle bounds, juce::Path const& path, juce::Colour color, int radius, juce::Point offset, int spread, bool isCanvas) { Image shadow(Image::ARGB, bounds.getWidth(), bounds.getHeight(), true); Graphics g(shadow); @@ -45,7 +45,9 @@ int StackShadow::createActivityDropShadowImage(NVGcontext* nvg, juce::Rectangle< renderDropShadow(g, path, color, radius, offset, spread); - return NVGImageRenderer::convertImage(nvg, shadow); + NVGImage image; + image.loadJUCEImage(nvg, shadow); + return image; } JUCE_IMPLEMENT_SINGLETON(StackShadow) diff --git a/Source/Utility/StackShadow.h b/Source/Utility/StackShadow.h index 30e84186f2..b125ba302c 100644 --- a/Source/Utility/StackShadow.h +++ b/Source/Utility/StackShadow.h @@ -17,7 +17,7 @@ struct StackShadow : public juce::DeletedAtShutdown static void renderDropShadow(juce::Graphics& g, juce::Path const& path, juce::Colour color, int radius = 1, juce::Point offset = { 0, 0 }, int spread = 0); - static int createActivityDropShadowImage(NVGcontext* nvg, juce::Rectangle bounds, juce::Path const& path, juce::Colour color, int radius = 1, juce::Point offset = { 0, 0 }, int spread = 0, bool isCanvas = false); + static NVGImage createActivityDropShadowImage(NVGcontext* nvg, juce::Rectangle bounds, juce::Path const& path, juce::Colour color, int radius = 1, juce::Point offset = { 0, 0 }, int spread = 0, bool isCanvas = false); melatonin::DropShadow* dropShadow; From c98364d3547bece2d5b62089cf944584d0c48bc9 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 31 May 2024 18:28:01 +0200 Subject: [PATCH 0840/1030] Small simplifications --- Source/NVGSurface.h | 25 +++------------------- Source/Objects/ObjectBase.h | 3 +-- Source/Objects/PictureObject.h | 11 ++++++---- Source/Utility/StackShadow.cpp | 39 +++++++++++++++------------------- 4 files changed, 28 insertions(+), 50 deletions(-) diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 10da79b953..a72e0ebead 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -130,15 +130,6 @@ class NVGImage { public: - NVGImage(NVGcontext* ctx, int width, int height, int image) - : nvg(ctx) - , imageId(image) - , imageWidth(width) - , imageHeight(height) - { - allImages.insert(this); - } - NVGImage(NVGcontext* nvg, int width, int height, std::function renderCall) { Image image = Image(Image::ARGB, width, height, true); @@ -147,13 +138,7 @@ class NVGImage loadJUCEImage(nvg, image); allImages.insert(this); } - - NVGImage(NVGcontext* nvg, Image image) - { - loadJUCEImage(nvg, image); - allImages.insert(this); - } - + NVGImage() { allImages.insert(this); @@ -178,6 +163,7 @@ class NVGImage // Check for self-assignment if (this != &other) { nvg = other.nvg; + imageId = other.imageId; imageWidth = other.imageWidth; imageHeight = other.imageHeight; @@ -250,7 +236,7 @@ class NVGImage if(imageId && imageWidth == width && imageHeight == height && nvg == context) { - update(pixelData); + nvgUpdateImage(nvg, imageId, pixelData); } else { nvg = context; @@ -260,11 +246,6 @@ class NVGImage } } - void update(uint8* pixelData) - { - nvgUpdateImage(nvg, imageId, pixelData); - } - bool needsUpdate(int width, int height) { return imageId == 0 || width != imageWidth || height != imageHeight; diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index 8658ba4a8d..71498bf718 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -71,8 +71,7 @@ class ObjectLabel : public Label, public NVGComponent { void updateImage(NVGcontext* nvg, float scale) { - auto componentImage = createComponentSnapshot(Rectangle(0, 0, getWidth() + 1, getHeight()), false, scale); - image.loadJUCEImage(nvg, componentImage); + image.renderJUCEComponent(nvg, *this, scale); } private: diff --git a/Source/Objects/PictureObject.h b/Source/Objects/PictureObject.h index acff61e0d3..ecdeb2803c 100644 --- a/Source/Objects/PictureObject.h +++ b/Source/Objects/PictureObject.h @@ -19,7 +19,6 @@ class PictureObject final : public ObjectBase { Image img; std::vector, Rectangle>> imageBuffers; bool imageNeedsReload = false; - uint8 pixelDataBuffer[8192 * 8192 * 4]; public: PictureObject(pd::WeakReference ptr, Object* object) @@ -49,7 +48,6 @@ class PictureObject final : public ObjectBase { ~PictureObject() { - // TODO: delete image buffers! } bool isTransparent() override @@ -143,8 +141,13 @@ class PictureObject final : public ObjectBase { { int height = std::min(8192, imageHeight - y); auto bounds = Rectangle(x, y, width, height); - auto clip = img.getClippedImage(bounds).createCopy(); - imageBuffers.emplace_back(std::make_unique(nvg, clip), bounds); + auto clip = img.getClippedImage(bounds); + + auto partialImage = std::make_unique(nvg, width, height, [&clip](Graphics& g){ + g.drawImageAt(clip, 0, 0); + }); + + imageBuffers.emplace_back(std::move(partialImage), bounds); y += 8192; } diff --git a/Source/Utility/StackShadow.cpp b/Source/Utility/StackShadow.cpp index d5996e7cf2..fef8917fe9 100644 --- a/Source/Utility/StackShadow.cpp +++ b/Source/Utility/StackShadow.cpp @@ -26,28 +26,23 @@ void StackShadow::renderDropShadow(juce::Graphics& g, juce::Path const& path, ju NVGImage StackShadow::createActivityDropShadowImage(NVGcontext* nvg, juce::Rectangle bounds, juce::Path const& path, juce::Colour color, int radius, juce::Point offset, int spread, bool isCanvas) { - Image shadow(Image::ARGB, bounds.getWidth(), bounds.getHeight(), true); - Graphics g(shadow); - - // make a hole in the middle of the dropshadow so that GOP doesn't render internal activity shadow - if (isCanvas) { - Path outside; - outside.addRectangle(0, 0, bounds.getWidth(), bounds.getHeight()); - outside.setUsingNonZeroWinding(false); - Path inside; - auto boundsRounded = bounds.toFloat().reduced(6.5); - // FIXME: we need to offset by -0.5px because the whole shadow is +0.5 px offset incorrectly, remove this and fix alignment of the whole shadow - boundsRounded.translate(-0.5f, -0.5f); - inside.addRoundedRectangle(boundsRounded, radius - 2.5f, radius - 2.5f); - outside.addPath(inside); - g.reduceClipRegion(outside); - } - - renderDropShadow(g, path, color, radius, offset, spread); - - NVGImage image; - image.loadJUCEImage(nvg, shadow); - return image; + return NVGImage(nvg, bounds.getWidth(), bounds.getHeight(), [=](Graphics& g){ + // make a hole in the middle of the dropshadow so that GOP doesn't render internal activity shadow + if (isCanvas) { + Path outside; + outside.addRectangle(0, 0, bounds.getWidth(), bounds.getHeight()); + outside.setUsingNonZeroWinding(false); + Path inside; + auto boundsRounded = bounds.toFloat().reduced(6.5); + // FIXME: we need to offset by -0.5px because the whole shadow is +0.5 px offset incorrectly, remove this and fix alignment of the whole shadow + boundsRounded.translate(-0.5f, -0.5f); + inside.addRoundedRectangle(boundsRounded, radius - 2.5f, radius - 2.5f); + outside.addPath(inside); + g.reduceClipRegion(outside); + } + + renderDropShadow(g, path, color, radius, offset, spread); + }); } JUCE_IMPLEMENT_SINGLETON(StackShadow) From 3f084cae4534ceb69802ccaa75978ca322238f2a Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sat, 1 Jun 2024 02:17:00 +0930 Subject: [PATCH 0841/1030] objects inside GOP's now trigger their parents overlay display --- Source/Object.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index e50392a6d7..d1e068ee8d 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -476,10 +476,14 @@ String Object::getType(bool withOriginPrefix) const void Object::triggerOverlayActiveState() { - if (!cnv->shouldShowObjectActivity()) + if (rateReducer.tooFast()) return; - if (rateReducer.tooFast()) + // propagate the activity overlay upwards if object is inside GOP + if (auto parentObject = findParentComponentOfClass()) + parentObject->triggerOverlayActiveState(); + + if (!cnv->shouldShowObjectActivity()) return; activeStateAlpha = 1.0f; From 3023252cda5fda2a9c0ac9e17d5bf7d0b367c603 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 31 May 2024 19:02:37 +0200 Subject: [PATCH 0842/1030] Don't show frame counter --- Source/NVGSurface.cpp | 2 +- Source/NVGSurface.h | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index a777d2254b..415649f856 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -20,7 +20,7 @@ using namespace juce::gl; #include "PluginProcessor.h" #include "Tabbar/WelcomePanel.h" -#define ENABLE_FPS_COUNT 1 +#define ENABLE_FPS_COUNT 0 class FrameTimer { diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index a72e0ebead..3c6ef1f74f 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -102,8 +102,6 @@ public Component, public Timer void resized() override; - void renderPerfMeter(NVGcontext* nvg); - PluginEditor* editor; NVGcontext* nvg = nullptr; bool needsBufferSwap = false; @@ -163,7 +161,6 @@ class NVGImage // Check for self-assignment if (this != &other) { nvg = other.nvg; - imageId = other.imageId; imageWidth = other.imageWidth; imageHeight = other.imageHeight; From 5d444f78cd25e9272e227bd87700ee77f878e738 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 31 May 2024 19:07:30 +0200 Subject: [PATCH 0843/1030] Updated pd --- Libraries/pure-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/pure-data b/Libraries/pure-data index 9edc79f3e9..bda42ba77c 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit 9edc79f3e972036c8d4df7c6038d527e7a102688 +Subproject commit bda42ba77c8da8167bdd1bfe99e25aa1529c843c From 13cfdd46a8b9640682e58c89b1b11de950553e1d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 31 May 2024 19:17:17 +0200 Subject: [PATCH 0844/1030] Cleaned up --- Source/Canvas.h | 2 +- Source/Components/DraggableNumber.h | 1 + Source/Components/SuggestionComponent.h | 1 + Source/Connection.cpp | 2 +- Source/Connection.h | 2 +- Source/Iolet.cpp | 1 + Source/Iolet.h | 2 - Source/NVGSurface.h | 55 ++++++++++++++++++++++++- Source/Object.h | 2 +- Source/ObjectGrid.h | 3 +- Source/Objects/ObjectBase.h | 2 +- Source/PluginProcessor.cpp | 4 +- Source/Sidebar/Sidebar.h | 2 +- Source/Tabbar/WelcomePanel.h | 1 + Source/Utility/NVGComponent.cpp | 51 ----------------------- Source/Utility/NVGComponent.h | 22 ---------- Source/Utility/StackShadow.cpp | 2 +- 17 files changed, 69 insertions(+), 86 deletions(-) delete mode 100644 Source/Utility/NVGComponent.cpp delete mode 100644 Source/Utility/NVGComponent.h diff --git a/Source/Canvas.h b/Source/Canvas.h index c3269ff0cd..5a15c14ac6 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -23,7 +23,7 @@ using namespace juce::gl; #include "Pd/Patch.h" #include "Constants.h" #include "Objects/ObjectParameters.h" -#include "Utility/NVGComponent.h" +#include "NVGSurface.h" #include "Utility/GlobalMouseListener.h" namespace pd { diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index b1634ddc4a..a7a1bbcd49 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -3,6 +3,7 @@ // For information on usage and redistribution, and for a DISCLAIMER OF ALL // WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ +#include "Utility/NanoVGGraphicsContext.h" #pragma once diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index 90a3cba8ec..803a7bb476 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -8,6 +8,7 @@ #include "PluginProcessor.h" // TODO: We shouldn't need this! #include "Objects/ObjectBase.h" #include "Heavy/CompatibleObjects.h" +#include "Utility/NanoVGGraphicsContext.h" extern "C" { diff --git a/Source/Connection.cpp b/Source/Connection.cpp index 1ee7d26035..098b159b59 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -10,8 +10,8 @@ using namespace juce::gl; #include #include "Utility/Config.h" #include "Utility/Fonts.h" -#include "Utility/NVGComponent.h" +#include "NVGSurface.h" #include "Connection.h" #include "Canvas.h" diff --git a/Source/Connection.h b/Source/Connection.h index d37ee839e1..90b2b0dee5 100644 --- a/Source/Connection.h +++ b/Source/Connection.h @@ -16,7 +16,7 @@ #include "Pd/MessageListener.h" #include "Utility/RateReducer.h" #include "Utility/ModifierKeyListener.h" -#include "Utility/NVGComponent.h" +#include "NVGSurface.h" using PathPlan = std::vector>; diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index b22230705f..8e15718306 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -11,6 +11,7 @@ using namespace juce::gl; #include "Utility/Config.h" #include "Utility/Fonts.h" +#include "NVGSurface.h" #include "Iolet.h" #include "Object.h" diff --git a/Source/Iolet.h b/Source/Iolet.h index 802a9b2d1c..03b9eded44 100644 --- a/Source/Iolet.h +++ b/Source/Iolet.h @@ -2,8 +2,6 @@ // For information on usage and redistribution, and for a DISCLAIMER OF ALL // WARRANTIES, see the file, "LICENSE.txt," in this distribution. -#include "Utility/NVGComponent.h" - #pragma once class Connection; diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 3c6ef1f74f..18aba7cb07 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -62,7 +62,6 @@ public Component, public Timer public: InvalidationListener(NVGSurface& s, Component* origin, bool passRepaintEvents = false) : surface(s), originComponent(origin), passEvents(passRepaintEvents) { - } void paint(Graphics& g) override {}; @@ -123,6 +122,60 @@ public Component, public Timer std::unique_ptr frameTimer; }; +class NVGComponent +{ +public: + NVGComponent(Component* comp) : component(*comp) {} + + static NVGcolor convertColour(Colour c) + { + return nvgRGBA(c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + } + + NVGcolor findNVGColour(int colourId) + { + return convertColour(component.findColour(colourId)); + } + + static void setJUCEPath(NVGcontext* nvg, Path const& p) + { + Path::Iterator i (p); + + nvgBeginPath (nvg); + + while (i.next()) + { + switch (i.elementType) + { + case Path::Iterator::startNewSubPath: + nvgBeginPath (nvg); + nvgMoveTo (nvg, i.x1, i.y1); + break; + case Path::Iterator::lineTo: + nvgLineTo (nvg, i.x1, i.y1); + break; + case Path::Iterator::quadraticTo: + nvgQuadTo (nvg, i.x1, i.y1, i.x2, i.y2); + break; + case Path::Iterator::cubicTo: + nvgBezierTo (nvg, i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); + break; + case Path::Iterator::closePath: + nvgClosePath (nvg); + break; + default: + break; + } + } + } + + virtual void render(NVGcontext*) {}; + +private: + Component& component; + + JUCE_DECLARE_WEAK_REFERENCEABLE(NVGComponent) +}; class NVGImage { diff --git a/Source/Object.h b/Source/Object.h index c8bd4d7b75..f1f098016b 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -10,8 +10,8 @@ #include #include "Utility/SettingsFile.h" #include "Utility/RateReducer.h" +#include "NVGSurface.h" #include "Pd/WeakReference.h" -#include "Utility/NVGComponent.h" #include #if NANOVG_GL_IMPLEMENTATION diff --git a/Source/ObjectGrid.h b/Source/ObjectGrid.h index dc5e33abc9..fe6c5ea6f0 100644 --- a/Source/ObjectGrid.h +++ b/Source/ObjectGrid.h @@ -6,11 +6,10 @@ #pragma once #include "Utility/SettingsFile.h" -#include "Utility/NVGComponent.h" +#include "NVGSurface.h" class Object; class Canvas; - struct ObjectGrid : public SettingsFileListener, public Timer { int gridSize = 20; diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index 71498bf718..919af7b565 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -11,7 +11,7 @@ #include "Constants.h" #include "ObjectParameters.h" #include "Utility/SynchronousValue.h" -#include "Utility/NVGComponent.h" +#include "NVGSurface.h" #include "Utility/CachedTextRender.h" #include "Object.h" #include "Canvas.h" diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index f2b2cc7b9e..78503e686a 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -20,7 +20,7 @@ #include "Utility/OSUtils.h" #include "Utility/AudioSampleRingBuffer.h" #include "Utility/MidiDeviceManager.h" -#include "Dialogs/ConnectionMessageDisplay.h" + #include "Utility/Presets.h" #include "Canvas.h" @@ -32,6 +32,8 @@ #include "Statusbar.h" #include "Dialogs/Dialogs.h" +#include "Dialogs/ConnectionMessageDisplay.h" + #include "Sidebar/Sidebar.h" extern "C" { diff --git a/Source/Sidebar/Sidebar.h b/Source/Sidebar/Sidebar.h index c47928196a..8a173a150f 100644 --- a/Source/Sidebar/Sidebar.h +++ b/Source/Sidebar/Sidebar.h @@ -9,7 +9,7 @@ #include "Components/Buttons.h" #include "Objects/ObjectParameters.h" #include "Utility/SettingsFile.h" -#include "Utility/NVGComponent.h" +#include "NVGSurface.h" class Console; class Inspector; diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Tabbar/WelcomePanel.h index 60dac9db63..be2e3f20b9 100644 --- a/Source/Tabbar/WelcomePanel.h +++ b/Source/Tabbar/WelcomePanel.h @@ -7,6 +7,7 @@ #pragma once #include "Utility/Autosave.h" #include "Utility/CachedTextRender.h" +#include "Utility/NanoVGGraphicsContext.h" class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater { diff --git a/Source/Utility/NVGComponent.cpp b/Source/Utility/NVGComponent.cpp deleted file mode 100644 index bc279ac12a..0000000000 --- a/Source/Utility/NVGComponent.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include -#include "Utility/Config.h" - -#include "NVGComponent.h" -#include "nanovg.h" - - -NVGComponent::NVGComponent(Component* comp) : component(*comp) {} - -void NVGComponent::setJUCEPath(NVGcontext* nvg, Path const& p) -{ - Path::Iterator i (p); - - nvgBeginPath (nvg); - - while (i.next()) - { - switch (i.elementType) - { - case Path::Iterator::startNewSubPath: - nvgBeginPath (nvg); - nvgMoveTo (nvg, i.x1, i.y1); - break; - case Path::Iterator::lineTo: - nvgLineTo (nvg, i.x1, i.y1); - break; - case Path::Iterator::quadraticTo: - nvgQuadTo (nvg, i.x1, i.y1, i.x2, i.y2); - break; - case Path::Iterator::cubicTo: - nvgBezierTo (nvg, i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); - break; - case Path::Iterator::closePath: - nvgClosePath (nvg); - break; - default: - break; - } - } -} - -NVGcolor NVGComponent::convertColour(Colour c) -{ - return nvgRGBA(c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); -} - - -NVGcolor NVGComponent::findNVGColour(int colourId) -{ - return convertColour(component.findColour(colourId)); -} diff --git a/Source/Utility/NVGComponent.h b/Source/Utility/NVGComponent.h deleted file mode 100644 index 80508f9e04..0000000000 --- a/Source/Utility/NVGComponent.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "NVGSurface.h" -#include "NanoVGGraphicsContext.h" - -class NVGComponent -{ -public: - - NVGComponent(Component*); - - static NVGcolor convertColour(Colour c); - NVGcolor findNVGColour(int colourId); - - static void setJUCEPath(NVGcontext* nvg, Path const& p); - - virtual void render(NVGcontext*) {}; - -private: - Component& component; - - JUCE_DECLARE_WEAK_REFERENCEABLE(NVGComponent) -}; diff --git a/Source/Utility/StackShadow.cpp b/Source/Utility/StackShadow.cpp index fef8917fe9..fdf5012f59 100644 --- a/Source/Utility/StackShadow.cpp +++ b/Source/Utility/StackShadow.cpp @@ -1,5 +1,5 @@ #include "StackShadow.h" -#include "NVGComponent.h" +#include "NVGSurface.h" #include From 570f9ee95b28b9f4f1534b410210f83f2b26c58e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 31 May 2024 19:21:09 +0200 Subject: [PATCH 0845/1030] Fixed shutdown bug --- Source/NVGSurface.cpp | 2 +- Source/Standalone/PlugDataApp.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 415649f856..67891aa4f4 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -149,7 +149,7 @@ void NVGSurface::initialise() void NVGSurface::detachContext() { - editor->welcomePanel->clearBuffers(); + if(editor->welcomePanel) editor->welcomePanel->clearBuffers(); NVGFramebuffer::clearAll(); NVGImage::clearAll(); diff --git a/Source/Standalone/PlugDataApp.cpp b/Source/Standalone/PlugDataApp.cpp index 5e888af2b2..edfc6012b8 100644 --- a/Source/Standalone/PlugDataApp.cpp +++ b/Source/Standalone/PlugDataApp.cpp @@ -263,11 +263,13 @@ void PlugDataWindow::closeAllPatches() if (openedEditors.size() == 1) { editor->closeAllTabs(true, nullptr, [this, editor, &openedEditors]() { + editor->nvgSurface.detachContext(); removeFromDesktop(); openedEditors.removeObject(editor); }); } else { editor->closeAllTabs(false, nullptr, [this, editor, &openedEditors]() { + editor->nvgSurface.detachContext(); removeFromDesktop(); openedEditors.removeObject(editor); }); From f4b97d89fb8066f8d7d2449da441cf38b4729dcd Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 31 May 2024 19:23:08 +0200 Subject: [PATCH 0846/1030] Fixed another shutdown bug --- Source/PluginEditor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index bb37a6fcc8..305d4303c5 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1865,6 +1865,7 @@ void PluginEditor::quit(bool askToSave) auto* window = dynamic_cast(getTopLevelComponent()); window->closeButtonPressed(); } else { + nvgSurface.detachContext(); JUCEApplication::quit(); } } From 8271cbe77abb8858fc5c84f6ef30384f9cab4b8c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 1 Jun 2024 13:43:52 +0200 Subject: [PATCH 0847/1030] Compilation fix --- Libraries/pure-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/pure-data b/Libraries/pure-data index bda42ba77c..9aef545588 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit bda42ba77c8da8167bdd1bfe99e25aa1529c843c +Subproject commit 9aef545588467093a9d315851c1c76d0e7484a18 From a4bf1f32dd30b2bbe1026da154d769cb9c04d6f8 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 1 Jun 2024 19:44:24 +0200 Subject: [PATCH 0848/1030] Small rendering improvements --- Source/Components/SuggestionComponent.h | 2 +- Source/NVGSurface.cpp | 4 +-- Source/NVGSurface.h | 28 ++++++++++++------ Source/Tabbar/WelcomePanel.h | 36 +++++++++++++++++------- Source/Utility/NanoVGGraphicsContext.cpp | 4 +-- 5 files changed, 51 insertions(+), 23 deletions(-) diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index 803a7bb476..e4b30f5c01 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -146,7 +146,7 @@ class AutoCompleteComponent auto completionBounds = getLocalBounds().toFloat().withTrimmedLeft(editorTextWidth + 7.5f); auto colour = findColour(PlugDataColour::canvasTextColourId).withAlpha(0.65f); - Fonts::drawText(g, suggestion, completionBounds.translated(-1.25f, -0.25f), colour); + Fonts::drawText(g, suggestion, completionBounds.translated(-1.25f, -1.25f), colour); } }; // Suggestions component that shows up when objects are edited diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 67891aa4f4..db1d5728ae 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -150,8 +150,8 @@ void NVGSurface::initialise() void NVGSurface::detachContext() { if(editor->welcomePanel) editor->welcomePanel->clearBuffers(); - NVGFramebuffer::clearAll(); - NVGImage::clearAll(); + NVGFramebuffer::clearAll(nvg); + NVGImage::clearAll(nvg); if(invalidFBO) { nvgDeleteFramebuffer(invalidFBO); diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 18aba7cb07..2f852bfdbe 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -231,11 +231,11 @@ class NVGImage allImages.erase(this); } - static void clearAll() + static void clearAll(NVGcontext* nvg) { for(auto* image : allImages) { - if(image->isValid() && image->nvg) nvgDeleteImage(image->nvg, image->imageId); + if(image->isValid() && image->nvg == nvg) nvgDeleteImage(image->nvg, image->imageId); image->imageId = 0; } } @@ -247,14 +247,14 @@ class NVGImage void renderJUCEComponent(NVGcontext* nvg, Component& component, float scale) { - Image componentImage = component.createComponentSnapshot(Rectangle(0, 0, component.getWidth() + 1, component.getHeight()), false, scale); + Image componentImage = component.createComponentSnapshot(Rectangle(0, 0, component.getWidth(), component.getHeight()), false, scale); if(componentImage.isNull()) return; loadJUCEImage(nvg, componentImage); nvgBeginPath(nvg); - nvgRect(nvg, 0, 0, component.getWidth() + 1, component.getHeight()); - nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, component.getWidth() + 1, component.getHeight(), 0, imageId, 1.0f)); + nvgRect(nvg, 0, 0, component.getWidth(), component.getHeight()); + nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, component.getWidth(), component.getHeight(), 0, imageId, 1.0f)); nvgFill(nvg); } @@ -296,6 +296,16 @@ class NVGImage } } + void render(NVGcontext* nvg, Rectangle b) + { + if(imageId) { + nvgBeginPath(nvg); + nvgRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight()); + nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, b.getWidth(), b.getHeight(), 0, imageId, 1)); + nvgFill(nvg); + } + } + bool needsUpdate(int width, int height) { return imageId == 0 || width != imageWidth || height != imageHeight; @@ -327,11 +337,11 @@ class NVGFramebuffer allFramebuffers.erase(this); } - static void clearAll() + static void clearAll(NVGcontext* nvg) { for(auto* buffer : allFramebuffers) { - if(buffer->fb) nvgDeleteFramebuffer(buffer->fb); + if(buffer->nvg == nvg && buffer->fb) nvgDeleteFramebuffer(buffer->fb); buffer->fb = nullptr; } } @@ -351,9 +361,10 @@ class NVGFramebuffer fbDirty = true; } - void bind(NVGcontext* nvg, int width, int height) + void bind(NVGcontext* ctx, int width, int height) { if(!fb || fbWidth != width || fbHeight != height) { + nvg = ctx; if(fb) nvgDeleteFramebuffer(fb); fb = nvgCreateFramebuffer(nvg, width, height, NVG_IMAGE_PREMULTIPLIED); fbWidth = width; @@ -396,6 +407,7 @@ class NVGFramebuffer private: static inline std::set allFramebuffers; + NVGcontext* nvg; NVGframebuffer* fb = nullptr; int fbWidth, fbHeight; bool fbDirty = false; diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Tabbar/WelcomePanel.h index be2e3f20b9..956f410295 100644 --- a/Source/Tabbar/WelcomePanel.h +++ b/Source/Tabbar/WelcomePanel.h @@ -15,9 +15,9 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater { float snapshotScale; bool isHovered = false; - String tileName; + String tileName, tileSubtitle; std::unique_ptr snapshot = nullptr; - CachedTextRender titleRenderer, subtitleRenderer; + NVGImage titleImage, subtitleImage; public: bool isFavourited; @@ -25,7 +25,7 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater std::function onFavourite = nullptr; WelcomePanelTile(String name, String subtitle, String svgImage, Colour iconColour, float scale, bool favourited) - : snapshotScale(scale), tileName(name), isFavourited(favourited) + : snapshotScale(scale), tileName(name), tileSubtitle(subtitle), isFavourited(favourited) { snapshot = Drawable::createFromImageData(svgImage.toRawUTF8(), svgImage.getNumBytesAsUTF8()); if(snapshot) { @@ -33,10 +33,6 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater } resized(); - - auto textColour = findColour(PlugDataColour::panelTextColourId); - titleRenderer.prepareLayout(name, Fonts::getBoldFont().withHeight(14), textColour, 500, 500); - subtitleRenderer.prepareLayout(subtitle, Fonts::getCurrentFont().withHeight(13.5f), textColour, 500, 500); } void paint(Graphics& g) override @@ -65,11 +61,31 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater g.strokePath(tilePath, PathStrokeType(1.0f)); auto* nvg = dynamic_cast(g.getInternalContext()).getContext(); + + auto textWidth = bounds.getWidth() - 8; + if(titleImage.needsUpdate(textWidth, 24) || subtitleImage.needsUpdate(textWidth, 16)) + { + auto textColour = findColour(PlugDataColour::panelTextColourId); + titleImage = NVGImage(nvg, textWidth * 2.0f, 24 * 2.0f, [this, textColour, textWidth](Graphics& g){ + g.addTransform(AffineTransform::scale(2.0f, 2.0f)); + g.setColour(textColour); + g.setFont(Fonts::getBoldFont().withHeight(14)); + g.drawText(tileName, Rectangle(0, 0, textWidth, 24), Justification::centredLeft, true); + }); + + subtitleImage = NVGImage(nvg, textWidth * 2.0f, 16 * 2.0f, [this, textColour, textWidth](Graphics& g){ + g.addTransform(AffineTransform::scale(2.0f, 2.0f)); + g.setColour(textColour); + g.setFont(Fonts::getDefaultFont().withHeight(13.5f)); + g.drawText(tileSubtitle, Rectangle(0, 0, textWidth, 16), Justification::centredLeft, true); + }); + } + nvgSave(nvg); - nvgTranslate(nvg, 0, bounds.getHeight() - 30); - titleRenderer.renderText(nvg, Rectangle(bounds.getX() + 10, 0, bounds.getWidth() - 8, 24), 2.0f); + nvgTranslate(nvg, 22, bounds.getHeight() - 30); + titleImage.render(nvg, Rectangle(0, 0, bounds.getWidth() - 8, 24)); nvgTranslate(nvg, 0, 20); - subtitleRenderer.renderText(nvg, Rectangle(bounds.getX() + 10, 0, bounds.getWidth() - 8, 16), 2.0f); + subtitleImage.render(nvg, Rectangle(0, 0, bounds.getWidth() - 8, 16)); nvgRestore(nvg); if(onFavourite) diff --git a/Source/Utility/NanoVGGraphicsContext.cpp b/Source/Utility/NanoVGGraphicsContext.cpp index 6bd25e832a..a2da321750 100644 --- a/Source/Utility/NanoVGGraphicsContext.cpp +++ b/Source/Utility/NanoVGGraphicsContext.cpp @@ -415,7 +415,7 @@ void NanoVGGraphicsContext::setFont (const juce::Font& f) currentGlyphToCharMap = &loadedFonts[typefaceName]; nvgFontFace(nvg, typefaceName.toUTF8()); - nvgFontSize (nvg, font.getHeight() * 0.86f); + nvgFontSize (nvg, font.getHeight() * 0.862f); nvgTextLetterSpacing(nvg, -0.275f); } @@ -435,7 +435,7 @@ void NanoVGGraphicsContext::drawGlyph (int glyphNumber, const juce::AffineTransf setFont(getFont()); nvgTransform (nvg, transform.mat00, transform.mat10, transform.mat01, transform.mat11, transform.mat02, transform.mat12); nvgTextAlign (nvg, NVG_ALIGN_BASELINE | NVG_ALIGN_LEFT); - nvgText (nvg, 0, 0, txt, &txt[1]); + nvgText (nvg, 0, 1, txt, &txt[1]); nvgRestore(nvg); } From 2174762fb527b85e94c56edd9d5dd4e4e3bc45d2 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sat, 1 Jun 2024 18:12:29 +0930 Subject: [PATCH 0849/1030] Canvas object UI hover --- Source/Objects/BangObject.h | 22 ++++++++++++++- Source/Objects/ButtonObject.h | 21 +++++++++++++-- Source/Objects/KnobObject.h | 51 ++++++++++++++++++++++++++++------- Source/Objects/ObjectBase.cpp | 11 ++++++++ Source/Objects/ObjectBase.h | 2 ++ Source/Objects/RadioObject.h | 47 +++++++++++++++++++++++++++----- Source/Objects/SliderObject.h | 21 +++++++++++++-- Source/Objects/ToggleObject.h | 23 ++++++++++++++-- 8 files changed, 176 insertions(+), 22 deletions(-) diff --git a/Source/Objects/BangObject.h b/Source/Objects/BangObject.h index 766dcbca6b..f515c53813 100644 --- a/Source/Objects/BangObject.h +++ b/Source/Objects/BangObject.h @@ -16,6 +16,8 @@ class BangObject final : public ObjectBase { IEMHelper iemHelper; + bool mouseHover = false; + public: BangObject(pd::WeakReference obj, Object* parent) : ObjectBase(obj, parent) @@ -102,13 +104,31 @@ class BangObject final : public ObjectBase { alreadyBanged = true; trigger(); } + + void mouseEnter(MouseEvent const& e) override + { + mouseHover = true; + repaint(); + } + + void mouseExit(MouseEvent const& e) override + { + mouseHover = false; + repaint(); + } void render(NVGcontext* nvg) override { auto b = getLocalBounds().toFloat().reduced(0.5f); auto foregroundColour = convertColour(getValue(iemHelper.primaryColour)); // TODO: this is some bad threading practice! - auto backgroundColour = convertColour(getValue(iemHelper.secondaryColour)); + + auto bgColour = getValue(iemHelper.secondaryColour); + + if (mouseHover) + bgColour = brightenOrDarken(bgColour); + + auto backgroundColour = convertColour(bgColour); auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); auto internalLineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour)); diff --git a/Source/Objects/ButtonObject.h b/Source/Objects/ButtonObject.h index 7571fe16ef..2c34959cf7 100644 --- a/Source/Objects/ButtonObject.h +++ b/Source/Objects/ButtonObject.h @@ -8,6 +8,7 @@ class ButtonObject : public ObjectBase { bool state = false; bool alreadyTriggered = false; + bool mouseHover = false; Value primaryColour = SynchronousValue(); Value secondaryColour = SynchronousValue(); @@ -203,13 +204,29 @@ class ButtonObject : public ObjectBase { repaint(); } - + + void mouseEnter(MouseEvent const& e) override + { + mouseHover = true; + repaint(); + } + + void mouseExit(MouseEvent const& e) override + { + mouseHover = false; + repaint(); + } + void render(NVGcontext* nvg) override { auto b = getLocalBounds().toFloat(); auto foregroundColour = convertColour(Colour::fromString(primaryColour.toString())); - auto backgroundColour = convertColour(Colour::fromString(secondaryColour.toString())); + auto bgColour = Colour::fromString(secondaryColour.toString()); + if (mouseHover) + bgColour = brightenOrDarken(bgColour); + + auto backgroundColour = convertColour(bgColour); auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); auto outlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); auto internalLineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour)); diff --git a/Source/Objects/KnobObject.h b/Source/Objects/KnobObject.h index 21ded5db80..ebc837edb9 100644 --- a/Source/Objects/KnobObject.h +++ b/Source/Objects/KnobObject.h @@ -4,7 +4,7 @@ // WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ -#include +#include #include "TclColours.h" extern "C" { @@ -23,6 +23,9 @@ class Knob : public Slider, public NVGComponent { float arcStart = 63.5f; public: + bool hasOutline = true; + bool ticks = false; + Knob() : Slider(Slider::RotaryHorizontalVerticalDrag, Slider::NoTextBox), NVGComponent(this) { @@ -55,6 +58,31 @@ class Knob : public Slider, public NVGComponent { } } + bool hitTest(int x, int y) override + { + if (hasOutline) return true; + + // If knob is circular limit hit test to circle, and expand more if there are ticks around the knob + auto centre = getLocalBounds().toFloat().getCentre(); + auto knobRadius = getWidth() * 0.33f; + auto knobRadiusWithTicks = knobRadius + (getWidth() * 0.06f); + if (centre.getDistanceFrom(Point(x, y)) < (ticks ? knobRadiusWithTicks : knobRadius)) { + return true; + } + + return false; + } + + void mouseEnter(const MouseEvent& e) override + { + getParentComponent()->getProperties().set("hover", true); + } + + void mouseExit(const MouseEvent& e) override + { + getParentComponent()->getProperties().set("hover", false); + } + void showArc(bool show) { drawArc = show; @@ -241,7 +269,7 @@ class KnobObject : public ObjectBase { if (auto knb = ptr.get()) { initialValue = knb->x_load; - ticks = knb->x_ticks; + ticks = knob.ticks = knb->x_ticks; angularRange = knb->x_range; angularOffset = knb->x_offset; discrete = knb->x_discrete; @@ -251,7 +279,7 @@ class KnobObject : public ObjectBase { primaryColour = getForegroundColour().toString(); secondaryColour = getBackgroundColour().toString(); arcColour = getArcColour().toString(); - outline = knb->x_outline; + outline = knob.hasOutline = knb->x_outline; sizeProperty = knb->x_size; arcStart = knb->x_start; } @@ -433,17 +461,22 @@ class KnobObject : public ObjectBase { void render(NVGcontext* nvg) override { auto b = getLocalBounds().toFloat().reduced(0.5f); + auto bgColour = Colour::fromString(secondaryColour.toString()); + + if (getProperties()["hover"]) + bgColour = brightenOrDarken(bgColour); + if (::getValue(outline)) { bool selected = object->isSelected() && !cnv->isGraph; auto outlineColour = LookAndFeel::getDefaultLookAndFeel().findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); - nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), convertColour(Colour::fromString(secondaryColour.toString())), convertColour(outlineColour), Corners::objectCornerRadius); + nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), convertColour(bgColour), convertColour(outlineColour), Corners::objectCornerRadius); } else { auto circleBounds = getLocalBounds().toFloat().reduced(getWidth() * 0.13f); auto const lineThickness = std::max(circleBounds.getWidth() * 0.07f, 1.5f); circleBounds = circleBounds.reduced(lineThickness - 0.5f); - nvgFillColor(nvg, convertColour(Colour::fromString(secondaryColour.toString()))); + nvgFillColor(nvg, convertColour(bgColour)); nvgBeginPath(nvg); nvgCircle(nvg, circleBounds.getCentreX(), circleBounds.getCentreY(), circleBounds.getWidth() / 2.0f); nvgFill(nvg); @@ -662,7 +695,6 @@ class KnobObject : public ObjectBase { void valueChanged(Value& value) override { - if (value.refersToSameSourceAs(sizeProperty)) { auto* constrainer = getConstrainer(); auto size = std::max(::getValue(sizeProperty), constrainer->getMinimumWidth()); @@ -719,7 +751,7 @@ class KnobObject : public ObjectBase { } else if (value.refersToSameSourceAs(ticks)) { ticks = jmax(::getValue(ticks), 0); if (auto knb = ptr.get()) - knb->x_ticks = ::getValue(ticks); + knob.ticks = knb->x_ticks = ::getValue(ticks); updateRotaryParameters(); updateRange(); } else if (value.refersToSameSourceAs(angularRange)) { @@ -744,8 +776,9 @@ class KnobObject : public ObjectBase { knb->x_discrete = ::getValue(discrete); updateRange(); } else if (value.refersToSameSourceAs(outline)) { - if (auto knb = ptr.get()) - knb->x_outline = ::getValue(outline); + if (auto knb = ptr.get()) { + knob.hasOutline = knb->x_outline = ::getValue < bool > (outline); + } repaint(); } else if (value.refersToSameSourceAs(exponential)) { if (auto knb = ptr.get()) diff --git a/Source/Objects/ObjectBase.cpp b/Source/Objects/ObjectBase.cpp index 0ebf7a9428..bcab9a1a2b 100644 --- a/Source/Objects/ObjectBase.cpp +++ b/Source/Objects/ObjectBase.cpp @@ -177,6 +177,17 @@ ObjectBase::~ObjectBase() delete lnf; } + +Colour ObjectBase::brightenOrDarken(const Colour& colour) +{ + auto brightness = colour.getBrightness(); + const float threshold = 0.5f; + if (brightness < threshold) + return colour.brighter(0.05f); + else + return colour.darker(0.03f); +} + void ObjectBase::initialise() { update(); diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index 919af7b565..4c88674351 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -216,6 +216,8 @@ class ObjectBase : public Component ~ObjectBase() override; + Colour brightenOrDarken(const Colour& colour); + void initialise(); void paint(Graphics& g) override; diff --git a/Source/Objects/RadioObject.h b/Source/Objects/RadioObject.h index 11e18913fa..9970254e88 100644 --- a/Source/Objects/RadioObject.h +++ b/Source/Objects/RadioObject.h @@ -12,6 +12,9 @@ class RadioObject final : public ObjectBase { int selected; + int hoverIdx = -1; + bool mouseHover = false; + IEMHelper iemHelper; Value max = SynchronousValue(0.0f); @@ -149,6 +152,27 @@ class RadioObject final : public ObjectBase { alreadyToggled = false; } + void mouseEnter(MouseEvent const& e) override + { + mouseHover = true; + repaint(); + } + + void mouseExit(MouseEvent const& e) override + { + mouseHover = false; + repaint(); + } + + void mouseMove(MouseEvent const& e) override + { + float pos = isVertical ? e.y : e.x; + float div = isVertical ? getHeight() : getWidth(); + + hoverIdx = (pos / div) * numItems; + repaint(); + } + void mouseDown(MouseEvent const& e) override { if (!e.mods.isLeftButtonDown()) @@ -189,24 +213,35 @@ class RadioObject final : public ObjectBase { for (int i = 1; i < numItems; i++) { if (isVertical) { nvgBeginPath(nvg); - nvgMoveTo(nvg, 0, i * size); - nvgLineTo(nvg, size, i * size); + nvgMoveTo(nvg, 1, i * size); + nvgLineTo(nvg, size - 0.5, i * size); nvgStroke(nvg); } else { nvgBeginPath(nvg); - nvgMoveTo(nvg, i * size, 0); - nvgLineTo(nvg, i * size, size); + nvgMoveTo(nvg, i * size, 1); + nvgLineTo(nvg, i * size, size - 0.5); nvgStroke(nvg); } } - - nvgFillColor(nvg, convertColour(::getValue(iemHelper.primaryColour))); + + auto bgColour = brightenOrDarken(::getValue(iemHelper.secondaryColour)); + + if (mouseHover) { + nvgBeginPath(nvg); + float hoverX = isVertical ? 0 : hoverIdx * size; + float hoverY = isVertical ? hoverIdx * size : 0; + auto hoverBounds = Rectangle(hoverX, hoverY, size, size).reduced(jmin(size * 0.25f, 5)); + nvgFillColor(nvg, convertColour(bgColour)); + nvgRoundedRect(nvg, hoverBounds.getX(), hoverBounds.getY(), hoverBounds.getWidth(), hoverBounds.getHeight(), Corners::objectCornerRadius / 2.0f); + nvgFill(nvg); + } float selectionX = isVertical ? 0 : selected * size; float selectionY = isVertical ? selected * size : 0; auto selectionBounds = Rectangle(selectionX, selectionY, size, size).reduced(jmin(size * 0.25f, 5)); nvgBeginPath(nvg); + nvgFillColor(nvg, convertColour(::getValue(iemHelper.primaryColour))); nvgRoundedRect(nvg, selectionBounds.getX(), selectionBounds.getY(), selectionBounds.getWidth(), selectionBounds.getHeight(), Corners::objectCornerRadius / 2.0f); nvgFill(nvg); } diff --git a/Source/Objects/SliderObject.h b/Source/Objects/SliderObject.h index d239226678..ad7fb3aa9f 100644 --- a/Source/Objects/SliderObject.h +++ b/Source/Objects/SliderObject.h @@ -45,6 +45,18 @@ class ReversibleSlider : public Slider, public NVGComponent { Slider::resized(); } + void mouseEnter(MouseEvent const& e) override + { + getParentComponent()->getProperties().set("hover", true); + getParentComponent()->repaint(); + } + + void mouseExit(MouseEvent const& e) override + { + getParentComponent()->getProperties().set("hover", false); + getParentComponent()->repaint(); + } + void mouseDown(MouseEvent const& e) override { if (!e.mods.isLeftButtonDown()) @@ -323,8 +335,13 @@ class SliderObject : public ObjectBase { auto b = getLocalBounds().toFloat().reduced(0.5f); bool selected = object->isSelected() && !cnv->isGraph; auto outlineColour = LookAndFeel::getDefaultLookAndFeel().findColour(selected ? PlugDataColour::objectSelectedOutlineColourId : objectOutlineColourId); - - nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), convertColour(getLookAndFeel().findColour(Slider::backgroundColourId)), convertColour(outlineColour), Corners::objectCornerRadius); + + auto bgColour = getLookAndFeel().findColour(Slider::backgroundColourId); + + if (getProperties()["hover"]) + bgColour = brightenOrDarken(bgColour); + + nvgDrawRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), convertColour(bgColour), convertColour(outlineColour), Corners::objectCornerRadius); slider.render(nvg); } diff --git a/Source/Objects/ToggleObject.h b/Source/Objects/ToggleObject.h index c9979721af..1e8d7a3b10 100644 --- a/Source/Objects/ToggleObject.h +++ b/Source/Objects/ToggleObject.h @@ -12,6 +12,8 @@ class ToggleObject final : public ObjectBase { float value = 0.0f; + bool mouseHover = false; + IEMHelper iemHelper; public: @@ -71,8 +73,13 @@ class ToggleObject final : public ObjectBase { void render(NVGcontext* nvg) override { auto b = getLocalBounds().toFloat().reduced(0.5f); - - auto backgroundColour = convertColour(::getValue(iemHelper.secondaryColour)); + + + auto bgColour = ::getValue(iemHelper.secondaryColour); + if (mouseHover) + bgColour = brightenOrDarken(bgColour); + + auto backgroundColour = convertColour(bgColour); auto toggledColour = convertColour(::getValue(iemHelper.primaryColour)); // TODO: don't access audio thread variables in render loop auto untoggledColour = convertColour(::getValue(iemHelper.primaryColour).interpolatedWith(::getValue(iemHelper.secondaryColour), 0.8f)); auto selectedOutlineColour = convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); @@ -145,6 +152,18 @@ class ToggleObject final : public ObjectBase { alreadyToggled = true; } + void mouseEnter(MouseEvent const& e) override + { + mouseHover = true; + repaint(); + } + + void mouseExit(MouseEvent const& e) override + { + mouseHover = false; + repaint(); + } + void setToggleStateFromFloat(float newValue) { value = newValue; From 7c07e6da864bac2a4d661a6685a6b9721545e38e Mon Sep 17 00:00:00 2001 From: alcomposer Date: Sun, 2 Jun 2024 03:54:48 +0930 Subject: [PATCH 0850/1030] fix scrolling in new presentation mode setup --- Source/CanvasViewport.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/CanvasViewport.h b/Source/CanvasViewport.h index da5fa3ac62..7eba9b1df3 100644 --- a/Source/CanvasViewport.h +++ b/Source/CanvasViewport.h @@ -336,6 +336,12 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent panner.enablePanning(enablePanning); } + bool hitTest(int x, int y) override + { + // needed so that mouseWheel event is registered in presentation mode + return true; + } + void mouseWheelMove(MouseEvent const& e, MouseWheelDetails const& wheel) override { // Check event time to filter out duplicate events From 7fde472b3166c09047766063c02a6224419d768a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 1 Jun 2024 21:30:45 +0200 Subject: [PATCH 0851/1030] Fix potential div by zero --- Source/Tabbar/WelcomePanel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Tabbar/WelcomePanel.h index 956f410295..67e6afd549 100644 --- a/Source/Tabbar/WelcomePanel.h +++ b/Source/Tabbar/WelcomePanel.h @@ -166,7 +166,7 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater int totalWidth = bounds.getWidth(); // Calculate the number of columns that can fit in the total width - int numColumns = totalWidth / (desiredTileWidth + tileSpacing); + int numColumns = std::max(1, totalWidth / (desiredTileWidth + tileSpacing)); // Adjust the tile width to fit within the available width int actualTileWidth = (totalWidth - (numColumns - 1) * tileSpacing) / numColumns; From 6ee5b1cb7f68ef69844077944fefe1e8c683761d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 2 Jun 2024 00:05:29 +0200 Subject: [PATCH 0852/1030] Rendering fix --- Source/Canvas.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index c72c930ddb..41d49bf61a 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -188,11 +188,11 @@ bool Canvas::updateFramebuffers(NVGcontext* nvg, Rectangle invalidRegion, i // First, check if we need to update our iolet buffer if(ioletBuffer.needsUpdate(ioletBufferSize, ioletBufferSize)) { - ioletBuffer.renderToFramebuffer(nvg, ioletBufferSize, ioletBufferSize, [this, zoom, ioletBufferSize](NVGcontext* nvg){ + ioletBuffer.renderToFramebuffer(nvg, ioletBufferSize, ioletBufferSize, [this, zoom, ioletBufferSize, pixelScale](NVGcontext* nvg){ nvgViewport(0, 0, ioletBufferSize, ioletBufferSize); nvgClear(nvg); - nvgBeginFrame(nvg, logicalIoletsSize * zoom, logicalIoletsSize * zoom, ioletBufferSize); + nvgBeginFrame(nvg, logicalIoletsSize * zoom, logicalIoletsSize * zoom, pixelScale); nvgScale(nvg, zoom, zoom); auto renderIolet = [](NVGcontext* nvg, Rectangle bounds, NVGcolor background, NVGcolor outline){ From cdf67771d04500deb313301110fd32b39d7c159b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 2 Jun 2024 00:21:02 +0200 Subject: [PATCH 0853/1030] Fixed potential crash in DAW --- Source/Connection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Connection.cpp b/Source/Connection.cpp index 098b159b59..e803efea35 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -118,8 +118,8 @@ Connection::~Connection() } auto* nvg = cnv->editor->nvgSurface.getRawContext(); - if(cacheId >= 0) nvgDeletePath(nvg, cacheId); - if (cacheId >= 0 && cableType == SignalCable) { + if(nvg && cacheId >= 0) nvgDeletePath(nvg, cacheId); + if (nvg && cacheId >= 0 && cableType == SignalCable) { nvgDeletePath(nvg, std::numeric_limits::max() - cacheId); } } From 03d69dbf45e213eb945e8afb31d1ac7fca4aa19a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 2 Jun 2024 01:21:23 +0200 Subject: [PATCH 0854/1030] Trying to fix Linux crash --- .../raw_keyboard_input/src/Keyboard.cpp | 18 ++++++++++-------- .../raw_keyboard_input/src/Keyboard.h | 9 +++++---- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.cpp b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.cpp index 3906877f46..a0c2ad07b3 100644 --- a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.cpp +++ b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.cpp @@ -1,10 +1,10 @@ #include "Keyboard.h" -std::set Keyboard::thisses; +std::set> Keyboard::thisses; Keyboard::Keyboard(juce::Component* initialParent) : parent(initialParent) { - thisses.emplace(this); + thisses.emplace(juce::WeakReference(this)); startTimer(1); } @@ -21,12 +21,14 @@ bool Keyboard::processKeyEvent(int keyCode, bool isKeyDown) return false; for (auto t : thisses) { - if (t->peer == focusedPeer || (t->auxPeer != nullptr && t->auxPeer == focusedPeer)) { - if (isKeyDown) - t->addPressedKey(keyCode); - else - t->removePressedKey(keyCode); - } + if(t) { + if (t->peer == focusedPeer || (t->auxPeer != nullptr && t->auxPeer == focusedPeer)) { + if (isKeyDown) + t->addPressedKey(keyCode); + else + t->removePressedKey(keyCode); + } + } } return true; diff --git a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h index 6defa10c07..d76b10ed6b 100644 --- a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h +++ b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h @@ -23,15 +23,15 @@ class Keyboard : public juce::Timer { bool isKeyDown(int keyCode); void allKeysUp(); - + std::function onKeyDownFn; std::function onKeyUpFn; protected: - static std::set thisses; + static std::set> thisses; static juce::ComponentPeer* getFocusedPeer(); - + void addPressedKey(int keyCode); void removePressedKey(int keyCode); @@ -40,5 +40,6 @@ class Keyboard : public juce::Timer { juce::Component* parent; juce::Component* auxParent = nullptr; std::set pressedKeys; - + + JUCE_DECLARE_WEAK_REFERENCEABLE(Keyboard); }; From 252cc96ec2ca17b56ee777cdaff2fd6fff07bdf0 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 2 Jun 2024 16:54:31 +0200 Subject: [PATCH 0855/1030] Fix heavy dialog popping file opener on startup and panel change --- Source/Heavy/ExporterBase.h | 6 ++++-- Source/Heavy/HeavyExportDialog.cpp | 13 +++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Source/Heavy/ExporterBase.h b/Source/Heavy/ExporterBase.h index 226d71805e..9c86b977ce 100644 --- a/Source/Heavy/ExporterBase.h +++ b/Source/Heavy/ExporterBase.h @@ -14,9 +14,11 @@ struct ExporterBase : public Component , public ThreadPool { TextButton exportButton = TextButton("Export"); - Value inputPatchValue; + Value inputPatchValue = SynchronousValue(); Value projectNameValue; Value projectCopyrightValue; + + bool blockDialog = false; #if JUCE_WINDOWS inline static String const exeSuffix = ".exe"; @@ -186,7 +188,7 @@ struct ExporterBase : public Component if (idx == 1) { patchFile = openedPatchFile; validPatchSelected = true; - } else if (idx == 2) { + } else if (idx == 2 && !blockDialog) { Dialogs::showOpenDialog([this](URL url) { auto result = url.getLocalFile(); if (result.existsAsFile()) { diff --git a/Source/Heavy/HeavyExportDialog.cpp b/Source/Heavy/HeavyExportDialog.cpp index 2fc036a74f..7ad0b43bbc 100644 --- a/Source/Heavy/HeavyExportDialog.cpp +++ b/Source/Heavy/HeavyExportDialog.cpp @@ -84,10 +84,12 @@ class ExporterSettingsPanel : public Component auto heavyState = settingsTree.getChildWithName("HeavyState"); if (heavyState.isValid()) { this->setState(heavyState); - views[0]->setState(heavyState); - views[1]->setState(heavyState); - views[2]->setState(heavyState); - views[3]->setState(heavyState); + for(int i = 0; i < 4; i++) + { + views[i]->blockDialog = true; + views[i]->setState(heavyState); + views[i]->blockDialog = false; + } } } @@ -138,7 +140,10 @@ class ExporterSettingsPanel : public Component views[lastRowSelected]->patchFile = view->patchFile; views[lastRowSelected]->projectNameValue = view->projectNameValue.getValue(); views[lastRowSelected]->projectCopyrightValue = view->projectCopyrightValue.getValue(); + + views[lastRowSelected]->blockDialog = true; views[lastRowSelected]->inputPatchValue = view->inputPatchValue.getValue(); + views[lastRowSelected]->blockDialog = false; } view->setVisible(false); } From 691c3b6d1124c08d8685d8f539e7dc73be7e2ae6 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 2 Jun 2024 17:14:19 +0200 Subject: [PATCH 0856/1030] Compilation fix --- Source/NVGSurface.cpp | 1 - Source/Tabbar/WelcomePanel.h | 6 ------ 2 files changed, 7 deletions(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index db1d5728ae..426e5c3674 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -149,7 +149,6 @@ void NVGSurface::initialise() void NVGSurface::detachContext() { - if(editor->welcomePanel) editor->welcomePanel->clearBuffers(); NVGFramebuffer::clearAll(nvg); NVGImage::clearAll(nvg); diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Tabbar/WelcomePanel.h index 67e6afd549..36df4dad60 100644 --- a/Source/Tabbar/WelcomePanel.h +++ b/Source/Tabbar/WelcomePanel.h @@ -287,12 +287,6 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater setVisible(false); } - void clearBuffers() - { - nvgContext.reset(nullptr); - handleAsyncUpdate(); - } - void render(NVGcontext* nvg) override { if(!nvgContext || nvgContext->getContext() != nvg) nvgContext = std::make_unique(nvg); From c20321188653fdc96e66d3f5a676861c6ac7bddc Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 2 Jun 2024 18:20:25 +0200 Subject: [PATCH 0857/1030] Fixed numbox~ crash when opening search panel --- Source/Sidebar/SearchPanel.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Sidebar/SearchPanel.h b/Source/Sidebar/SearchPanel.h index ff7151b84a..4bfdb50def 100644 --- a/Source/Sidebar/SearchPanel.h +++ b/Source/Sidebar/SearchPanel.h @@ -302,7 +302,6 @@ class SearchPanel : public Component, public KeyListener, public Timer case hash("slider"): case hash("tgl"): case hash("nbx"): - case hash("numbox~"): case hash("vradio"): case hash("hradio"): case hash("vu"): From 4dfb5506b51060eb5ee356ba254521451ff1848e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 2 Jun 2024 18:20:31 +0200 Subject: [PATCH 0858/1030] Trying to fix data races --- .../raw_keyboard_input/src/Keyboard.cpp | 6 ++++++ .../raw_keyboard_input/src/Keyboard.h | 1 + 2 files changed, 7 insertions(+) diff --git a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.cpp b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.cpp index a0c2ad07b3..20298f3955 100644 --- a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.cpp +++ b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.cpp @@ -4,13 +4,17 @@ std::set> Keyboard::thisses; Keyboard::Keyboard(juce::Component* initialParent) : parent(initialParent) { + instanceMutex.lock(); thisses.emplace(juce::WeakReference(this)); + instanceMutex.unlock(); startTimer(1); } Keyboard::~Keyboard() { + instanceMutex.lock(); thisses.erase(this); + instanceMutex.unlock(); } bool Keyboard::processKeyEvent(int keyCode, bool isKeyDown) @@ -20,6 +24,7 @@ bool Keyboard::processKeyEvent(int keyCode, bool isKeyDown) if (focusedPeer == nullptr) return false; + instanceMutex.lock(); for (auto t : thisses) { if(t) { if (t->peer == focusedPeer || (t->auxPeer != nullptr && t->auxPeer == focusedPeer)) { @@ -30,6 +35,7 @@ bool Keyboard::processKeyEvent(int keyCode, bool isKeyDown) } } } + instanceMutex.unlock(); return true; } diff --git a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h index d76b10ed6b..af0e0380de 100644 --- a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h +++ b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h @@ -37,6 +37,7 @@ class Keyboard : public juce::Timer { private: std::recursive_mutex pressedKeysMutex; + std::recursive_mutex instanceMutex; juce::Component* parent; juce::Component* auxParent = nullptr; std::set pressedKeys; From 26bd7a8b42f3a566cdb8c130eef1cea1eec8f6ed Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 2 Jun 2024 18:22:47 +0200 Subject: [PATCH 0859/1030] Compilation fix --- .../raw_keyboard_input/src/Keyboard.cpp | 2 ++ .../raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.cpp b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.cpp index 20298f3955..122a3c1fa3 100644 --- a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.cpp +++ b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.cpp @@ -2,6 +2,8 @@ std::set> Keyboard::thisses; +std::recursive_mutex Keyboard::instanceMutex; + Keyboard::Keyboard(juce::Component* initialParent) : parent(initialParent) { instanceMutex.lock(); diff --git a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h index af0e0380de..712c30ef38 100644 --- a/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h +++ b/Libraries/raw-keyboard-input-module/raw_keyboard_input/src/Keyboard.h @@ -37,7 +37,7 @@ class Keyboard : public juce::Timer { private: std::recursive_mutex pressedKeysMutex; - std::recursive_mutex instanceMutex; + static std::recursive_mutex instanceMutex; juce::Component* parent; juce::Component* auxParent = nullptr; std::set pressedKeys; From 0e7e5b2fa17c8509e62c7912a7448f251b850548 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sun, 2 Jun 2024 20:09:21 +0200 Subject: [PATCH 0860/1030] Make zoom button inactive in welcome screen --- Source/PluginEditor.cpp | 2 ++ Source/Statusbar.cpp | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 305d4303c5..909c076c51 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1022,6 +1022,7 @@ void PluginEditor::handleAsyncUpdate() statusbar->centreButton.setEnabled(true); statusbar->zoomComboButton.setEnabled(true); + reinterpret_cast(statusbar->zoomLabel.get())->setEnabled(true); addObjectMenuButton.setEnabled(true); } else { @@ -1034,6 +1035,7 @@ void PluginEditor::handleAsyncUpdate() statusbar->centreButton.setEnabled(false); statusbar->zoomComboButton.setEnabled(false); + reinterpret_cast(statusbar->zoomLabel.get())->setEnabled(false); undoButton.setEnabled(false); redoButton.setEnabled(false); diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 5e16559561..2aa9835403 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -660,12 +660,25 @@ class ZoomLabel : public Component { // We can use a tabular numbers font here, but I'm not sure it really looks better that way //g.setFont(Fonts::getTabularNumbersFont().withHeight(14)); - g.setColour(findColour(PlugDataColour::toolbarTextColourId).contrasting(isMouseOver() ? 0.35f : 0.0f)); + if(isEnabled()) { + g.setColour(findColour(PlugDataColour::toolbarTextColourId).contrasting(isMouseOver() ? 0.35f : 0.0f)); + } + else + { + g.setColour(findColour(PlugDataColour::toolbarTextColourId).withAlpha(0.65f)); + } g.drawText(String(int(statusbar->currentZoomLevel)) + "%", 0, 0, 44, getHeight(), Justification::centredLeft); } + void enablementChanged() override + { + repaint(); + } + void mouseDown(const MouseEvent& e) override { + if(!isEnabled()) return; + auto* editor = findParentComponentOfClass(); if (auto* cnv = editor->getCurrentCanvas()) { cnv->zoomScale.setValue(1.0f); From 9c7e46c7fb980bb7a4fdd887e4cae552a6571ea4 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 3 Jun 2024 03:39:24 +0200 Subject: [PATCH 0861/1030] Updated about panel --- Resources/Fonts/IconFont.ttf | Bin 60892 -> 60696 bytes Source/Components/Buttons.h | 94 ++++---- Source/Components/SuggestionComponent.h | 2 +- Source/Constants.h | 2 +- Source/Dialogs/AboutPanel.h | 295 +++++++++++++++++++----- Source/Dialogs/Dialogs.cpp | 4 +- Source/Heavy/Toolchain.h | 49 +++- Source/Tabbar/WelcomePanel.h | 2 +- 8 files changed, 341 insertions(+), 107 deletions(-) diff --git a/Resources/Fonts/IconFont.ttf b/Resources/Fonts/IconFont.ttf index 12c8b97957da0ed6297acdaa3b22414459dfa795..2b3b7fde89be621f8a9d3adacc1cf8cda9a7a871 100644 GIT binary patch delta 739 zcmca}n|a19W)%iT1_lORh6V;^h5$FW5Z^`eOEVZ4-uwZI%J>KC8)dF2iJYinG;1`EXob{k{n37Al9?}5IFeun-O{VxW43#+FILB_jjW&8oUjeBePOR|G4{PS+dvY5=^yW$Ozq#s}7)t)#V((%7#GuUJ0E|K$lF=Is|GgCnm zGb1q|k4;%oNe#pUii!bwW)K!g1|-hNrVOM^%ni)!85NZn&)9~A*|KEVhFJw#o$C8N zqwmzIKE@7BOH0jfmYPg;8dgB@e_FDdnpSdeO3Tdu1UkyPgBlHQ&aYJS?OkWol6Iqfe8fwB|q$n delta 939 zcmYjPTTD}T82|rI5C5JW3e?lnp5E_0r_f5FJ+$4pH>@$W+`C#^EKrc!N+GjsOdSx1 z#t^e$XNWP259))7dvMg4J-B4s!@?ZdbcLcJ{j58WGcKRfd;0eIRXB+TrsBdkz^fmhT4ZsjEtQjfenn`LJF)f=3bK1OSX|SBL6s%XQ z|JdTTOSXrslbvHPaT4yH{kVO-GF!P-#a7*(cO;!*=jQ$t=1QT=|Ca4 z6kVDR<+IuH*;hpJd_RwN1ayW8$H6DsL&c=UC zV2MEDRN`shsUr(XZE~sK*q{vm=(_H#wocJ zh!NWed9DfPc+ShQr~WfTlzz5iG9dzePNn^6d^D6j$3zZCBmbjrH=zR_kE-eudb%W&Ml52&MzYG7!l94|*=#z~e*8bb$Y6{b@ciuX6*(c)jYOoWr94#^~PhLBM* z6QPo-Jjg&8n$707FcWSmnSb)2xUx|!7E>KaL;aSjK}vtm>DY-_a)#hEN*&ZnOfGUJ z89Na@9p$ySEl83MhsiF+-k?Y%CVd(OS6fknj?`h426e#85Zizg4_@U63BoZ{NhnI? j9;7Z)c?g9}Ws;Cgsg%T&7>VfJr-%8V onClick = []() {}; - - WelcomePanelButton(String icon, String mainText, String subText) - : iconText(std::move(icon)) - , topText(std::move(mainText)) - , bottomText(std::move(subText)) - { - setInterceptsMouseClicks(true, false); - setAlwaysOnTop(true); - } - - void paint(Graphics& g) override - { - auto colour = findColour(PlugDataColour::panelTextColourId); - if (isMouseOver()) { - g.setColour(findColour(PlugDataColour::panelActiveBackgroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, Rectangle(1, 1, getWidth() - 2, getHeight() - 2), Corners::largeCornerRadius); - } - - Fonts::drawIcon(g, iconText, 20, 5, 40, colour, 24, false); - Fonts::drawText(g, topText, 60, 7, getWidth() - 60, 20, colour, 16); - Fonts::drawStyledText(g, bottomText, 60, 25, getWidth() - 60, 16, colour, Thin, 14); - } - - void mouseUp(MouseEvent const& e) override - { - onClick(); - } - - void mouseEnter(MouseEvent const& e) override - { - repaint(); - } - - void mouseExit(MouseEvent const& e) override - { - repaint(); - } -}; - class ReorderButton : public SmallIconButton { public: ReorderButton() diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index e4b30f5c01..ed999e70f9 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -145,7 +145,7 @@ class AutoCompleteComponent auto editorTextWidth = editor->getFont().getStringWidthFloat(editorText); auto completionBounds = getLocalBounds().toFloat().withTrimmedLeft(editorTextWidth + 7.5f); - auto colour = findColour(PlugDataColour::canvasTextColourId).withAlpha(0.65f); + auto colour = findColour(PlugDataColour::canvasTextColourId).withAlpha(0.5f); Fonts::drawText(g, suggestion, completionBounds.translated(-1.25f, -1.25f), colour); } }; diff --git a/Source/Constants.h b/Source/Constants.h index 508a40cbdc..03abf155c7 100644 --- a/Source/Constants.h +++ b/Source/Constants.h @@ -53,7 +53,7 @@ struct Icons { inline static String const Documentation = "N"; inline static String const AddCircled = "O"; inline static String const Console = "P"; - inline static String const GitHub = "Q"; + inline static String const OpenLink = "Q"; inline static String const Wrench = "R"; inline static String const Back = "S"; inline static String const Forward = "T"; diff --git a/Source/Dialogs/AboutPanel.h b/Source/Dialogs/AboutPanel.h index 4e136c3626..20705c0774 100644 --- a/Source/Dialogs/AboutPanel.h +++ b/Source/Dialogs/AboutPanel.h @@ -9,49 +9,195 @@ EXTERN char* pd_version; } class AboutPanel : public Component { + WidePanelButton viewWebsite = WidePanelButton(Icons::OpenLink); + WidePanelButton viewOnGithub = WidePanelButton(Icons::OpenLink); + WidePanelButton reportIssue = WidePanelButton(Icons::OpenLink); + WidePanelButton sponsor = WidePanelButton(Icons::OpenLink); + + WidePanelButton showCredits = WidePanelButton(Icons::Forward, 15); + WidePanelButton showLicense = WidePanelButton(Icons::Forward, 15); + + class CreditsViewport : public BouncingViewport + { + void paint(Graphics& g) override + { + g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); + g.fillRoundedRectangle(getLocalBounds().toFloat(), Corners::windowCornerRadius); + } + + void paintOverChildren(Graphics& g) override + { + g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); + g.fillRoundedRectangle(getLocalBounds().removeFromTop(16).withTrimmedRight(16).toFloat(), Corners::windowCornerRadius); + + // Draw fade for credits viewport + g.setGradientFill(ColourGradient::vertical(findColour(PlugDataColour::panelBackgroundColourId), 36, findColour(PlugDataColour::panelBackgroundColourId).withAlpha(0.0f), 48)); + g.fillRect(0, 16, getWidth() - 16, 48); + } + }; + class CreditsPanel : public Component + { + const std::vector> contributors = { + {"Timothy Schoen", "Development, UI/UX design"}, + {"Alex Mitchell", "Development, UI/UX design"}, + {"Joshua A.C. Newman", "Community management, logo and identity design"}, + {"Bas de Bruin", "Logo design"}, + {"dreamer", "Hvcc development"}, + {"tomara-x", "Documentation, testing"} + }; + + const StringArray sponsors = { + "Deskew Technologies", + "Nasko", + "PowerUser64", + "DSBHproject", + "CFDAF", + "cotik1", + "alfonso73", + "spamfunnel", + "ooroor", + "bla9kdog", + "KPY7030P", + "chee", + "duddex", + "rubenlorenzo", + "mungbean", + "jamescorrea", + "Soundworlds-JO", + "vasilymilovidov", + "polarity", + "ludnny" + }; + + public: + CreditsPanel() = default; - String creditsText = " Thanks to Alex Mitchell and tomara-x for helping out with development, documentation and testing.\n\n - ELSE v1.0-rc11 by Alexandre Porres\n - cyclone v0.8-1 by Krzysztof Czaja, Hans-Christoph Steiner, Fred Jan Kraan, Alexandre Porres, Derek Kwan, Matt Barber et al.\n - Based on Camomile by Pierre Guillot\n - Inter font by Rasmus Andersson\n - Made with JUCE\n\n\nSpecial thanks to: Deskew Technologies, PowerUser64, DSBHproject, CFDAF, cotik1 , alfonso73, spamfunnel, ooroor, bla9kdog, KPY7030P, duddex, rubenlorenzo, mungbean, jamescorrea, Soundworlds-JO, vasilymilovidov, polarity, chee, Joshua A.C. Newman and ludnny for supporting this project\n\n\nThis program is published under the terms of the GPL3 license"; + void paint(Graphics& g) override + { + auto bounds = getLocalBounds().withTrimmedTop(46).reduced(16, 4); + + Fonts::drawStyledText(g, "Contributors", bounds.getX(), bounds.getY() - 8, bounds.getWidth(), 15.0f, findColour(PlugDataColour::panelTextColourId), Semibold, 15.0f); + + bounds.removeFromTop(16); + + Path firstShadowPath; + firstShadowPath.addRoundedRectangle(Rectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), contributors.size() * 48).reduced(4), Corners::largeCornerRadius); + StackShadow::renderDropShadow(g, firstShadowPath, Colour(0, 0, 0).withAlpha(0.32f), 8); + + for(int i = 0; i < contributors.size(); i++) + { + auto rowBounds = bounds.removeFromTop(48); + auto first = i == 0; + auto last = i == (contributors.size() - 1); + auto& [name, role] = contributors[i]; + Path outline; + outline.addRoundedRectangle(rowBounds.getX(), rowBounds.getY(), rowBounds.getWidth(), rowBounds.getHeight(), Corners::largeCornerRadius, Corners::largeCornerRadius, first, first, last, last); + + g.setColour(findColour(PlugDataColour::panelForegroundColourId)); + g.fillPath(outline); - TextEditor credits; - TextButton viewOnGithub; - TextButton reportIssue; - TextButton sponsor; + g.setColour(findColour(PlugDataColour::outlineColourId)); + g.strokePath(outline, PathStrokeType(1)); + + Fonts::drawText(g, name, rowBounds.reduced(12, 2).translated(0, -8), findColour(PlugDataColour::panelTextColourId), 15); + Fonts::drawText(g, role, rowBounds.reduced(12, 2).translated(0, 8), findColour(PlugDataColour::panelTextColourId).withAlpha(0.5f), 15); + } + + bounds.removeFromTop(24); + + Fonts::drawStyledText(g, "Sponsors", bounds.getX(), bounds.getY() - 8, bounds.getWidth(), 15.0f, findColour(PlugDataColour::panelTextColourId), Semibold, 15.0f); + + bounds.removeFromTop(16); + + Path secondShadowPath; + secondShadowPath.addRoundedRectangle(Rectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), sponsors.size() * 32).reduced(4), Corners::largeCornerRadius); + StackShadow::renderDropShadow(g, secondShadowPath, Colour(0, 0, 0).withAlpha(0.32f), 8); + for(int i = 0; i < sponsors.size(); i++) + { + auto rowBounds = bounds.removeFromTop(36); + auto first = i == 0; + auto last = i == (sponsors.size() - 1); + auto& name = sponsors[i]; + Path outline; + outline.addRoundedRectangle(rowBounds.getX(), rowBounds.getY(), rowBounds.getWidth(), rowBounds.getHeight(), Corners::largeCornerRadius, Corners::largeCornerRadius, first, first, last, last); + + g.setColour(findColour(PlugDataColour::panelForegroundColourId)); + g.fillPath(outline); + + g.setColour(findColour(PlugDataColour::outlineColourId)); + g.strokePath(outline, PathStrokeType(1)); + + Fonts::drawText(g, name, rowBounds.reduced(12, 2), findColour(PlugDataColour::panelTextColourId), 15); + + jassert(!bounds.isEmpty()); + } + } + }; + + class LicensePanel : public Component + { + TextEditor license; + + String licenseText = "Copyright Timothy Schoen\n\n" + "This app is licensed under the GNU General Public License version 3 (GPL-3.0). You are free to use, modify, and distribute the software, provided that any derivative works also carry the same license and the source code remains accessible.\n" + "This application comes with absolutely no warranty."; + + public: + LicensePanel() + { + license.setColour(TextEditor::outlineColourId, Colours::transparentBlack); + license.setColour(TextEditor::backgroundColourId, Colours::transparentBlack); + license.setReadOnly(true); + license.setMultiLine(true); + license.setText(licenseText); + license.setFont(Font(15)); + license.setLineSpacing(1.1f); + addAndMakeVisible(license); + } + + void resized() override + { + license.setBounds(getLocalBounds().withTrimmedTop(32).reduced(16, 8)); + } + + void paint(Graphics& g) override + { + g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); + g.fillRoundedRectangle(getLocalBounds().toFloat(), Corners::windowCornerRadius); + } + }; + + CreditsViewport creditsViewport; + CreditsPanel creditsComponent; + LicensePanel licenseComponent; + + MainToolbarButton backButton; Image logo = ImageFileFormat::loadFrom(BinaryData::plugdata_large_logo_png, BinaryData::plugdata_large_logo_pngSize); public: AboutPanel() { - credits.setColour(TextEditor::outlineColourId, Colours::transparentBlack); - credits.setColour(TextEditor::backgroundColourId, Colours::transparentBlack); - credits.setReadOnly(true); - credits.setMultiLine(true); - credits.setText(creditsText); - credits.setFont(Font(15)); - credits.setLineSpacing(1.1f); - addAndMakeVisible(credits); - - auto backgroundColour = findColour(PlugDataColour::dialogBackgroundColourId); - viewOnGithub.setColour(TextButton::buttonColourId, backgroundColour.contrasting(0.05f)); - viewOnGithub.setColour(TextButton::buttonOnColourId, backgroundColour.contrasting(0.1f)); - viewOnGithub.setColour(ComboBox::outlineColourId, Colours::transparentBlack); - - reportIssue.setColour(TextButton::buttonColourId, backgroundColour.contrasting(0.05f)); - reportIssue.setColour(TextButton::buttonOnColourId, backgroundColour.contrasting(0.1f)); - reportIssue.setColour(ComboBox::outlineColourId, Colours::transparentBlack); - - sponsor.setColour(TextButton::buttonColourId, backgroundColour.contrasting(0.05f)); - sponsor.setColour(TextButton::buttonOnColourId, backgroundColour.contrasting(0.1f)); - sponsor.setColour(ComboBox::outlineColourId, Colours::transparentBlack); - + viewWebsite.setButtonText("Website"); viewOnGithub.setButtonText("View on Github"); reportIssue.setButtonText("Report issue"); - sponsor.setButtonText("Become a sponsor"); + sponsor.setButtonText("Sponsor"); + showCredits.setButtonText("Credits"); + showLicense.setButtonText("License"); + + addAndMakeVisible(viewWebsite); addAndMakeVisible(viewOnGithub); addAndMakeVisible(reportIssue); addAndMakeVisible(sponsor); + + viewWebsite.setConnectedEdges(Button::ConnectedOnBottom); + viewOnGithub.setConnectedEdges(Button::ConnectedOnTop); + viewWebsite.onClick = []() { + URL("https://plugdata.org").launchInDefaultBrowser(); + }; + viewOnGithub.onClick = []() { URL("https://github.com/plugdata-team/plugdata").launchInDefaultBrowser(); }; @@ -63,46 +209,89 @@ class AboutPanel : public Component { sponsor.onClick = []() { URL("https://github.com/sponsors/timothyschoen").launchInDefaultBrowser(); }; + + backButton.setButtonText(Icons::Back); + backButton.onClick = [this](){ + creditsViewport.setVisible(false); + licenseComponent.setVisible(false); + backButton.setVisible(false); + }; + backButton.setAlwaysOnTop(true); + addChildComponent(backButton); + addAndMakeVisible(showCredits); + addAndMakeVisible(showLicense); + + creditsViewport.setScrollBarsShown (true, false); + creditsViewport.setViewedComponent(&creditsComponent, false); + creditsComponent.setVisible(true); + addChildComponent(creditsViewport); + addChildComponent(licenseComponent); + + showCredits.setConnectedEdges(Button::ConnectedOnBottom); + showLicense.setConnectedEdges(Button::ConnectedOnTop); + + showCredits.onClick = [this]() { + creditsViewport.setVisible(true); + backButton.setVisible(true); + }; + + showLicense.onClick = [this]() { + licenseComponent.setVisible(true); + backButton.setVisible(true); + }; } void paint(Graphics& g) override { - auto logoStart = 185.0f; - auto logoSize = 75.0f; - auto textStart = logoStart + logoSize + 15.0f; - - Fonts::drawStyledText(g, "plugdata " + String(ProjectInfo::versionString), textStart, 20, getWidth() - textStart, 30, findColour(PlugDataColour::panelTextColourId), Bold, 30, Justification::left); + g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); + g.fillRoundedRectangle(getLocalBounds().toFloat(), Corners::windowCornerRadius); + + Fonts::drawStyledText(g, "plugdata", 0, 100, getWidth(), 30, findColour(PlugDataColour::panelTextColourId), Bold, 30, Justification::centred); g.setFont(Font(16)); - g.drawFittedText("By Timothy Schoen", textStart, 50, getWidth() - textStart, 30, Justification::left, 1); - g.drawFittedText("Based on " + String(pd_version).upToFirstOccurrenceOf("(", false, false) + "by Miller Puckette et al.", textStart, 70, getWidth() - textStart, 30, Justification::left, 1); + g.drawFittedText("By Timothy Schoen", 0, 132, getWidth(), 30, Justification::centred, 1); + + g.setColour(findColour(PlugDataColour::dataColourId).withAlpha(0.2f)); + auto versionBounds = getLocalBounds().withTrimmedTop(162).removeFromTop(32).withSizeKeepingCentre(64, 24); + g.fillRoundedRectangle(versionBounds.toFloat(), 12.0f); + Fonts::drawStyledText(g, "v" + String(ProjectInfo::versionString), versionBounds.getX(), versionBounds.getY(), versionBounds.getWidth(), versionBounds.getHeight(), findColour(PlugDataColour::panelTextColourId), Semibold, 16, Justification::centred); - Rectangle logoBounds = { logoStart, 25.0f, logoSize, logoSize }; + Rectangle logoBounds = getLocalBounds().removeFromTop(120.0f).withSizeKeepingCentre(84.0f, 84.0f).toFloat(); g.setImageResamplingQuality(Graphics::highResamplingQuality); g.drawImage(logo, logoBounds); g.setImageResamplingQuality(Graphics::mediumResamplingQuality); + + + for(auto& shadow : std::vector>{viewWebsite.getBounds().getUnion(viewOnGithub.getBounds()), reportIssue.getBounds(), sponsor.getBounds(), showCredits.getBounds().getUnion(showLicense.getBounds())}) + { + Path shadowPath; + shadowPath.addRoundedRectangle(shadow.reduced(4), Corners::largeCornerRadius); + StackShadow::renderDropShadow(g, shadowPath, Colour(0, 0, 0).withAlpha(0.32f), 8); + } - auto creditsBounds = credits.getBounds().expanded(5); - g.setColour(findColour(PlugDataColour::panelForegroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, creditsBounds.toFloat(), Corners::largeCornerRadius); - - g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); - PlugDataLook::drawSmoothedRectangle(g, PathStrokeType(1.0f), creditsBounds.toFloat(), Corners::largeCornerRadius); + backButton.setBounds(2, 0, 40, 40); } - + void resized() override { - credits.setBounds(getLocalBounds().withTrimmedTop(100).withTrimmedBottom(25).reduced(30)); - auto buttonBounds = getLocalBounds().removeFromBottom(54).reduced(25, 15); - int buttonWidth = (buttonBounds.getWidth() / 3) - 5; - - viewOnGithub.setBounds(buttonBounds.removeFromLeft(buttonWidth)); - buttonBounds.removeFromLeft(5); - - reportIssue.setBounds(buttonBounds.removeFromLeft(buttonWidth)); - buttonBounds.removeFromLeft(5); + creditsViewport.setBounds(getLocalBounds()); + creditsComponent.setSize(getWidth(), 1132); + licenseComponent.setBounds(getLocalBounds()); + + auto bounds = getLocalBounds().withTrimmedTop(190).reduced(16, 10); - sponsor.setBounds(buttonBounds); + viewWebsite.setBounds(bounds.removeFromTop(44).reduced(4)); + viewOnGithub.setBounds(bounds.removeFromTop(44).reduced(4).translated(0, -9)); + + bounds.removeFromTop(3); + reportIssue.setBounds(bounds.removeFromTop(44).reduced(4)); + + bounds.removeFromTop(6); + sponsor.setBounds(bounds.removeFromTop(44).reduced(4)); + + bounds.removeFromTop(6); + showCredits.setBounds(bounds.removeFromTop(44).reduced(4)); + showLicense.setBounds(bounds.removeFromTop(44).reduced(4).translated(0, -9)); } }; diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index 115a583a97..379be68fe0 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -186,7 +186,7 @@ void Dialogs::showMainMenu(PluginEditor* editor, Component* centre) break; } case 6: { - auto* dialog = new Dialog(&editor->openedDialog, editor, 675, 500, true); + auto* dialog = new Dialog(&editor->openedDialog, editor, 360, 490, true); auto* aboutPanel = new AboutPanel(); dialog->setViewedComponent(aboutPanel); editor->openedDialog.reset(dialog); @@ -252,7 +252,7 @@ void Dialogs::showMainMenu(PluginEditor* editor, Component* centre) break; } case MainMenu::MenuItem::About: { - auto* dialog = new Dialog(&editor->openedDialog, editor, 675, 500, true); + auto* dialog = new Dialog(&editor->openedDialog, editor, 360, 490, true); auto* aboutPanel = new AboutPanel(); dialog->setViewedComponent(aboutPanel); editor->openedDialog.reset(dialog); diff --git a/Source/Heavy/Toolchain.h b/Source/Heavy/Toolchain.h index 94b7feed21..d0dacf6b31 100644 --- a/Source/Heavy/Toolchain.h +++ b/Source/Heavy/Toolchain.h @@ -339,8 +339,55 @@ class ToolchainInstaller : public Component #else String downloadSize = "1.45 GB"; #endif + + class ToolchainInstallerButton : public Component { + + public: + String iconText; + String topText; + String bottomText; + + std::function onClick = []() {}; + + ToolchainInstallerButton(String icon, String mainText, String subText) + : iconText(std::move(icon)) + , topText(std::move(mainText)) + , bottomText(std::move(subText)) + { + setInterceptsMouseClicks(true, false); + setAlwaysOnTop(true); + } + + void paint(Graphics& g) override + { + auto colour = findColour(PlugDataColour::panelTextColourId); + if (isMouseOver()) { + g.setColour(findColour(PlugDataColour::panelActiveBackgroundColourId)); + PlugDataLook::fillSmoothedRectangle(g, Rectangle(1, 1, getWidth() - 2, getHeight() - 2), Corners::largeCornerRadius); + } + + Fonts::drawIcon(g, iconText, 20, 5, 40, colour, 24, false); + Fonts::drawText(g, topText, 60, 7, getWidth() - 60, 20, colour, 16); + Fonts::drawStyledText(g, bottomText, 60, 25, getWidth() - 60, 16, colour, Thin, 14); + } + + void mouseUp(MouseEvent const& e) override + { + onClick(); + } + + void mouseEnter(MouseEvent const& e) override + { + repaint(); + } + + void mouseExit(MouseEvent const& e) override + { + repaint(); + } + }; - WelcomePanelButton installButton = WelcomePanelButton(Icons::SaveAs, "Download Toolchain", "Download compilation utilities (" + downloadSize + ")"); + ToolchainInstallerButton installButton = ToolchainInstallerButton(Icons::SaveAs, "Download Toolchain", "Download compilation utilities (" + downloadSize + ")"); std::function toolchainInstalledCallback; diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Tabbar/WelcomePanel.h index 36df4dad60..919dab55c6 100644 --- a/Source/Tabbar/WelcomePanel.h +++ b/Source/Tabbar/WelcomePanel.h @@ -75,7 +75,7 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater subtitleImage = NVGImage(nvg, textWidth * 2.0f, 16 * 2.0f, [this, textColour, textWidth](Graphics& g){ g.addTransform(AffineTransform::scale(2.0f, 2.0f)); - g.setColour(textColour); + g.setColour(textColour.withAlpha(0.75f)); g.setFont(Fonts::getDefaultFont().withHeight(13.5f)); g.drawText(tileSubtitle, Rectangle(0, 0, textWidth, 16), Justification::centredLeft, true); }); From 68a2f2ac1237ffe85b1b892744cc627129e298a8 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 3 Jun 2024 03:42:07 +0200 Subject: [PATCH 0862/1030] Small fix --- Source/Dialogs/AboutPanel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Dialogs/AboutPanel.h b/Source/Dialogs/AboutPanel.h index 20705c0774..0c933dfcf4 100644 --- a/Source/Dialogs/AboutPanel.h +++ b/Source/Dialogs/AboutPanel.h @@ -28,7 +28,7 @@ class AboutPanel : public Component { void paintOverChildren(Graphics& g) override { g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); - g.fillRoundedRectangle(getLocalBounds().removeFromTop(16).withTrimmedRight(16).toFloat(), Corners::windowCornerRadius); + g.fillRoundedRectangle(getLocalBounds().removeFromTop(16).withTrimmedRight(8).toFloat(), Corners::windowCornerRadius); // Draw fade for credits viewport g.setGradientFill(ColourGradient::vertical(findColour(PlugDataColour::panelBackgroundColourId), 36, findColour(PlugDataColour::panelBackgroundColourId).withAlpha(0.0f), 48)); From 500e7534693687350467dd1bb67c6336f0d2f032 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Mon, 3 Jun 2024 14:31:03 +0930 Subject: [PATCH 0863/1030] use circular hittest when knob is borderless * Fix object hittest when iolets are not selectable --- Source/Object.cpp | 21 ++++++++++++++++----- Source/Object.h | 2 ++ Source/Objects/KnobObject.h | 30 +++++++++++++++--------------- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/Source/Object.cpp b/Source/Object.cpp index d1e068ee8d..f60cd97bd8 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -219,6 +219,22 @@ bool Object::hitTest(int x, int y) if (cnv->panningModifierDown()) return false; + // Knob object is able to reduce its bounds when transparent. + // Use the objects hit-test if it is populated (currently only for knob) + if (transparentHitTest && (presentationMode.getValue() || locked.getValue() || commandLocked.getValue())) { + return transparentHitTest(x, y); + } + + // Mouse over object + if (getLocalBounds().reduced(margin).contains(x, y)) { + return true; + } + + // If the hit-test get's to here, and any of these are still true + // return! Otherwise it will test non-existent iolets and return true! + if ((presentationMode.getValue() || locked.getValue() || commandLocked.getValue())) + return false; + // Mouse over iolets for (auto* iolet : iolets) { if (iolet->getBounds().contains(x, y)) @@ -239,11 +255,6 @@ bool Object::hitTest(int x, int y) return false; } - // Mouse over object - if (getLocalBounds().reduced(margin).contains(x, y)) { - return true; - } - return false; } diff --git a/Source/Object.h b/Source/Object.h index f1f098016b..8ec684a0fd 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -132,6 +132,8 @@ class Object : public Component bool isSelected() const; + std::function transparentHitTest = nullptr; + private: void initialise(); diff --git a/Source/Objects/KnobObject.h b/Source/Objects/KnobObject.h index ebc837edb9..8bea56a63d 100644 --- a/Source/Objects/KnobObject.h +++ b/Source/Objects/KnobObject.h @@ -58,21 +58,6 @@ class Knob : public Slider, public NVGComponent { } } - bool hitTest(int x, int y) override - { - if (hasOutline) return true; - - // If knob is circular limit hit test to circle, and expand more if there are ticks around the knob - auto centre = getLocalBounds().toFloat().getCentre(); - auto knobRadius = getWidth() * 0.33f; - auto knobRadiusWithTicks = knobRadius + (getWidth() * 0.06f); - if (centre.getDistanceFrom(Point(x, y)) < (ticks ? knobRadiusWithTicks : knobRadius)) { - return true; - } - - return false; - } - void mouseEnter(const MouseEvent& e) override { getParentComponent()->getProperties().set("hover", true); @@ -218,6 +203,21 @@ class KnobObject : public ObjectBase { stopEdition(); }; + object->transparentHitTest = [this, object](int x, int y) -> bool { + if (outline.getValue()) return true; + + // If knob is circular limit hit test to circle, and expand more if there are ticks around the knob + auto hitPoint = getLocalPoint(object, Point(x, y)); + auto centre = getLocalBounds().toFloat().getCentre(); + auto knobRadius = getWidth() * 0.33f; + auto knobRadiusWithTicks = knobRadius + (getWidth() * 0.06f); + if (centre.getDistanceFrom(hitPoint) < (ticks.getValue() ? knobRadiusWithTicks : knobRadius)) { + return true; + } + + return false; + }; + onConstrainerCreate = [this]() { constrainer->setFixedAspectRatio(1.0f); constrainer->setMinimumSize(this->object->minimumSize, this->object->minimumSize); From f1091133612b7086e3f6707be1efcde7789b78eb Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 3 Jun 2024 14:06:57 +0200 Subject: [PATCH 0864/1030] Fixed welcome panel spacing --- Source/Tabbar/WelcomePanel.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Tabbar/WelcomePanel.h index 919dab55c6..41f9c928ba 100644 --- a/Source/Tabbar/WelcomePanel.h +++ b/Source/Tabbar/WelcomePanel.h @@ -40,7 +40,7 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater auto bounds = getLocalBounds().reduced(12); Path tilePath; - tilePath.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), Corners::largeCornerRadius); + tilePath.addRoundedRectangle(bounds.getX() + 1, bounds.getY() + 1, bounds.getWidth() - 2, bounds.getHeight() - 2, Corners::largeCornerRadius); StackShadow::renderDropShadow(g, tilePath, Colour(0, 0, 0).withAlpha(0.1f), 7, {0, 2}); g.setColour(findColour(PlugDataColour::canvasBackgroundColourId)); @@ -182,7 +182,7 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater int numRows = (tiles.size() + numColumns - 1) / numColumns; int totalHeight = numRows * 160; - auto scrollable = Rectangle(24, 6, totalWidth + 24, totalHeight); + auto scrollable = Rectangle(24, 6, totalWidth + 24, totalHeight + 24); recentlyOpenedComponent.setBounds(scrollable); // Start positioning the tiles @@ -308,15 +308,15 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater nvgFill(nvg); nvgBeginPath(nvg); - nvgFillColor(nvg, convertColour(findColour(PlugDataColour::panelTextColourId))); + nvgFillColor(nvg, findNVGColour(PlugDataColour::panelTextColourId)); nvgTextAlign(nvg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); nvgFontSize(nvg, 30); nvgFontFace(nvg, "Inter-Bold"); - nvgText(nvg, 32, 32, "Welcome to plugdata", nullptr); + nvgText(nvg, 35, 38, "Welcome to plugdata", nullptr); nvgBeginPath(nvg); nvgFontSize(nvg, 24); - nvgText(nvg, 32, 240, "Recently Opened", nullptr); + nvgText(nvg, 35, 244, "Recently Opened", nullptr); } void lookAndFeelChanged() override From 1afd7b63df521ab115287841065e603a8ff73556 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 3 Jun 2024 14:27:50 +0200 Subject: [PATCH 0865/1030] Differentiate MC cable from regular cable look --- Source/Connection.cpp | 2 +- Source/Tabbar/WelcomePanel.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Connection.cpp b/Source/Connection.cpp index e803efea35..15c936782d 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -221,7 +221,7 @@ void Connection::render(NVGcontext* nvg) nvgStrokeColor(nvg, dashColor); nvgLineStyle(nvg, NVG_LINE_DASHED); - nvgDashLength(nvg, 5.0f); + nvgDashLength(nvg, numSignalChannels <= 1 ? 5.0f : 3.5f); nvgStrokeWidth(nvg, useThinConnection ? 1.5f : 2.0f); if(!cachedIsValid) nvgDeletePath(nvg, std::numeric_limits::max() - cacheId); diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Tabbar/WelcomePanel.h index 41f9c928ba..ba3e1ecda4 100644 --- a/Source/Tabbar/WelcomePanel.h +++ b/Source/Tabbar/WelcomePanel.h @@ -42,7 +42,7 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater Path tilePath; tilePath.addRoundedRectangle(bounds.getX() + 1, bounds.getY() + 1, bounds.getWidth() - 2, bounds.getHeight() - 2, Corners::largeCornerRadius); - StackShadow::renderDropShadow(g, tilePath, Colour(0, 0, 0).withAlpha(0.1f), 7, {0, 2}); + StackShadow::renderDropShadow(g, tilePath, Colour(0, 0, 0).withAlpha(0.08f), 6, {0, 1}); g.setColour(findColour(PlugDataColour::canvasBackgroundColourId)); g.fillPath(tilePath); From ee00e774411239d2c1d4eb9d62985086281cdc65 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 3 Jun 2024 15:12:03 +0200 Subject: [PATCH 0866/1030] Reduce presentation mode shadow --- Source/Canvas.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 41d49bf61a..844264eab7 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -445,14 +445,14 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) nvgPathWinding(nvg, NVG_HOLE); nvgRoundedRect(nvg, pos.getX(), pos.getY(), borderWidth, borderHeight, Corners::windowCornerRadius); - const int shadowSize = 40; + const int shadowSize = 16; auto borderArea = Rectangle(0, 0, borderWidth, borderHeight).expanded(shadowSize); if (presentationShadowImage.needsUpdate(borderArea.getWidth(), borderArea.getHeight())) { presentationShadowImage = NVGImage(nvg, borderArea.getWidth(), borderArea.getHeight(), [borderArea](Graphics& g){ auto shadowPath = Path(); shadowPath.addRoundedRectangle(borderArea.reduced(shadowSize).withPosition(shadowSize, shadowSize), Corners::windowCornerRadius); - StackShadow::renderDropShadow(g, shadowPath, Colours::black, shadowSize); + StackShadow::renderDropShadow(g, shadowPath, Colours::black.withAlpha(0.35f), shadowSize, Point(0, 2)); }); } auto shadowImage = nvgImagePattern(nvg, pos.getX() - shadowSize, pos.getY() - shadowSize, borderArea.getWidth(), borderArea.getHeight(), 0, presentationShadowImage.getImageId(), 0.33f); From ff1a9b11de4469d5e151c0739ff3a53b70c8e806 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Mon, 3 Jun 2024 15:36:55 +0200 Subject: [PATCH 0867/1030] Fix text editor bug on Linux --- Source/Dialogs/TextEditorDialog.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Dialogs/TextEditorDialog.h b/Source/Dialogs/TextEditorDialog.h index f8707b551c..60faa829e9 100644 --- a/Source/Dialogs/TextEditorDialog.h +++ b/Source/Dialogs/TextEditorDialog.h @@ -2089,7 +2089,7 @@ struct TextEditorDialog : public Component { }); }; - addToDesktop(ComponentPeer::windowIsTemporary); + addToDesktop(0); setVisible(true); // Position in centre of screen From 0efb1cf529bc74873229813547ef153fd104efc0 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Tue, 4 Jun 2024 15:03:02 +0930 Subject: [PATCH 0868/1030] Presentation mode zoom to fit all uses patch dimensions for ROI --- Source/Canvas.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 844264eab7..03f6171104 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -703,9 +703,12 @@ void Canvas::zoomToFitAll() auto scale = getValue(zoomScale); - auto regionOfInterest = Rectangle(); - for (auto* object : objects) { - regionOfInterest = regionOfInterest.getUnion(object->getBounds().reduced(Object::margin)); + auto regionOfInterest = Rectangle(canvasOrigin.x, canvasOrigin.y, getValue(patchWidth), getValue(patchHeight)); + + if (!presentationMode.getValue()) { + for (auto *object: objects) { + regionOfInterest = regionOfInterest.getUnion(object->getBounds().reduced(Object::margin)); + } } // Add a bit of margin to make it nice From 0255b2a231a5c81e67f451338a1c97b7f0464c6c Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 5 Jun 2024 13:30:41 +0930 Subject: [PATCH 0869/1030] no need to store ticks & outline value in knob gui --- Source/Objects/KnobObject.h | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Source/Objects/KnobObject.h b/Source/Objects/KnobObject.h index 8bea56a63d..41c106db7a 100644 --- a/Source/Objects/KnobObject.h +++ b/Source/Objects/KnobObject.h @@ -23,9 +23,6 @@ class Knob : public Slider, public NVGComponent { float arcStart = 63.5f; public: - bool hasOutline = true; - bool ticks = false; - Knob() : Slider(Slider::RotaryHorizontalVerticalDrag, Slider::NoTextBox), NVGComponent(this) { @@ -269,7 +266,7 @@ class KnobObject : public ObjectBase { if (auto knb = ptr.get()) { initialValue = knb->x_load; - ticks = knob.ticks = knb->x_ticks; + ticks = knb->x_ticks; angularRange = knb->x_range; angularOffset = knb->x_offset; discrete = knb->x_discrete; @@ -279,7 +276,7 @@ class KnobObject : public ObjectBase { primaryColour = getForegroundColour().toString(); secondaryColour = getBackgroundColour().toString(); arcColour = getArcColour().toString(); - outline = knob.hasOutline = knb->x_outline; + outline = knb->x_outline; sizeProperty = knb->x_size; arcStart = knb->x_start; } @@ -751,7 +748,7 @@ class KnobObject : public ObjectBase { } else if (value.refersToSameSourceAs(ticks)) { ticks = jmax(::getValue(ticks), 0); if (auto knb = ptr.get()) - knob.ticks = knb->x_ticks = ::getValue(ticks); + knb->x_ticks = ::getValue(ticks); updateRotaryParameters(); updateRange(); } else if (value.refersToSameSourceAs(angularRange)) { @@ -777,7 +774,7 @@ class KnobObject : public ObjectBase { updateRange(); } else if (value.refersToSameSourceAs(outline)) { if (auto knb = ptr.get()) { - knob.hasOutline = knb->x_outline = ::getValue < bool > (outline); + knb->x_outline = ::getValue < bool > (outline); } repaint(); } else if (value.refersToSameSourceAs(exponential)) { From d3d6dd7f3f63cf83aa25e175bee68737a668b4ee Mon Sep 17 00:00:00 2001 From: alcomposer Date: Wed, 5 Jun 2024 17:02:04 +0930 Subject: [PATCH 0870/1030] make presentation mode border / dropshadow independent of scale add constrasting border to mimic OS window --- Source/Canvas.cpp | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 03f6171104..51fbf1e47d 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -419,11 +419,14 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) renderAllObjects(nvg, invalidRegion); // render presentation mode as clipped 'virtual' plugin view if (::getValue(presentationMode) && !editor->pluginMode) { - auto borderWidth = getValue(patchWidth); - auto borderHeight = getValue(patchHeight); - auto pos = Point(halfSize, halfSize); + const auto borderWidth = getValue(patchWidth); + const auto borderHeight = getValue(patchHeight); + const auto pos = Point(halfSize, halfSize); + const auto scale = getValue(zoomScale); + const auto windowCorner = Corners::windowCornerRadius / scale; - auto bgColour = convertColour(findColour(PlugDataColour::presentationBackgroundColourId)); + const auto bgColour = convertColour(findColour(PlugDataColour::presentationBackgroundColourId)); + const auto windowOutlineColour = convertColour(findColour(PlugDataColour::presentationBackgroundColourId).contrasting(0.3f)); nvgSave(nvg); @@ -433,7 +436,7 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) nvgRect(nvg, 0, 0, infiniteCanvasSize, infiniteCanvasSize); nvgPathWinding(nvg, NVG_HOLE); - nvgRoundedRect(nvg, pos.getX(), pos.getY(), borderWidth, borderHeight, Corners::windowCornerRadius); + nvgRoundedRect(nvg, pos.getX(), pos.getY(), borderWidth, borderHeight, windowCorner); nvgFillColor(nvg, bgColour); nvgFill(nvg); @@ -443,21 +446,25 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) nvgRect(nvg, 0, 0, infiniteCanvasSize, infiniteCanvasSize); nvgPathWinding(nvg, NVG_HOLE); - nvgRoundedRect(nvg, pos.getX(), pos.getY(), borderWidth, borderHeight, Corners::windowCornerRadius); + nvgRoundedRect(nvg, pos.getX(), pos.getY(), borderWidth, borderHeight, windowCorner); - const int shadowSize = 16; + const int shadowSize = 24 / scale; auto borderArea = Rectangle(0, 0, borderWidth, borderHeight).expanded(shadowSize); if (presentationShadowImage.needsUpdate(borderArea.getWidth(), borderArea.getHeight())) { - presentationShadowImage = NVGImage(nvg, borderArea.getWidth(), borderArea.getHeight(), [borderArea](Graphics& g){ + presentationShadowImage = NVGImage(nvg, borderArea.getWidth(), borderArea.getHeight(), [borderArea, shadowSize, windowCorner](Graphics& g){ auto shadowPath = Path(); - shadowPath.addRoundedRectangle(borderArea.reduced(shadowSize).withPosition(shadowSize, shadowSize), Corners::windowCornerRadius); - StackShadow::renderDropShadow(g, shadowPath, Colours::black.withAlpha(0.35f), shadowSize, Point(0, 2)); + shadowPath.addRoundedRectangle(borderArea.reduced(shadowSize).withPosition(shadowSize, shadowSize), windowCorner); + StackShadow::renderDropShadow(g, shadowPath, Colours::black, shadowSize, Point(0, 2)); }); } - auto shadowImage = nvgImagePattern(nvg, pos.getX() - shadowSize, pos.getY() - shadowSize, borderArea.getWidth(), borderArea.getHeight(), 0, presentationShadowImage.getImageId(), 0.33f); + auto shadowImage = nvgImagePattern(nvg, pos.getX() - shadowSize, pos.getY() - shadowSize, borderArea.getWidth(), borderArea.getHeight(), 0, presentationShadowImage.getImageId(), 0.16f); + + nvgStrokeColor(nvg, windowOutlineColour); + nvgStrokeWidth(nvg, 0.5f / scale); nvgFillPaint(nvg, shadowImage); nvgFill(nvg); + nvgStroke(nvg); nvgRestore(nvg); } From 314380a69c40d7f963c47461774a3f1fd016b2a7 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Tue, 4 Jun 2024 22:50:43 +0930 Subject: [PATCH 0871/1030] implement border infront of objects when editing dimensions --- Source/Canvas.cpp | 113 ++++++++++++++++------------ Source/Canvas.h | 2 + Source/Components/DraggableNumber.h | 8 ++ Source/Components/PropertiesPanel.h | 5 +- Source/Objects/ObjectBase.cpp | 2 +- Source/Objects/ObjectParameters.h | 13 ++-- Source/Sidebar/Inspector.h | 12 +-- 7 files changed, 93 insertions(+), 62 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 03f6171104..8cf3488d75 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -163,8 +163,14 @@ Canvas::Canvas(PluginEditor* parent, pd::Patch::Ptr p, Component* parentGraph) parameters.addParamBool("Hide name and arguments", cGeneral, &hideNameAndArgs, { "No", "Yes" }, 0); parameters.addParamRange("X range", cGeneral, &xRange, { 0.0f, 1.0f }); parameters.addParamRange("Y range", cGeneral, &yRange, { 1.0f, 0.0f }); - parameters.addParamInt("Width", cDimensions, &patchWidth, 527); - parameters.addParamInt("Height", cDimensions, &patchHeight, 327); + + auto onInteractionFn = [this](bool state){ + dimensionsAreBeingEdited = state; + repaint(); + }; + + parameters.addParamInt("Width", cDimensions, &patchWidth, 527, onInteractionFn); + parameters.addParamInt("Height", cDimensions, &patchHeight, 327, onInteractionFn); updatePatchSnapshot(); } @@ -352,55 +358,63 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) nvgRestore(nvg); } } + auto drawBorder = [this, nvg, hasViewport, backgroundColour, zoom, dotsColour](bool bg, bool fg) { + if (hasViewport && (showOrigin || showBorder) && !::getValue(presentationMode)) { + nvgBeginPath(nvg); - if(hasViewport && (showOrigin || showBorder) && !::getValue(presentationMode)) { - nvgBeginPath(nvg); - - const auto borderWidth = getValue(patchWidth); - const auto borderHeight = getValue(patchHeight); - const auto pos = Point(halfSize, halfSize); - - // Origin line paths. Draw both from {0, 0} so the strokes touch at the origin - nvgMoveTo(nvg, pos.x, pos.y); - nvgLineTo(nvg, pos.x, pos.y + (showOrigin ? halfSize : borderHeight)); - nvgMoveTo(nvg, pos.x, pos.y); - nvgLineTo(nvg, pos.x + (showOrigin ? halfSize : borderWidth), pos.y); - - if(showBorder) - { - nvgMoveTo(nvg, pos.x + borderWidth, pos.y + borderHeight); - nvgLineTo(nvg, pos.x + borderWidth, pos.y); - nvgMoveTo(nvg, pos.x + borderWidth, pos.y + borderHeight); - nvgLineTo(nvg, pos.x, pos.y + borderHeight); + const auto borderWidth = getValue(patchWidth); + const auto borderHeight = getValue(patchHeight); + const auto pos = Point(halfSize, halfSize); + + // Origin line paths. Draw both from {0, 0} so the strokes touch at the origin + nvgMoveTo(nvg, pos.x, pos.y); + nvgLineTo(nvg, pos.x, pos.y + (showOrigin ? halfSize : borderHeight)); + nvgMoveTo(nvg, pos.x, pos.y); + nvgLineTo(nvg, pos.x + (showOrigin ? halfSize : borderWidth), pos.y); + + if (showBorder) { + nvgMoveTo(nvg, pos.x + borderWidth, pos.y + borderHeight); + nvgLineTo(nvg, pos.x + borderWidth, pos.y); + nvgMoveTo(nvg, pos.x + borderWidth, pos.y + borderHeight); + nvgLineTo(nvg, pos.x, pos.y + borderHeight); + } + + if (bg) { + // place solid line behind (to fake removeing grid points for now) + nvgLineStyle(nvg, NVG_LINE_SOLID); + nvgStrokeColor(nvg, backgroundColour); + nvgStrokeWidth(nvg, 6.0f); + nvgStroke(nvg); + } + if (fg) { + auto scaledStrokeSize = zoom < 1.0f ? jmap(zoom, 1.0f, 0.25f, 1.5f, 4.0f) : 1.5f; + if (zoom < 0.3f && getRenderScale() <= 1.0f) + scaledStrokeSize = jmap(zoom, 0.3f, 0.25f, 4.0f, 8.0f); + + // draw 0,0 point lines + nvgLineStyle(nvg, NVG_LINE_DASHED); + + nvgStrokeColor(nvg, dotsColour); + nvgStrokeWidth(nvg, scaledStrokeSize); + nvgDashLength(nvg, 8.0f); + nvgStroke(nvg); + + // Connect origin lines at {0, 0} + nvgBeginPath(nvg); + nvgMoveTo(nvg, pos.x + 2.0f, pos.y); + nvgLineTo(nvg, pos.x, pos.y); + nvgLineTo(nvg, pos.x, pos.y + 2.0f); + nvgLineStyle(nvg, NVG_LINE_SOLID); + nvgStrokeWidth(nvg, 1.25f); + nvgStroke(nvg); + } } - - // place solid line behind (to fake removeing grid points for now) - nvgLineStyle(nvg, NVG_LINE_SOLID); - nvgStrokeColor(nvg, backgroundColour); - nvgStrokeWidth(nvg, 6.0f); - nvgStroke(nvg); - - auto scaledStrokeSize = zoom < 1.0f ? jmap(zoom, 1.0f, 0.25f, 1.5f, 4.0f) : 1.5f; - if (zoom < 0.3f && getRenderScale() <= 1.0f) - scaledStrokeSize = jmap(zoom, 0.3f, 0.25f, 4.0f, 8.0f); + }; - // draw 0,0 point lines - nvgLineStyle(nvg, NVG_LINE_DASHED); - - nvgStrokeColor(nvg, dotsColour); - nvgStrokeWidth(nvg, scaledStrokeSize); - nvgDashLength(nvg, 8.0f); - nvgStroke(nvg); - - // Connect origin lines at {0, 0} - nvgBeginPath(nvg); - nvgMoveTo(nvg, pos.x + 2.0f, pos.y); - nvgLineTo(nvg, pos.x, pos.y); - nvgLineTo(nvg, pos.x, pos.y + 2.0f); - nvgLineStyle(nvg, NVG_LINE_SOLID); - nvgStrokeWidth(nvg, 1.25f); - nvgStroke(nvg); - } + if (!dimensionsAreBeingEdited) + drawBorder(true, true); + else + drawBorder(true, false); // Render objects like [drawcurve], [fillcurve] etc. at the back @@ -506,6 +520,9 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) } suggestor->renderAutocompletion(nvg); + + if (dimensionsAreBeingEdited) + drawBorder(false, true); nvgRestore(nvg); diff --git a/Source/Canvas.h b/Source/Canvas.h index 5a15c14ac6..c8f1b23923 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -269,6 +269,8 @@ class Canvas : public Component private: GlobalMouseListener globalMouseListener; + + bool dimensionsAreBeingEdited = false; int lastMouseX, lastMouseY; LassoComponent> lasso; diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index a7a1bbcd49..3466141223 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -43,6 +43,8 @@ class DraggableNumber : public Label std::function dragStart = []() {}; std::function dragEnd = []() {}; + std::function onInteraction = [](bool) {}; + explicit DraggableNumber(bool integerDrag) : dragMode(integerDrag ? Integer : Regular) { @@ -55,6 +57,7 @@ class DraggableNumber : public Label void editorShown(Label* l, TextEditor& editor) override { + onInteraction(true); dragStart(); editor.onTextChange = [this]() { if (onTextChange) @@ -64,6 +67,7 @@ class DraggableNumber : public Label void editorHidden(Label*, TextEditor& editor) override { + onInteraction(false); auto newValue = editor.getText().getDoubleValue(); setValue(newValue, dontSendNotification); decimalDrag = 0; @@ -188,6 +192,8 @@ class DraggableNumber : public Label if (isBeingEdited()) return; + onInteraction(true); + bool command = e.mods.isCommandDown(); if (command && resetOnCommandClick) { @@ -419,6 +425,8 @@ class DraggableNumber : public Label if (isBeingEdited()) return; + onInteraction(false); + repaint(); // Show cursor again diff --git a/Source/Components/PropertiesPanel.h b/Source/Components/PropertiesPanel.h index f37888c4cd..b31d0b715b 100644 --- a/Source/Components/PropertiesPanel.h +++ b/Source/Components/PropertiesPanel.h @@ -780,7 +780,7 @@ class PropertiesPanel : public Component { Value property; String allowedCharacters = ""; - EditableComponent(String propertyName, Value& value, double minimum = 0.0, double maximum = 0.0) + EditableComponent(String propertyName, Value& value, double minimum = 0.0, double maximum = 0.0, std::function onInteractionFn = nullptr) : PropertiesPanelProperty(propertyName) , property(value) { @@ -796,6 +796,9 @@ class PropertiesPanel : public Component { if(minimum != 0.0f) draggableNumber->setMinimum(minimum); if(maximum != 0.0f) draggableNumber->setMaximum(maximum); + if (onInteractionFn) + draggableNumber->onInteraction = onInteractionFn; + draggableNumber->onEditorShow = [draggableNumber]() { auto* editor = draggableNumber->getCurrentTextEditor(); editor->setBorder(BorderSize(2, 1, 4, 1)); diff --git a/Source/Objects/ObjectBase.cpp b/Source/Objects/ObjectBase.cpp index bcab9a1a2b..2e437213d0 100644 --- a/Source/Objects/ObjectBase.cpp +++ b/Source/Objects/ObjectBase.cpp @@ -197,7 +197,7 @@ void ObjectBase::initialise() pd->registerMessageListener(ptr.getRawUnchecked(), this); - for (auto& [name, type, cat, value, list, valueDefault, customComponent] : objectParameters.getParameters()) { + for (auto& [name, type, cat, value, list, valueDefault, customComponent, onInteractionFn] : objectParameters.getParameters()) { if (value) { value->addListener(this); value->addListener(&propertyUndoListener); diff --git a/Source/Objects/ObjectParameters.h b/Source/Objects/ObjectParameters.h index 1d1705da34..d4a057b073 100644 --- a/Source/Objects/ObjectParameters.h +++ b/Source/Objects/ObjectParameters.h @@ -28,8 +28,9 @@ enum ParameterCategory { class PropertiesPanelProperty; using CustomPanelCreateFn = std::function; +using InteractionFn = std::function; -using ObjectParameter = std::tuple; +using ObjectParameter = std::tuple; class ObjectParameters { public: @@ -48,7 +49,7 @@ class ObjectParameters { void resetAll() { auto& lnf = LookAndFeel::getDefaultLookAndFeel(); - for (auto [name, type, category, value, options, defaultVal, customComponent] : objectParameters) { + for (auto [name, type, category, value, options, defaultVal, customComponent, onInteractionFn] : objectParameters) { if (!defaultVal.isVoid()) { if (type == tColour) { value->setValue(lnf.findColour(defaultVal).toString()); @@ -68,9 +69,9 @@ class ObjectParameters { objectParameters.add(makeParam(pString, tFloat, pCat, pVal, StringArray(), pDefault)); } - void addParamInt(String const& pString, ParameterCategory pCat, Value* pVal, var const& pDefault = var()) + void addParamInt(String const& pString, ParameterCategory pCat, Value* pVal, var const& pDefault = var(), InteractionFn onInteractionFn = nullptr) { - objectParameters.add(makeParam(pString, tInt, pCat, pVal, StringArray(), pDefault)); + objectParameters.add(makeParam(pString, tInt, pCat, pVal, StringArray(), pDefault, nullptr, onInteractionFn)); } void addParamBool(String const& pString, ParameterCategory pCat, Value* pVal, StringArray const& pList, var const& pDefault = var()) @@ -146,8 +147,8 @@ class ObjectParameters { private: Array objectParameters; - static ObjectParameter makeParam(String const& pString, ParameterType pType, ParameterCategory pCat, Value* pVal, StringArray const& pStringList, var const& pDefault, CustomPanelCreateFn customComponentFn = nullptr) + static ObjectParameter makeParam(String const& pString, ParameterType pType, ParameterCategory pCat, Value* pVal, StringArray const& pStringList, var const& pDefault, CustomPanelCreateFn customComponentFn = nullptr, InteractionFn onInteractionFn = nullptr) { - return std::make_tuple(pString, pType, pCat, pVal, pStringList, pDefault, customComponentFn); + return std::make_tuple(pString, pType, pCat, pVal, pStringList, pDefault, customComponentFn, onInteractionFn); } }; diff --git a/Source/Sidebar/Inspector.h b/Source/Sidebar/Inspector.h index 4108d34ca6..b35e173206 100644 --- a/Source/Sidebar/Inspector.h +++ b/Source/Sidebar/Inspector.h @@ -68,7 +68,7 @@ class Inspector : public Component { panel.setContentWidth(getWidth() - 16); } - static PropertiesPanelProperty* createPanel(int type, String const& name, Value* value, StringArray& options) + static PropertiesPanelProperty* createPanel(int type, String const& name, Value* value, StringArray& options, std::function onInteractionFn = nullptr) { switch (type) { case tString: @@ -76,7 +76,7 @@ class Inspector : public Component { case tFloat: return new PropertiesPanel::EditableComponent(name, *value); case tInt: - return new PropertiesPanel::EditableComponent(name, *value); + return new PropertiesPanel::EditableComponent(name, *value, 0.0f, 0.0f, onInteractionFn); case tColour: return new PropertiesPanel::ColourComponent(name, *value); case tBool: @@ -108,7 +108,7 @@ class Inspector : public Component { panel.clear(); auto parameterIsInAllObjects = [&objectParameters](ObjectParameter& param, Array& values) { - auto& [name1, type1, category1, value1, options1, defaultVal1, customComponent1] = param; + auto& [name1, type1, category1, value1, options1, defaultVal1, customComponent1, onInteractionFn1] = param; if (objectParameters.size() > 1 && (name1 == "Size" || name1 == "Position" || name1 == "Height")) { return false; @@ -117,7 +117,7 @@ class Inspector : public Component { bool isInAllObjects = true; for (auto& parameters : objectParameters) { bool hasParameter = false; - for (auto& [name2, type2, category2, value2, options2, defaultVal2, customComponent2] : parameters.getParameters()) { + for (auto& [name2, type2, category2, value2, options2, defaultVal2, customComponent2, onInteractionFn2] : parameters.getParameters()) { if (name1 == name2 && type1 == type2 && category1 == category2) { values.add(value2); hasParameter = true; @@ -136,7 +136,7 @@ class Inspector : public Component { for (int i = 0; i < 4; i++) { Array panels; for (auto& parameter : objectParameters[0].getParameters()) { - auto& [name, type, category, value, options, defaultVal, customComponentFn] = parameter; + auto& [name, type, category, value, options, defaultVal, customComponentFn, onInteractionFn] = parameter; if (customComponentFn && objectParameters.size() == 1 && static_cast(category) == i) { if (auto* customComponent = customComponentFn()) { @@ -153,7 +153,7 @@ class Inspector : public Component { continue; else if (objectParameters.size() == 1) { - auto newPanel = createPanel(type, name, value, options); + auto newPanel = createPanel(type, name, value, options, onInteractionFn); newPanel->setPreferredHeight(26); panels.add(newPanel); } else { From f2f35da0ab0b83720ddc141c05e1e0f9d54c0c3a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 5 Jun 2024 17:35:37 +0200 Subject: [PATCH 0872/1030] Split view cleanup, simplified patch management --- Libraries/JUCE | 2 +- Source/Canvas.cpp | 63 +- Source/Canvas.h | 5 +- Source/CanvasViewport.h | 2 +- .../{Utility => Components}/MarkupDisplay.h | 3 +- Source/Components/ObjectDragAndDrop.h | 22 +- Source/Components/SuggestionComponent.h | 6 +- Source/Dialogs/Dialogs.cpp | 22 +- Source/Dialogs/HelpDialog.h | 3 +- Source/Dialogs/MainMenu.h | 2 +- Source/LookAndFeel.cpp | 75 +- Source/LookAndFeel.h | 16 - Source/NVGSurface.cpp | 22 +- Source/Object.cpp | 30 +- Source/Object.h | 1 + Source/Objects/ArrayObject.h | 2 +- Source/Objects/CloneObject.h | 17 +- Source/Objects/GraphOnParent.h | 7 +- Source/Objects/ImplementationBase.cpp | 22 +- Source/Objects/ObjectBase.cpp | 21 +- Source/Objects/SubpatchObject.h | 2 +- Source/Pd/Instance.h | 3 + Source/Pd/Patch.cpp | 13 + Source/Pd/Patch.h | 1 + Source/PluginEditor.cpp | 357 ++-------- Source/PluginEditor.h | 41 +- Source/PluginMode.h | 5 +- Source/PluginProcessor.cpp | 148 ++-- Source/PluginProcessor.h | 8 +- Source/Sidebar/DocumentationBrowser.h | 3 +- Source/Sidebar/Palettes.h | 2 +- Source/Standalone/PlugDataApp.cpp | 6 +- Source/TabComponent.cpp | 651 ++++++++++++++++++ Source/TabComponent.h | 243 +++++++ Source/Tabbar/ResizableTabbedComponent.cpp | 528 -------------- Source/Tabbar/ResizableTabbedComponent.h | 96 --- Source/Tabbar/SplitView.cpp | 246 ------- Source/Tabbar/SplitView.h | 59 -- Source/Tabbar/SplitViewResizer.cpp | 154 ----- Source/Tabbar/SplitViewResizer.h | 46 -- Source/Tabbar/TabBarButtonComponent.cpp | 371 ---------- Source/Tabbar/TabBarButtonComponent.h | 54 -- Source/Tabbar/Tabbar.cpp | 527 -------------- Source/Tabbar/Tabbar.h | 114 --- Source/Tabbar/WelcomePanel.h | 7 +- Source/Utility/Autosave.h | 2 + Source/Utility/SplitModeEnum.h | 9 - Source/Utility/ValueTreeNodeBranchLine.cpp | 33 - Source/Utility/ValueTreeNodeBranchLine.h | 69 -- Source/Utility/ValueTreeViewer.h | 83 ++- .../ZoomableDragAndDropContainer.cpp | 64 +- .../ZoomableDragAndDropContainer.h | 8 +- 52 files changed, 1273 insertions(+), 3023 deletions(-) rename Source/{Utility => Components}/MarkupDisplay.h (97%) create mode 100644 Source/TabComponent.cpp create mode 100644 Source/TabComponent.h delete mode 100644 Source/Tabbar/ResizableTabbedComponent.cpp delete mode 100644 Source/Tabbar/ResizableTabbedComponent.h delete mode 100644 Source/Tabbar/SplitView.cpp delete mode 100644 Source/Tabbar/SplitView.h delete mode 100644 Source/Tabbar/SplitViewResizer.cpp delete mode 100644 Source/Tabbar/SplitViewResizer.h delete mode 100644 Source/Tabbar/TabBarButtonComponent.cpp delete mode 100644 Source/Tabbar/TabBarButtonComponent.h delete mode 100644 Source/Tabbar/Tabbar.cpp delete mode 100644 Source/Tabbar/Tabbar.h delete mode 100644 Source/Utility/SplitModeEnum.h delete mode 100644 Source/Utility/ValueTreeNodeBranchLine.cpp delete mode 100644 Source/Utility/ValueTreeNodeBranchLine.h rename Source/{Components => Utility}/ZoomableDragAndDropContainer.cpp (89%) rename Source/{Components => Utility}/ZoomableDragAndDropContainer.h (96%) diff --git a/Libraries/JUCE b/Libraries/JUCE index d9d358251f..74517f27ef 160000 --- a/Libraries/JUCE +++ b/Libraries/JUCE @@ -1 +1 @@ -Subproject commit d9d358251fdd17e0796f062bb35b2415d5ea5250 +Subproject commit 74517f27efce19b7c070de34ee63a9a357d355fb diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 844264eab7..60ba0d7c03 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -17,7 +17,6 @@ #include "LookAndFeel.h" #include "Components/SuggestionComponent.h" #include "CanvasViewport.h" -#include "Tabbar/SplitView.h" #include "Objects/ObjectBase.h" @@ -736,17 +735,6 @@ void Canvas::zoomToFitAll() viewport->setViewPosition(newViewPos); } -TabComponent* Canvas::getTabbar() -{ - for (auto* split : editor->splitView.splits) { - auto tabbar = split->getTabComponent(); - if (tabbar->getIndexOfCanvas(this) >= 0) - return tabbar; - } - - return nullptr; -} - void Canvas::tabChanged() { patch.setCurrent(); @@ -767,14 +755,50 @@ void Canvas::tabChanged() editor->repaint(); // Make sure everything it up to date } -int Canvas::getTabIndex() +void Canvas::save(std::function const& nestedCallback) { - if (auto* tabbar = getTabbar()) { - return tabbar->getIndexOfCanvas(this); + Canvas* canvasToSave = this; + if (patch.isSubpatch()) { + for (auto& parentCanvas : editor->getCanvases()) { + if (patch.getRoot() == parentCanvas->patch.getPointer().get()) { + canvasToSave = parentCanvas; + } + } + } + + if (patch.getCurrentFile().existsAsFile()) { + canvasToSave->updatePatchSnapshot(); + canvasToSave->patch.savePatch(); + SettingsFile::getInstance()->addToRecentlyOpened(canvasToSave->patch.getCurrentFile()); + nestedCallback(); + pd->titleChanged(); + } else { + saveAs(nestedCallback); } - return -1; } +void Canvas::saveAs(std::function const& nestedCallback) +{ + Dialogs::showSaveDialog([this, nestedCallback](URL resultURL) mutable { + auto result = resultURL.getLocalFile(); + if (result.getFullPathName().isNotEmpty()) { + if (result.exists()) + result.deleteFile(); + + if(!result.hasFileExtension("pd")) result = result.getFullPathName() + ".pd"; + + updatePatchSnapshot(); + patch.savePatch(resultURL); + SettingsFile::getInstance()->addToRecentlyOpened(result); + pd->titleChanged(); + } + + nestedCallback(); + }, + "*.pd", "Patch", this); +} + + void Canvas::handleAsyncUpdate() { performSynchronise(); @@ -787,10 +811,9 @@ void Canvas::synchronise() void Canvas::synchroniseSplitCanvas() { - for (auto split : editor->splitView.splits) { - auto tabbar = split->getTabComponent(); - if (auto* activeTabCanvas = tabbar->getCurrentCanvas()) - activeTabCanvas->synchronise(); + for(auto* canvas : editor->getTabComponent().getVisibleCanvases()) + { + canvas->synchronise(); } } diff --git a/Source/Canvas.h b/Source/Canvas.h index 5a15c14ac6..b0fc0bca9f 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -100,6 +100,9 @@ class Canvas : public Component bool shouldShowIndex(); bool shouldShowConnectionDirection(); bool shouldShowConnectionActivity(); + + void save(std::function const& nestedCallback = [](){}); + void saveAs(std::function const& nestedCallback = [](){}); void synchroniseSplitCanvas(); void synchronise(); @@ -111,8 +114,6 @@ class Canvas : public Component bool keyPressed(KeyPress const& key) override; void valueChanged(Value& v) override; - TabComponent* getTabbar(); - int getTabIndex(); void tabChanged(); void hideAllActiveEditors(); diff --git a/Source/CanvasViewport.h b/Source/CanvasViewport.h index 7eba9b1df3..f97c72124c 100644 --- a/Source/CanvasViewport.h +++ b/Source/CanvasViewport.h @@ -401,7 +401,7 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent return; auto thickness = getScrollBarThickness(); - auto localArea = getLocalBounds().reduced(8); + auto localArea = getLocalBounds().reduced(2); vbar.setBounds(localArea.removeFromRight(thickness).withTrimmedBottom(thickness).translated(-1, 0)); hbar.setBounds(localArea.removeFromBottom(thickness).translated(0, -1)); diff --git a/Source/Utility/MarkupDisplay.h b/Source/Components/MarkupDisplay.h similarity index 97% rename from Source/Utility/MarkupDisplay.h rename to Source/Components/MarkupDisplay.h index cbd20d4500..701279254e 100644 --- a/Source/Utility/MarkupDisplay.h +++ b/Source/Components/MarkupDisplay.h @@ -117,7 +117,8 @@ #pragma once #include -#include "Fonts.h" +#include "Utility/Fonts.h" +#include "BouncingViewport.h" namespace MarkupDisplay { diff --git a/Source/Components/ObjectDragAndDrop.h b/Source/Components/ObjectDragAndDrop.h index 537eaec537..07b4e5e037 100644 --- a/Source/Components/ObjectDragAndDrop.h +++ b/Source/Components/ObjectDragAndDrop.h @@ -1,7 +1,7 @@ #pragma once #include -#include "ZoomableDragAndDropContainer.h" +//#include "Utility/ZoomableDragAndDropContainer.h" #include "Utility/OfflineObjectRenderer.h" #include "../PluginEditor.h" #include "Canvas.h" @@ -154,26 +154,16 @@ class ObjectClickAndDrop : public Component } else if (auto* cnv = underMouse->findParentComponentOfClass()) { foundCanvas = cnv; scale = getValue(cnv->zoomScale); - } else if (auto* split = editor->splitView.getSplitAtScreenPosition(screenPos)) { - // if we get here, this object is on the editor (not a window) - so we have to manually find the canvas - if (auto* tabComponent = split->getTabComponent()) { - foundCanvas = tabComponent->getCurrentCanvas(); - scale = getValue(foundCanvas->zoomScale); - } else { - scale = 1.0f; - } + } else if (auto* cnv = editor->getTabComponent().getCanvasAtScreenPosition(screenPos)) { + foundCanvas = cnv; + scale = getValue(foundCanvas->zoomScale); } else { scale = 1.0f; } - + if (foundCanvas && (foundCanvas != canvas)) { canvas = foundCanvas; - for (auto* split : editor->splitView.splits) { - if (canvas == split->getTabComponent()->getCurrentCanvas()) { - editor->splitView.setFocus(split); - break; - } - } + editor->getTabComponent().setActiveSplit(canvas); } // swap the image to show if the current drop position will result in adding a new object diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index ed999e70f9..93ebcfba4c 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -4,11 +4,13 @@ // WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ +#include #include "PluginEditor.h" #include "PluginProcessor.h" // TODO: We shouldn't need this! #include "Objects/ObjectBase.h" #include "Heavy/CompatibleObjects.h" #include "Utility/NanoVGGraphicsContext.h" +#include "Components/BouncingViewport.h" extern "C" { @@ -318,7 +320,7 @@ class SuggestionComponent : public Component currentObject = object; openedEditor = editor; - setTransform(object->cnv->editor->getTransform()); + setTransform(object->editor->getTransform()); editor->addComponentListener(this); editor->addListener(this); @@ -717,7 +719,7 @@ class SuggestionComponent : public Component return; // When hvcc mode is enabled, show only hvcc compatible objects - if (getValue(_this->currentObject->cnv->editor->hvccMode)) { + if (getValue(_this->currentObject->editor->hvccMode)) { StringArray hvccObjectsFound; for (auto& object : toFilter) { diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index 379be68fe0..248152f870 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -164,7 +164,7 @@ void Dialogs::showMainMenu(PluginEditor* editor, Component* centre) switch (result) { case 1: { - editor->newProject(); + editor->getTabComponent().newPatch(); break; } case 2: { @@ -172,13 +172,13 @@ void Dialogs::showMainMenu(PluginEditor* editor, Component* centre) break; } case 3: { - if (editor->getCurrentCanvas()) - editor->saveProject(); + if (auto* cnv = editor->getCurrentCanvas()) + cnv->saveProject(); break; } case 4: { - if (editor->getCurrentCanvas()) - editor->saveProjectAs(); + if (auto* cnv = editor->getCurrentCanvas()) + cnv->saveAs(); break; } case 5: { @@ -212,21 +212,21 @@ void Dialogs::showMainMenu(PluginEditor* editor, Component* centre) [editor, popup, settingsTree = SettingsFile::getInstance()->getValueTree()](int result) mutable { switch (result) { case MainMenu::MenuItem::NewPatch: { - editor->newProject(); + editor->getTabComponent().newPatch(); break; } case MainMenu::MenuItem::OpenPatch: { - editor->openProject(); + editor->getTabComponent().openPatch(); break; } case MainMenu::MenuItem::Save: { - if (editor->getCurrentCanvas()) - editor->saveProject(); + if (auto* cnv = editor->getCurrentCanvas()) + cnv->save(); break; } case MainMenu::MenuItem::SaveAs: { - if (editor->getCurrentCanvas()) - editor->saveProjectAs(); + if (auto* cnv = editor->getCurrentCanvas()) + cnv->saveAs(); break; } case MainMenu::MenuItem::CompiledMode: { diff --git a/Source/Dialogs/HelpDialog.h b/Source/Dialogs/HelpDialog.h index 7e532a6f22..6a1849353b 100644 --- a/Source/Dialogs/HelpDialog.h +++ b/Source/Dialogs/HelpDialog.h @@ -4,7 +4,8 @@ // WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ -#include +#include "Components/MarkupDisplay.h" +#include "Components/BouncingViewport.h" class HelpDialog : public TopLevelWindow , public MarkupDisplay::FileSource { diff --git a/Source/Dialogs/MainMenu.h b/Source/Dialogs/MainMenu.h index 87cf7339c3..20169800e7 100644 --- a/Source/Dialogs/MainMenu.h +++ b/Source/Dialogs/MainMenu.h @@ -31,7 +31,7 @@ class MainMenu : public PopupMenu { auto path = File(recentlyOpenedTree.getChild(i).getProperty("Path").toString()); recentlyOpened->addItem(path.getFileName(), [path, editor]() mutable { editor->autosave->checkForMoreRecentAutosave(path, [editor, path]() { - editor->pd->loadPatch(URL(path), editor, -1); + editor->getTabComponent().openPatch(URL(path)); SettingsFile::getInstance()->addToRecentlyOpened(path); }); }); diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index 01a903370d..80143ac2bd 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -11,7 +11,6 @@ #include "Utility/SettingsFile.h" #include "Constants.h" #include "Utility/Fonts.h" -#include "Tabbar/TabBarButtonComponent.h" class PlugData_DocumentWindowButton_macOS : public Button , public FocusChangeListener { @@ -468,8 +467,7 @@ void PlugDataLook::positionDocumentWindowButtons(DocumentWindow& window, } } -// ==================== LookAndFeel TabBarButton ==================== - +/* Rectangle PlugDataLook::getTabButtonExtraComponentBounds(TabBarButton const& button, Rectangle& textArea, Component& comp) { Rectangle extraComp; @@ -515,68 +513,6 @@ Rectangle PlugDataLook::getTabButtonExtraComponentBounds(TabBarButton const return extraComp; } -int PlugDataLook::getTabButtonBestWidth(TabBarButton& button, int tabDepth) -{ - auto& buttonBar = button.getTabbedButtonBar(); - return std::max((buttonBar.getWidth() / buttonBar.getNumTabs()) + 1, 120); -} - -int PlugDataLook::getTabButtonOverlap(int tabDepth) -{ - return 0; -} - -void PlugDataLook::drawTabButton(TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown) -{ - drawTabButton(button, g, isMouseOver, isMouseDown, false); -} - -void PlugDataLook::drawTabButton(TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown, bool isForceDrawn) -{ - auto dragged = button.getProperties()["dragged"]; - if (!isForceDrawn && !dragged.isVoid() && static_cast(dragged)) - return; - - bool isActive = button.getToggleState(); - - if (isActive) { - g.setColour(findColour(PlugDataColour::activeTabBackgroundColourId)); - } else if (isMouseOver) { - g.setColour(findColour(PlugDataColour::activeTabBackgroundColourId).interpolatedWith(findColour(PlugDataColour::toolbarBackgroundColourId), 0.4f)); - } else { - g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); - } - - fillSmoothedRectangle(g, button.getLocalBounds().toFloat().reduced(4.5f), Corners::defaultCornerRadius); - drawTabButtonText(button, g, isMouseOver, isMouseDown); -} - -void PlugDataLook::drawTabButtonText(TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown) -{ - auto area = button.getLocalBounds().reduced(4, 1).toFloat(); - - Font font(getTabButtonFont(button, area.getHeight())); - font.setUnderline(button.hasKeyboardFocus(false)); - - Colour col; - - if (button.isFrontTab() && (button.isColourSpecified(TabbedButtonBar::frontTextColourId) || isColourSpecified(TabbedButtonBar::frontTextColourId))) - col = findColour(TabbedButtonBar::frontTextColourId); - else if (button.isColourSpecified(TabbedButtonBar::tabTextColourId) - || isColourSpecified(TabbedButtonBar::tabTextColourId)) - col = findColour(TabbedButtonBar::tabTextColourId); - else - col = button.getTabBackgroundColour().contrasting(); - - // Use a gradient to make it fade out when it gets near to the close button - auto fadeX = (isMouseOver || button.getToggleState()) ? area.getRight() - 25 : area.getRight() - 8; - g.setGradientFill(ColourGradient(col, fadeX - 18, area.getY(), Colours::transparentBlack, fadeX, area.getY(), false)); - - g.setFont(font); - - g.drawText(button.getButtonText().trim(), area.reduced(4, 0), Justification::centred, false); -} - Button* PlugDataLook::createTabBarExtrasButton() { @@ -641,6 +577,7 @@ Button* PlugDataLook::createTabBarExtrasButton() void mouseDown(MouseEvent const& e) override { + // TODO: split refactor fix class HiddenTabMenuItem : public PopupMenu::CustomComponent { String tabTitle; @@ -736,10 +673,8 @@ Button* PlugDataLook::createTabBarExtrasButton() if (!tab->isVisible()) { m.addCustomItem(i + 1, std::make_unique(tabNames[i], i, *parent), nullptr, tabNames[i]); } - /* - m.addItem (PopupMenu::Item (tabNames[i]) - .setTicked (i == parent->getCurrentTabIndex()) - .setAction ([this, i, parent] { parent->setCurrentTabIndex (i); })); */ + + //m.addItem (PopupMenu::Item (tabNames[i]).setTicked (i == parent->getCurrentTabIndex()) .setAction ([this, i, parent] { parent->setCurrentTabIndex (i); })); } m.showMenuAsync(PopupMenu::Options() @@ -750,7 +685,7 @@ Button* PlugDataLook::createTabBarExtrasButton() }; return new TabBarExtrasButton(); -} +} */ Font PlugDataLook::getTabButtonFont(TabBarButton&, float height) { diff --git a/Source/LookAndFeel.h b/Source/LookAndFeel.h index 94615748ff..cfb83511d5 100644 --- a/Source/LookAndFeel.h +++ b/Source/LookAndFeel.h @@ -96,22 +96,6 @@ struct PlugDataLook : public LookAndFeel_V4 { Button* closeButton, bool positionTitleBarButtonsOnLeft) override; - Rectangle getTabButtonExtraComponentBounds(TabBarButton const& button, Rectangle& textArea, Component& comp) override; - - int getTabButtonBestWidth(TabBarButton& button, int tabDepth) override; - - int getTabButtonOverlap(int tabDepth) override; - - void drawTabButton(TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown) override; - - void drawTabButton(TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown, bool isForceDrawn); - - void drawTabButtonText(TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown) override; - - void drawTabAreaBehindFrontButton(TabbedButtonBar& bar, Graphics& g, int const w, int const h) override { } - - Button* createTabBarExtrasButton() override; - Font getTabButtonFont(TabBarButton&, float height) override; void drawScrollbar(Graphics& g, ScrollBar& scrollbar, int x, int y, int width, int height, diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 426e5c3674..1029fa916e 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -281,15 +281,8 @@ void NVGSurface::render() return; // Render on next frame } - bool hasCanvas = false; - for(auto* split : editor->splitView.splits) - { - if(auto* cnv = split->getTabComponent()->getCurrentCanvas()) - { - hasCanvas = true; - break; - } - } + bool hasCanvas = editor->getCurrentCanvas() != nullptr; + // Manage showing/hiding welcome panel if(hasCanvas && editor->welcomePanel->isVisible()) { editor->welcomePanel->hide(); @@ -356,10 +349,6 @@ void NVGSurface::render() nvgFill(nvg); nvgRestore(nvg); - if(!editor->pluginMode) { - editor->splitView.render(nvg); // Render split view outlines and tab dnd areas - } - #if ENABLE_FPS_COUNT nvgSave(nvg); frameTimer->render(nvg); @@ -383,12 +372,9 @@ void NVGSurface::render() auto elapsed = Time::getMillisecondCounter() - startTime; // We update frambuffers after we call swapBuffers to make sure the frame is on time if(elapsed < 14) { - for(auto* split : editor->splitView.splits) + for(auto* cnv : editor->getTabComponent().getVisibleCanvases()) { - if(auto* cnv = split->getTabComponent()->getCurrentCanvas()) - { - cnv->updateFramebuffers(nvg, cnv->getLocalBounds(), 14 - elapsed); - } + cnv->updateFramebuffers(nvg, cnv->getLocalBounds(), 14 - elapsed); } } } diff --git a/Source/Object.cpp b/Source/Object.cpp index f60cd97bd8..8bf42e3a65 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -33,6 +33,7 @@ extern "C" { Object::Object(Canvas* parent, String const& name, Point position) : NVGComponent(this) , cnv(parent) + , editor(parent->editor) , gui(nullptr) , ds(parent->dragState) { @@ -55,11 +56,11 @@ Object::Object(Canvas* parent, String const& name, Point position) Object::Object(pd::WeakReference object, Canvas* parent) : NVGComponent(this) + , cnv(parent) + , editor(parent->editor) , gui(nullptr) , ds(parent->dragState) { - cnv = parent; - initialise(); setType("", object); @@ -129,7 +130,7 @@ void Object::initialise() commandLocked.referTo(cnv->pd->commandLocked); presentationMode.referTo(cnv->presentationMode); - hvccMode.referTo(cnv->editor->hvccMode); + hvccMode.referTo(editor->hvccMode); presentationMode.addListener(this); locked.addListener(this); @@ -335,9 +336,9 @@ void Object::applyBounds() patch->endUndoSequence("Resize"); - MessageManager::callAsync([cnv = SafePointer(this->cnv)] { - if (cnv) - cnv->editor->updateCommandStatus(); + MessageManager::callAsync([editor = SafePointer(this->editor)] { + if (editor) + editor->updateCommandStatus(); }); cnv->pd->unlockAudioThread(); @@ -425,7 +426,7 @@ void Object::setType(String const& newType, pd::WeakReference existingObject) resized(); // If bounds haven't changed, we'll still want to update gui and iolets bounds // Auto patching - if (getValue(cnv->editor->autoconnect) && numInputs && cnv->lastSelectedObject && cnv->lastSelectedObject != this && cnv->lastSelectedObject->numOutputs) { + if (getValue(editor->autoconnect) && numInputs && cnv->lastSelectedObject && cnv->lastSelectedObject != this && cnv->lastSelectedObject->numOutputs) { auto outlet = cnv->lastSelectedObject->iolets[cnv->lastSelectedObject->numInputs]; auto inlet = iolets[0]; if (outlet->isSignal == inlet->isSignal) { @@ -461,7 +462,7 @@ void Object::setType(String const& newType, pd::WeakReference existingObject) cnv->lastSelectedConnection = nullptr; - cnv->editor->updateCommandStatus(); + editor->updateCommandStatus(); cnv->synchroniseSplitCanvas(); cnv->pd->updateObjectImplementations(); @@ -1295,7 +1296,7 @@ void Object::performRender(NVGcontext* nvg) } // If autoconnect is about to happen, draw a fake inlet with a dotted outline - if (getValue(cnv->editor->autoconnect) && isInitialEditorShown() && cnv->lastSelectedObject && cnv->lastSelectedObject != this && cnv->lastSelectedObject->numOutputs) { + if (getValue(editor->autoconnect) && isInitialEditorShown() && cnv->lastSelectedObject && cnv->lastSelectedObject != this && cnv->lastSelectedObject->numOutputs) { auto outlet = cnv->lastSelectedObject->iolets[cnv->lastSelectedObject->numInputs]; float fakeInletBounds[4] = {16.0f, 4.0f, 8.0f, 8.0f}; nvgBeginPath(nvg); @@ -1559,7 +1560,6 @@ void Object::openHelpPatch() const cnv->pd->setThis(); if (auto* ptr = getPointer()) { - auto file = cnv->pd->objectLibrary->findHelpfile(ptr, cnv->patch.getCurrentFile()); if (!file.existsAsFile()) { @@ -1567,16 +1567,12 @@ void Object::openHelpPatch() const return; } - cnv->pd->lockAudioThread(); - auto patchPtr = cnv->pd->loadPatch(URL(file), cnv->editor, -1); - if(patchPtr) { - if(auto patch = patchPtr->getPointer()) { + auto* helpCanvas = editor->getTabComponent().openPatch(URL(file)); + if(helpCanvas) { + if(auto patch = helpCanvas->patch.getPointer()) { patch->gl_edit = 0; } } - - cnv->pd->unlockAudioThread(); - return; } diff --git a/Source/Object.h b/Source/Object.h index 8ec684a0fd..a9710ac55c 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -114,6 +114,7 @@ class Object : public Component Value hvccMode = Value(var(false)); Canvas* cnv; + PluginEditor* editor; std::unique_ptr gui; diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index e9a01bd7ee..94d7ca431b 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -313,7 +313,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess return; _this->object->cnv->setSelected(_this->object, false); - _this->object->cnv->editor->sidebar->hideParameters(); + _this->object->editor->sidebar->hideParameters(); _this->name = newName; }); diff --git a/Source/Objects/CloneObject.h b/Source/Objects/CloneObject.h index ad8bf4dc63..f8158614c7 100644 --- a/Source/Objects/CloneObject.h +++ b/Source/Objects/CloneObject.h @@ -82,18 +82,15 @@ class CloneObject final : public TextBase { return; // Check if patch is already opened - for (auto* cnv : cnv->editor->canvases) { + for (auto* cnv : cnv->editor->getCanvases()) { if (cnv->patch == *patch) { - - auto* tabbar = cnv->getTabbar(); - // Close the patch on "vis 0" if (!shouldVis) { - cnv->editor->closeTab(cnv); + cnv->editor->getTabComponent().closeTab(cnv); } // Show the current tab on "vis 1" else { - tabbar->setCurrentTabIndex(cnv->getTabIndex()); + cnv->editor->getTabComponent().showTab(cnv); } return; @@ -107,13 +104,7 @@ class CloneObject final : public TextBase { path = File(String::fromUTF8(canvas_getdir(glist)->s_name)).getChildFile(String::fromUTF8(glist->gl_name->s_name)).withFileExtension("pd"); } - cnv->editor->pd->patches.add(patch); - auto newPatch = cnv->editor->pd->patches.getLast(); - auto* newCanvas = cnv->editor->canvases.add(new Canvas(cnv->editor, *newPatch, nullptr)); - - newPatch->setCurrentFile(URL(path)); - - cnv->editor->addTab(newCanvas); + cnv->editor->getTabComponent().openPatch(patch); } void receiveObjectMessage(hash32 symbol, pd::Atom const atoms[8], int numAtoms) override diff --git a/Source/Objects/GraphOnParent.h b/Source/Objects/GraphOnParent.h index 6e1ad88787..6509afe34d 100644 --- a/Source/Objects/GraphOnParent.h +++ b/Source/Objects/GraphOnParent.h @@ -173,13 +173,14 @@ class GraphOnParent final : public ObjectBase { void tabChanged() override { isOpenedInSplitView = false; + /* TODO: split restucture! for (auto* split : cnv->editor->splitView.splits) { - if (auto* cnv = split->getTabComponent()->getCurrentCanvas()) { + if (auto* cnv = split->getTabComponent().getCurrentCanvas()) { if (cnv->patch == *getPatch()) { isOpenedInSplitView = true; } } - } + } */ updateCanvas(); repaint(); @@ -399,7 +400,7 @@ class GraphOnParent final : public ObjectBase { return; _this->cnv->setSelected(_this->object, false); - _this->object->cnv->editor->sidebar->hideParameters(); + _this->object->editor->sidebar->hideParameters(); _this->object->setType(_this->getText(), _this->ptr); }); diff --git a/Source/Objects/ImplementationBase.cpp b/Source/Objects/ImplementationBase.cpp index c8de5e572f..ce9e466627 100644 --- a/Source/Objects/ImplementationBase.cpp +++ b/Source/Objects/ImplementationBase.cpp @@ -44,7 +44,7 @@ Canvas* ImplementationBase::getMainCanvas(t_canvas* patchPtr, bool alsoSearchRoo auto editors = pd->getEditors(); for (auto* editor : editors) { - for (auto* cnv : editor->canvases) { + for (auto* cnv : editor->getCanvases()) { auto glist = cnv->patch.getPointer(); if (glist && glist.get() == patchPtr) { return cnv; @@ -55,7 +55,7 @@ Canvas* ImplementationBase::getMainCanvas(t_canvas* patchPtr, bool alsoSearchRoo if (alsoSearchRoot) { patchPtr = glist_getcanvas(patchPtr); for (auto* editor : editors) { - for (auto* cnv : editor->canvases) { + for (auto* cnv : editor->getCanvases()) { auto glist = cnv->patch.getPointer(); if (glist && glist.get() == patchPtr) { return cnv; @@ -133,7 +133,6 @@ void ImplementationBase::openSubpatch(pd::Patch* subpatch) auto path = File(String::fromUTF8(canvas_getdir(glist.get())->s_name)).getChildFile(String::fromUTF8(glist->gl_name->s_name)).withFileExtension("pd"); subpatch->setCurrentFile(URL(path)); } - pd->patches.add(subpatch); } else { return; } @@ -142,17 +141,8 @@ void ImplementationBase::openSubpatch(pd::Patch* subpatch) if (!editor->isActiveWindow()) continue; - // Check if subpatch is already opened - for (auto* cnv : editor->canvases) { - if (cnv->patch == *subpatch) { - auto* tabbar = cnv->getTabbar(); - tabbar->setCurrentTabIndex(cnv->getTabIndex()); - return; - } - } - - auto* newCanvas = editor->canvases.add(new Canvas(editor, subpatch, nullptr)); - editor->addTab(newCanvas); + editor->getTabComponent().openPatch(subpatch); + break; } } @@ -163,10 +153,10 @@ void ImplementationBase::closeOpenedSubpatchers() return; for (auto* editor : pd->getEditors()) { - for (auto* canvas : editor->canvases) { + for (auto* canvas : editor->getCanvases()) { auto canvasPtr = canvas->patch.getPointer(); if (canvasPtr && canvasPtr.get() == glist.get()) { - canvas->editor->closeTab(canvas); + canvas->editor->getTabComponent().closeTab(canvas); break; } } diff --git a/Source/Objects/ObjectBase.cpp b/Source/Objects/ObjectBase.cpp index bcab9a1a2b..8a0781cd38 100644 --- a/Source/Objects/ObjectBase.cpp +++ b/Source/Objects/ObjectBase.cpp @@ -33,10 +33,10 @@ void canvas_click(t_canvas* x, t_floatarg xpos, t_floatarg ypos, t_floatarg shif #include "Object.h" #include "Iolet.h" #include "Canvas.h" -#include "Tabbar/Tabbar.h" #include "Components/SuggestionComponent.h" #include "PluginEditor.h" #include "LookAndFeel.h" +#include "TabComponent.h" #include "Pd/Patch.h" #include "Sidebar/Sidebar.h" #include "Utility/CachedTextRender.h" @@ -321,13 +321,13 @@ Rectangle ObjectBase::getSelectableBounds() // Makes sure that any tabs refering to the now deleted patch will be closed void ObjectBase::closeOpenedSubpatchers() { - auto* editor = object->cnv->editor; + auto* editor = object->editor; - for (auto* canvas : editor->canvases) { + for (auto* canvas : editor->getCanvases()) { auto* patch = getPatch().get(); if (patch && canvas && canvas->patch == *patch) { - canvas->editor->closeTab(canvas); + canvas->editor->getTabComponent().closeTab(canvas); break; } } @@ -377,23 +377,18 @@ void ObjectBase::openSubpatch() } // Check if subpatch is already opened - for (auto* cnv : cnv->editor->canvases) { + for (auto* cnv : cnv->editor->getCanvases()) { if (cnv->patch == *subpatch) { - auto* tabbar = cnv->getTabbar(); - tabbar->setCurrentTabIndex(cnv->getTabIndex()); + cnv->editor->getTabComponent().showTab(cnv); return; } } - cnv->editor->pd->patches.add(subpatch); - auto newPatch = cnv->editor->pd->patches.getLast(); - auto* newCanvas = cnv->editor->canvases.add(new Canvas(cnv->editor, *newPatch, nullptr)); + cnv->editor->getTabComponent().openPatch(subpatch); if(path.getFullPathName().isNotEmpty()) { - newPatch->setCurrentFile(URL(path)); + subpatch->setCurrentFile(URL(path)); } - - cnv->editor->addTab(newCanvas); } void ObjectBase::moveToFront() diff --git a/Source/Objects/SubpatchObject.h b/Source/Objects/SubpatchObject.h index 13db07bb60..cea98ce204 100644 --- a/Source/Objects/SubpatchObject.h +++ b/Source/Objects/SubpatchObject.h @@ -111,7 +111,7 @@ class SubpatchObject final : public TextBase { return; _this->cnv->setSelected(object, false); - _this->object->cnv->editor->sidebar->hideParameters(); + _this->object->editor->sidebar->hideParameters(); _this->object->setType(_this->getText(), ptr); }); } diff --git a/Source/Pd/Instance.h b/Source/Pd/Instance.h index 96c722f8fb..8a5aa049b7 100644 --- a/Source/Pd/Instance.h +++ b/Source/Pd/Instance.h @@ -321,6 +321,9 @@ class Instance { std::recursive_mutex weakReferenceMutex; std::unique_ptr messageDispatcher; + // All opened patches + Array patches; + private: std::unordered_map> pdWeakReferences; diff --git a/Source/Pd/Patch.cpp b/Source/Pd/Patch.cpp index 38a88ea501..ff554d59bd 100644 --- a/Source/Pd/Patch.cpp +++ b/Source/Pd/Patch.cpp @@ -697,6 +697,19 @@ void Patch::setTitle(String const& title) }); } +void Patch::setUntitled() +{ + // find the lowest `Untitled-N` number, for the new patch title + int lowestNumber = 0; + for (auto patch : instance->patches) { + lowestNumber = std::max(lowestNumber, patch->untitledPatchNum); + } + lowestNumber += 1; + + untitledPatchNum = lowestNumber; + setTitle("Untitled-" + String(lowestNumber)); +} + File Patch::getCurrentFile() const { return currentFile; diff --git a/Source/Pd/Patch.h b/Source/Pd/Patch.h index 2b8199f49c..f11b790020 100644 --- a/Source/Pd/Patch.h +++ b/Source/Pd/Patch.h @@ -114,6 +114,7 @@ class Patch : public ReferenceCountedObject { String getTitle() const; void setTitle(String const& title); + void setUntitled(); Instance* instance = nullptr; bool closePatchOnDelete; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 909c076c51..c17c9ed4b6 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -28,7 +28,6 @@ #include "Dialogs/ConnectionMessageDisplay.h" #include "Dialogs/Dialogs.h" #include "Statusbar.h" -#include "Tabbar/TabBarButtonComponent.h" #include "Tabbar/WelcomePanel.h" #include "Sidebar/Sidebar.h" #include "Object.h" @@ -48,7 +47,6 @@ PluginEditor::PluginEditor(PluginProcessor& p) , statusbar(std::make_unique(&p)) , openedDialog(nullptr) , pluginMode(nullptr) - , splitView(this) , offlineRenderer(&p) , nvgSurface(this) , pluginConstrainer(*getConstrainer()) @@ -60,6 +58,7 @@ PluginEditor::PluginEditor(PluginProcessor& p) return true; }) + , tabComponent(this) , touchSelectionHelper(std::make_unique(this)) { #if JUCE_IOS @@ -128,10 +127,10 @@ PluginEditor::PluginEditor(PluginProcessor& p) addChildComponent(*palettes); addAndMakeVisible(*statusbar); - addAndMakeVisible(splitView); addAndMakeVisible(*sidebar); sidebar->toBehind(statusbar.get()); - + addAndMakeVisible(tabComponent); + calloutArea = std::make_unique(this); calloutArea->setVisible(true); calloutArea->setAlwaysOnTop(true); @@ -265,7 +264,7 @@ PluginEditor::PluginEditor(PluginProcessor& p) objectManager.lookAndFeelChanged(); addChildComponent(nvgSurface); - nvgSurface.toBehind(&splitView); + nvgSurface.toBehind(&tabComponent); #if JUCE_IOS addAndMakeVisible(touchSelectionHelper.get()); @@ -274,8 +273,7 @@ PluginEditor::PluginEditor(PluginProcessor& p) } PluginEditor::~PluginEditor() -{ - pd->savePatchTabPositions(); +{ theme.removeListener(this); if (auto* window = dynamic_cast(getTopLevelComponent())) { @@ -284,11 +282,6 @@ PluginEditor::~PluginEditor() } -SplitView* PluginEditor::getSplitView() -{ - return &splitView; -} - void PluginEditor::setUseBorderResizer(bool shouldUse) { if (shouldUse) { @@ -370,33 +363,24 @@ void PluginEditor::renderArea(NVGcontext* nvg, Rectangle area) pluginMode->render(nvg); } else { - bool hasCanvas = false; - for(auto* split : splitView.splits) + bool hasCanvas = getCurrentCanvas() != nullptr; + if(hasCanvas) { - if(auto* cnv = split->getTabComponent()->getCurrentCanvas()) - { + tabComponent.renderArea(nvg, area); + + if(touchSelectionHelper && touchSelectionHelper->isVisible() && area.intersects(touchSelectionHelper->getBounds() - nvgSurface.getPosition())) { nvgSave(nvg); - nvgTranslate(nvg, split->getX(), 0); - nvgScissor(nvg, 0, 0, split->getWidth(), split->getHeight()); - cnv->performRender(nvg, area.translated(-split->getX(), 0)); + nvgTranslate(nvg, touchSelectionHelper->getX() - nvgSurface.getX(), touchSelectionHelper->getY() - nvgSurface.getY()); + touchSelectionHelper->render(nvg); nvgRestore(nvg); - hasCanvas = true; } } - if(!hasCanvas) + else { nvgSave(nvg); welcomePanel->render(nvg); nvgRestore(nvg); } - else { - if(touchSelectionHelper && touchSelectionHelper->isVisible() && area.intersects(touchSelectionHelper->getBounds() - nvgSurface.getPosition())) { - nvgSave(nvg); - nvgTranslate(nvg, touchSelectionHelper->getX() - nvgSurface.getX(), touchSelectionHelper->getY() - nvgSurface.getY()); - touchSelectionHelper->render(nvg); - nvgRestore(nvg); - } - } } } @@ -432,7 +416,7 @@ CallOutBox& PluginEditor::showCalloutBox(std::unique_ptr content, Rec DragAndDropTarget* PluginEditor::findNextDragAndDropTarget(Point screenPos) { - return splitView.getSplitAtScreenPosition(screenPos); + return &tabComponent; } void PluginEditor::resized() @@ -464,7 +448,7 @@ void PluginEditor::resized() palettes->setBounds(0, toolbarHeight, palettes->getWidth(), workAreaHeight); auto workArea = Rectangle(paletteWidth, toolbarHeight, (getWidth() - sidebar->getWidth() - paletteWidth), workAreaHeight); - splitView.setBounds(workArea); + tabComponent.setBounds(workArea); welcomePanel->setBounds(workArea.withTrimmedTop(4)); nvgSurface.updateBounds(welcomePanel->isVisible() ? workArea.withTrimmedTop(6) : workArea.withTrimmedTop(31)); @@ -621,20 +605,20 @@ void PluginEditor::fileDragMove(StringArray const& files, int x, int y) } } - auto* splitUnderMouse = splitView.getSplitAtScreenPosition(localPointToGlobal(Point(x, y))); bool wasDraggingFile = isDraggingFile; - if (splitUnderMouse) { + if(auto* cnv = tabComponent.getCanvasAtScreenPosition(localPointToGlobal(Point(x, y)))) + { if (wasDraggingFile) { isDraggingFile = false; repaint(); } - splitView.setFocus(splitUnderMouse); + tabComponent.setActiveSplit(cnv); return; - } else { + } + else { if (!wasDraggingFile) { isDraggingFile = true; - repaint(); } repaint(); } @@ -649,31 +633,27 @@ void PluginEditor::filesDropped(StringArray const& files, int x, int y) if (file.exists() && file.hasFileExtension("pd")) { openedPdFiles = true; autosave->checkForMoreRecentAutosave(file, [this, file]() { - pd->loadPatch(URL(file), this, -1); + tabComponent.openPatch(URL(file)); SettingsFile::getInstance()->addToRecentlyOpened(file); pd->titleChanged(); }); } } - auto* splitUnderMouse = splitView.getSplitAtScreenPosition(localPointToGlobal(Point(x, y))); - if (splitUnderMouse && !openedPdFiles) { - if (auto* cnv = splitUnderMouse->getTabComponent()->getCurrentCanvas()) { - for (auto& path : files) { - auto file = File(path); - if (file.exists()) { - auto position = cnv->getLocalPoint(this, Point(x, y)); - auto filePath = file.getFullPathName().replaceCharacter('\\', '/').replace(" ", "\\ "); - - auto* object = cnv->objects.add(new Object(cnv, "msg " + filePath, position)); - object->hideEditor(); - } + if(auto* cnv = tabComponent.getCanvasAtScreenPosition(localPointToGlobal(Point(x, y)))) + { + for (auto& path : files) { + auto file = File(path); + if (file.exists() && !openedPdFiles) { + auto position = cnv->getLocalPoint(this, Point(x, y)); + auto filePath = file.getFullPathName().replaceCharacter('\\', '/').replace(" ", "\\ "); + + auto* object = cnv->objects.add(new Object(cnv, "msg " + filePath, position)); + object->hideEditor(); } - return; } } - isDraggingFile = false; repaint(); } @@ -689,7 +669,12 @@ void PluginEditor::fileDragExit(StringArray const&) repaint(); } -void PluginEditor::createNewWindow(TabBarButtonComponent* tabButton) +TabComponent& PluginEditor::getTabComponent() +{ + return tabComponent; +} + +void PluginEditor::createNewWindow(Canvas* cnv) { if (!ProjectInfo::isStandalone) return; @@ -704,19 +689,14 @@ void PluginEditor::createNewWindow(TabBarButtonComponent* tabButton) newWindow->addToDesktop(window->getDesktopWindowStyleFlags()); newWindow->setVisible(true); - auto* originalTabComponent = tabButton->getTabComponent(); - auto* originalCanvas = originalTabComponent->getCanvas(tabButton->getIndex()); - - newEditor->pd->patches.add(originalCanvas->patch); - auto newPatch = newEditor->pd->patches.getLast(); - auto* newCanvas = newEditor->canvases.add(new Canvas(newEditor, *newPatch, nullptr)); - newEditor->addTab(newCanvas); - newCanvas->jumpToOrigin(); + /* TODO: split refactor fix + auto* newCanvas = newEditor->getTabComponent().openPatch(subpatch); + newCanvas->jumpToOrigin(); */ newWindow->setTopLeftPosition(Desktop::getInstance().getMousePosition() - Point(500, 60)); newWindow->toFront(true); - closeTab(originalCanvas); + tabComponent.closeTab(cnv); } bool PluginEditor::isActiveWindow() @@ -725,127 +705,37 @@ bool PluginEditor::isActiveWindow() return !ProjectInfo::isStandalone || isDraggingTab || (TopLevelWindow::getActiveTopLevelWindow() == getTopLevelComponent()); } -void PluginEditor::newProject() -{ - // find the lowest `Untitled-N` number, for the new patch title - int lowestNumber = 1; - Array patchNumbers; - for (auto patch : pd->patches) { - patchNumbers.add(patch->untitledPatchNum); - } - // all patches with an untitledPatchNum of 0 are saved patches (at least once) - patchNumbers.removeAllInstancesOf(0); - patchNumbers.sort(); - - for (auto number : patchNumbers) { - if (number <= lowestNumber) - lowestNumber = number + 1; - } - - auto patch = pd->loadPatch(pd::Instance::defaultPatch, this, -1); - patch->untitledPatchNum = lowestNumber; - patch->setTitle("Untitled-" + String(lowestNumber)); -} - -void PluginEditor::openProject() -{ - Dialogs::showOpenDialog([this](URL resultURL) { - auto result = resultURL.getLocalFile(); - if (result.exists() && result.getFileExtension().equalsIgnoreCase(".pd")) { - - autosave->checkForMoreRecentAutosave(result, [this, result, resultURL]() { - pd->loadPatch(resultURL, this, -1); - SettingsFile::getInstance()->addToRecentlyOpened(result); - pd->titleChanged(); - }); - } - }, - true, false, "*.pd", "Patch", this); -} - -void PluginEditor::saveProjectAs(std::function const& nestedCallback) -{ - Dialogs::showSaveDialog([this, nestedCallback](URL resultURL) mutable { - auto result = resultURL.getLocalFile(); - if (result.getFullPathName().isNotEmpty()) { - if (result.exists()) - result.deleteFile(); - - if(!result.hasFileExtension("pd")) result = result.getFullPathName() + ".pd"; - - auto* cnv = getCurrentCanvas(); - cnv->updatePatchSnapshot(); - cnv->patch.savePatch(resultURL); - SettingsFile::getInstance()->addToRecentlyOpened(result); - pd->titleChanged(); - } - - nestedCallback(); - }, - "*.pd", "Patch", this); -} - -void PluginEditor::saveProject(std::function const& nestedCallback) -{ - for (auto const& patch : pd->patches) { - patch->deselectAll(); - } - - auto* cnv = getCurrentCanvas(); - - if (cnv->patch.isSubpatch()) { - for (auto& parentCanvas : canvases) { - if (cnv->patch.getRoot() == parentCanvas->patch.getPointer().get()) { - cnv = parentCanvas; - } - } - } - - if (cnv->patch.getCurrentFile().existsAsFile()) { - cnv->updatePatchSnapshot(); - cnv->patch.savePatch(); - SettingsFile::getInstance()->addToRecentlyOpened(cnv->patch.getCurrentFile()); - nestedCallback(); - pd->titleChanged(); - } else { - saveProjectAs(nestedCallback); - } -} - -TabComponent* PluginEditor::getActiveTabbar() +Array PluginEditor::getCanvases() { - return splitView.getActiveTabbar(); + return tabComponent.getCanvases(); } Canvas* PluginEditor::getCurrentCanvas() { - if (auto activeTabbar = getActiveTabbar()) { - return activeTabbar->getCurrentCanvas(); - } - return nullptr; + return tabComponent.getCurrentCanvas(); } void PluginEditor::closeAllTabs(bool quitAfterComplete, Canvas* patchToExclude, std::function afterComplete) { - if (!canvases.size()) { + if (!getCanvases().size()) { afterComplete(); if (quitAfterComplete) { JUCEApplication::quit(); } return; } - if (patchToExclude && canvases.size() == 1) { + if (patchToExclude && getCanvases().size() == 1) { afterComplete(); return; } - auto canvas = SafePointer(canvases.getLast()); + auto canvas = SafePointer(getCanvases().getLast()); auto patch = canvas->refCountedPatch; auto deleteFunc = [this, canvas, quitAfterComplete, patchToExclude, afterComplete]() { if (canvas && !(patchToExclude && canvas == patchToExclude)) { - closeTab(canvas); + tabComponent.closeTab(canvas); } closeAllTabs(quitAfterComplete, patchToExclude, afterComplete); @@ -856,11 +746,11 @@ void PluginEditor::closeAllTabs(bool quitAfterComplete, Canvas* patchToExclude, if (patch->isDirty()) { Dialogs::showAskToSaveDialog( &openedDialog, this, patch->getTitle(), - [this, canvas, deleteFunc](int result) mutable { + [canvas, deleteFunc](int result) mutable { if (!canvas) return; if (result == 2) - saveProject([deleteFunc]() mutable { deleteFunc(); }); + canvas->save([deleteFunc]() mutable { deleteFunc(); }); else if (result == 1) deleteFunc(); }, @@ -872,96 +762,6 @@ void PluginEditor::closeAllTabs(bool quitAfterComplete, Canvas* patchToExclude, } } -void PluginEditor::closeTab(Canvas* cnv) -{ - if (!cnv || !cnv->getTabbar()) - return; - - auto tabbar = SafePointer(cnv->getTabbar()); - auto const tabIdx = cnv->getTabIndex(); - auto const currentTabIdx = tabbar->getCurrentTabIndex(); - auto patch = cnv->refCountedPatch; - - sidebar->hideParameters(); - sidebar->clearSearchOutliner(); - - patch->setVisible(false); - - cnv->setCachedComponentImage(nullptr); - tabbar->removeTab(tabIdx); - canvases.removeObject(cnv); - - // It's possible that the tabbar has been deleted if this was the last tab - if (tabbar && tabbar->getNumTabs() > 0) { - int newTabIdx = std::max(currentTabIdx, 0); - if ((currentTabIdx >= tabIdx || currentTabIdx >= tabbar->getNumTabs()) && currentTabIdx > 0) { - newTabIdx--; - } - - // Set the new current tab index - tabbar->setCurrentTabIndex(newTabIdx); - } - - pd->patches.removeFirstMatchingValue(patch); - - for (auto split : splitView.splits) { - auto tabbar = split->getTabComponent(); - if (auto* cnv = tabbar->getCurrentCanvas()) - cnv->tabChanged(); - } - - pd->updateObjectImplementations(); - - splitView.closeEmptySplits(); - - pd->savePatchTabPositions(); - - MessageManager::callAsync([_this = SafePointer(this)]() { - if (!_this) - return; - _this->resized(); - }); -} - -void PluginEditor::addTab(Canvas* cnv, int splitIdx) -{ - auto patchTitle = cnv->patch.getTitle(); - - // Create a pointer to the TabBar in focus - if (splitIdx < 0) { - if (auto* focusedTabbar = splitView.getActiveTabbar()) { - int const newTabIdx = focusedTabbar->getCurrentTabIndex() + 1; // The tab index for the added tab - - // Add tab next to the currently focused tab - focusedTabbar->addTab(patchTitle, cnv->viewport.get(), newTabIdx); - focusedTabbar->setCurrentTabIndex(newTabIdx); - } - } else { - if (splitIdx > splitView.splits.size() - 1) { - while (splitIdx > splitView.splits.size() - 1) { - splitView.createNewSplit(cnv); - } - } else { - auto* tabComponent = splitView.splits[splitIdx]->getTabComponent(); - tabComponent->addTab(patchTitle, cnv->viewport.get(), tabComponent->getNumTabs() + 1); - } - } - - // Open help files and references in Locked Mode - if (patchTitle.contains("-help") || patchTitle.equalsIgnoreCase("reference")) - cnv->locked.setValue(true); - - cnv->setVisible(true); - cnv->jumpToOrigin(); - cnv->patch.setVisible(true); - - if (cnv->patch.openInPluginMode) { - enablePluginMode(cnv); - } - - pd->savePatchTabPositions(); -} - void PluginEditor::valueChanged(Value& v) { // Update theme @@ -980,6 +780,7 @@ void PluginEditor::modifierKeysChanged(ModifierKeys const& modifiers) void PluginEditor::handleAsyncUpdate() { // Reflect patch dirty state in tab title + /* TODO: split restucture fix for (auto split : splitView.splits) { auto tabbar = split->getTabComponent(); for (int n = 0; n < tabbar->getNumTabs(); n++) { @@ -991,7 +792,7 @@ void PluginEditor::handleAsyncUpdate() auto tabText = tabbar->getTabText(n); tabbar->setTabText(n, tabText.trimCharactersAtEnd("*") + (isDirty ? "*" : "")); } - } + } */ if (auto* cnv = getCurrentCanvas()) { bool locked = getValue(cnv->locked); @@ -1453,11 +1254,11 @@ bool PluginEditor::perform(InvocationInfo const& info) return false; } case CommandIDs::NewProject: { - newProject(); + tabComponent.newPatch(); return true; } case CommandIDs::OpenProject: { - openProject(); + tabComponent.openPatch(); return true; } case CommandIDs::ShowBrowser: { @@ -1502,18 +1303,14 @@ bool PluginEditor::perform(InvocationInfo const& info) switch (info.commandID) { case CommandIDs::SaveProject: { - saveProject(); + cnv->save(); return true; } case CommandIDs::SaveProjectAs: { - saveProjectAs(); + cnv->saveAs(); return true; } case CommandIDs::CloseTab: { - auto* activeTabbar = splitView.getActiveTabbar(); - if (activeTabbar && activeTabbar->getNumTabs() == 0) - return true; - if (cnv) { MessageManager::callAsync([this, cnv = SafePointer(cnv)]() mutable { if (cnv && cnv->patch.isDirty()) { @@ -1523,13 +1320,13 @@ bool PluginEditor::perform(InvocationInfo const& info) if (!cnv) return; if (result == 2) - saveProject([this, cnv]() mutable { closeTab(cnv); }); + cnv->save([this, cnv]() mutable { tabComponent.closeTab(cnv); }); else if (result == 1) - closeTab(cnv); + tabComponent.closeTab(cnv); }, 0, true); } else { - closeTab(cnv); + tabComponent.closeTab(cnv); } }); } @@ -1662,31 +1459,11 @@ bool PluginEditor::perform(InvocationInfo const& info) return true; } case CommandIDs::NextTab: { - auto* tabbar = cnv->getTabbar(); - - int currentIdx = cnv->getTabIndex() + 1; - - if (currentIdx >= tabbar->getNumTabs()) - currentIdx -= tabbar->getNumTabs(); - if (currentIdx < 0) - currentIdx += tabbar->getNumTabs(); - - tabbar->setCurrentTabIndex(currentIdx); - + tabComponent.nextTab(); return true; } case CommandIDs::PreviousTab: { - auto* tabbar = cnv->getTabbar(); - - int currentIdx = cnv->getTabIndex() - 1; - - if (currentIdx >= tabbar->getNumTabs()) - currentIdx -= tabbar->getNumTabs(); - if (currentIdx < 0) - currentIdx += tabbar->getNumTabs(); - - tabbar->setCurrentTabIndex(currentIdx); - + tabComponent.previousTab(); return true; } case CommandIDs::ShowReference: { @@ -1804,7 +1581,7 @@ void PluginEditor::enablePluginMode(Canvas* cnv) return; // Restore Plugin Mode View - for (auto* canvas : canvases) { + for (auto* canvas : getCanvases()) { if (canvas && canvas->patch.openInPluginMode) { enablePluginMode(canvas); } @@ -1917,7 +1694,7 @@ bool PluginEditor::highlightSearchTarget(void* target, bool openNewTabIfNeeded) return false; - for (auto* cnv : canvases) { + for (auto* cnv : getCanvases()) { if (cnv->patch.getPointer().get() == targetCanvas) { Object* found = nullptr; for (auto* object : cnv->objects) { @@ -1940,7 +1717,7 @@ bool PluginEditor::highlightSearchTarget(void* target, bool openNewTabIfNeeded) pos.y -= viewport->getViewHeight() * 0.5f; viewport->setViewPosition(pos); - cnv->getTabbar()->setCurrentTabIndex(cnv->getTabIndex()); + tabComponent.showTab(cnv); return true; } @@ -1948,9 +1725,7 @@ bool PluginEditor::highlightSearchTarget(void* target, bool openNewTabIfNeeded) } if(openNewTabIfNeeded) { - auto* patch = new pd::Patch(pd::WeakReference(targetCanvas, pd), pd, false); - auto* cnv = canvases.add(new Canvas(this, patch)); - addTab(cnv); + auto* cnv = tabComponent.openPatch(new pd::Patch(pd::WeakReference(targetCanvas, pd), pd, false)); Object* found = nullptr; for (auto* object : cnv->objects) { @@ -1973,7 +1748,7 @@ bool PluginEditor::highlightSearchTarget(void* target, bool openNewTabIfNeeded) pos.y -= viewport->getViewHeight() * 0.5f; viewport->setViewPosition(pos); - cnv->getTabbar()->setCurrentTabIndex(cnv->getTabIndex()); + tabComponent.showTab(cnv); } return true; diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 0bef449442..ab84e7399d 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -12,11 +12,12 @@ #include "Utility/Fonts.h" #include "Utility/ModifierKeyListener.h" #include "Components/CheckedTooltip.h" -#include "Components/ZoomableDragAndDropContainer.h" +#include "Utility/ZoomableDragAndDropContainer.h" #include "Utility/OfflineObjectRenderer.h" #include "Utility/WindowDragger.h" - -#include "Tabbar/SplitView.h" +#include "Canvas.h" +#include "Components/Buttons.h" +#include "TabComponent.h" #include "Utility/ObjectThemeManager.h" @@ -83,8 +84,6 @@ class PluginEditor : public AudioProcessorEditor void paintOverChildren(Graphics& g) override; void renderArea(NVGcontext* nvg, Rectangle area); - - void initialiseCanvasRenderer(); bool isActiveWindow() override; @@ -97,27 +96,16 @@ class PluginEditor : public AudioProcessorEditor void mouseDrag(MouseEvent const& e) override; void mouseDown(MouseEvent const& e) override; - void newProject(); - void openProject(); - void saveProject(std::function const& nestedCallback = []() {}); - void saveProjectAs(std::function const& nestedCallback = []() {}); - - void addTab(Canvas* cnv, int splitIdx = -1); - void closeTab(Canvas* cnv); void closeAllTabs( bool quitAfterComplete = false, Canvas* patchToExclude = nullptr, std::function afterComplete = []() {}); void quit(bool askToSave); + Array getCanvases(); Canvas* getCurrentCanvas(); - // Part of the ZoomableDragAndDropContainer, we give it the splitview - // so it can check if the drag image is over the entire splitview - // otherwise some objects inside the splitview will trigger a zoom - SplitView* getSplitView() override; - void modifierKeysChanged(ModifierKeys const& modifiers) override; - + void valueChanged(Value& v) override; void updateCommandStatus(); @@ -129,7 +117,9 @@ class PluginEditor : public AudioProcessorEditor void fileDragMove(StringArray const& files, int x, int y) override; void fileDragExit(StringArray const&) override; - void createNewWindow(TabBarButtonComponent* tabButton) override; + void createNewWindow(Canvas* cnv) override; + + TabComponent& getTabComponent() override; DragAndDropTarget* findNextDragAndDropTarget(Point screenPos) override; @@ -154,13 +144,10 @@ class PluginEditor : public AudioProcessorEditor Array openTextEditors; - TabComponent* getActiveTabbar(); - PluginProcessor* pd; std::unique_ptr connectionMessageDisplay; - OwnedArray canvases; std::unique_ptr sidebar; std::unique_ptr statusbar; @@ -172,10 +159,7 @@ class PluginEditor : public AudioProcessorEditor Value hvccMode; Value autoconnect; - - SplitView splitView; - DrawableRectangle selectedSplitRect; - + std::unique_ptr palettes; OfflineObjectRenderer offlineRenderer; @@ -199,6 +183,8 @@ class PluginEditor : public AudioProcessorEditor CheckedTooltip tooltipWindow; private: + + TabComponent tabComponent; std::unique_ptr touchSelectionHelper; // Used by standalone to handle dragging the window @@ -208,10 +194,9 @@ class PluginEditor : public AudioProcessorEditor MainToolbarButton mainMenuButton, undoButton, redoButton, addObjectMenuButton, pluginModeButton; ToolbarRadioButton editButton, runButton, presentButton; - - TextButton seperators[8]; + #if JUCE_MAC Rectangle unmaximisedSize; #endif diff --git a/Source/PluginMode.h b/Source/PluginMode.h index f23a532a12..6793e619a4 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -8,7 +8,6 @@ #include "PluginEditor.h" #include "Canvas.h" -#include "Tabbar/Tabbar.h" #include "Standalone/PlugDataWindow.h" @@ -187,9 +186,7 @@ class PluginMode : public Component, public NVGComponent { editor->setBounds(windowBounds); } - if (auto* tabbar = editor->getActiveTabbar()) { - tabbar->resized(); - } + editor->getTabComponent().resized(); if (originalCanvas) { // Reset the canvas properties to before plugin mode was entered diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 78503e686a..160f9ff5b1 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -27,7 +27,6 @@ #include "PluginMode.h" #include "PluginEditor.h" #include "LookAndFeel.h" -#include "Tabbar/Tabbar.h" #include "Object.h" #include "Statusbar.h" @@ -995,12 +994,7 @@ AudioProcessorEditor* PluginProcessor::createEditor() if (ProjectInfo::isStandalone) { openedEditors.add(editor); } - - for (auto const& patch : patches) { - auto* cnv = editor->canvases.add(new Canvas(editor, *patch, nullptr)); - editor->addTab(cnv, patch->splitViewIndex); - } - + editor->resized(); return editor; } @@ -1020,8 +1014,6 @@ void PluginProcessor::getStateInformation(MemoryBlock& destData) { setThis(); - savePatchTabPositions(); - // Store pure-data and parameter state MemoryOutputStream ostream(destData, false); @@ -1112,10 +1104,7 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) // Close any opened patches MessageManager::callAsync([this]() { for (auto* editor : getEditors()) { - for (auto split : editor->splitView.splits) { - split->getTabComponent()->clearTabs(); - } - editor->canvases.clear(); + editor->getTabComponent().closeAllTabs(); } }); } @@ -1162,23 +1151,41 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) // This makes sure the patch can find abstractions/resources, even though it's loading patch from state glob_forcefilename(generateSymbol(location.getFileName().toRawUTF8()), generateSymbol(location.getParentDirectory().getFullPathName().toRawUTF8())); - auto patch = loadPatch(content, getEditors()[0], splitIndex); - if (patch && ((location.exists() && location.getParentDirectory() == File::getSpecialLocation(File::tempDirectory)) || !location.exists())) { - patch->setTitle("Untitled Patcher"); - patch->openInPluginMode = pluginMode; - patch->splitViewIndex = splitIndex; - } else if (patch && location.existsAsFile()) { - patch->setCurrentFile(URL(location)); - patch->setTitle(location.getFileName()); - patch->openInPluginMode = pluginMode; - patch->splitViewIndex = splitIndex; + if(auto* editor = dynamic_cast(getActiveEditor())) + { + auto* cnv = editor->getTabComponent().openPatch(content); + cnv->patch.splitViewIndex = splitIndex; + cnv->patch.openInPluginMode = pluginMode; + if(!location.exists() || (location.exists() && location.getParentDirectory() == File::getSpecialLocation(File::tempDirectory))) + { + cnv->patch.setUntitled(); + } + else + { + cnv->patch.setTitle(location.getFileName()); + } + } + else { + loadPatch(content, nullptr); } } - else if (location.getFullPathName().isNotEmpty() && location.existsAsFile()) { - auto patch = loadPatch(URL(location), getEditors()[0], splitIndex); - if (patch) { - patch->setTitle(location.getFileName()); - patch->openInPluginMode = pluginMode; + else { + if(auto* editor = dynamic_cast(getActiveEditor())) + { + auto* cnv = editor->getTabComponent().openPatch(URL(location)); + cnv->patch.splitViewIndex = splitIndex; + cnv->patch.openInPluginMode = pluginMode; + if(!location.exists() || (location.exists() && location.getParentDirectory() == File::getSpecialLocation(File::tempDirectory))) + { + cnv->patch.setUntitled(); + } + else + { + cnv->patch.setTitle(location.getFileName()); + } + } + else { + loadPatch(URL(location), nullptr); } } }; @@ -1254,6 +1261,7 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) delete[] xmlData; + // TODO: ugly, clean this up MessageManager::callAsync([this]() { for (auto* editor : getEditors()) { editor->sidebar->updateAutomationParameters(); @@ -1282,25 +1290,6 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) pd::Patch::Ptr PluginProcessor::loadPatch(URL const& patchURL, PluginEditor* editor, int splitIndex) { auto patchFile = patchURL.getLocalFile(); - // First, check if patch is already opened - for (auto const& patch : patches) { - if (patch->getCurrentFile() == patchFile) { - MessageManager::callAsync([this, patch]() mutable { - for (auto* editor : getEditors()) { - for (auto* cnv : editor->canvases) { - if (cnv->patch == *patch) { - cnv->getTabbar()->setCurrentTabIndex(cnv->getTabIndex()); - } - } - editor->pd->logError("Patch is already open"); - } - }); - - // Patch is already opened - return nullptr; - } - } - // Stop the audio callback when loading a new patch // TODO: why though? lockAudioThread(); @@ -1337,22 +1326,6 @@ pd::Patch::Ptr PluginProcessor::loadPatch(URL const& patchURL, PluginEditor* edi patches.add(newPatch); auto* patch = patches.getLast().get(); - - if (editor) { - MessageManager::callAsync([this, patch, splitIndex, _editor = Component::SafePointer(editor)]() mutable { - if (!_editor) - return; - // There are some subroutines that get called when we create a canvas, that will lock the audio thread - // By locking it around this whole function, we can prevent slowdowns from constantly locking/unlocking the audio thread - lockAudioThread(); - - auto* cnv = _editor->canvases.add(new Canvas(_editor.getComponent(), *patch, nullptr)); - - unlockAudioThread(); - - _editor->addTab(cnv, splitIndex); - }); - } patch->setCurrentFile(URL(patchFile)); return patch; @@ -1546,7 +1519,7 @@ void PluginProcessor::receiveSysMessage(String const& selector, std::vectorcanvases) + for(auto* canvas : editors[0]->getCanvases()) { if(patches[0] == canvas->patch) { @@ -1916,7 +1889,7 @@ void PluginProcessor::reloadAbstractions(File changedPatch, t_glist* except) // Synchronising can potentially delete some other canvases, so make sure we use a safepointer Array> canvases; - for (auto* canvas : editor->canvases) { + for (auto* canvas : editor->getCanvases()) { canvases.add(canvas); } @@ -1936,51 +1909,8 @@ void PluginProcessor::reloadAbstractions(File changedPatch, t_glist* except) void PluginProcessor::titleChanged() { for (auto* editor : getEditors()) { - for (auto split : editor->splitView.splits) { - auto tabbar = split->getTabComponent(); - for (int n = 0; n < tabbar->getNumTabs(); n++) { - auto* cnv = tabbar->getCanvas(n); - if (!cnv) - return; - - tabbar->setTabText(n, cnv->patch.getTitle() + String(cnv->patch.isDirty() ? "*" : "")); - } - } - } -} - -void PluginProcessor::savePatchTabPositions() -{ - Array> sortedPatches; - // TODO: make multi-window friendly - if (auto* editor = dynamic_cast(getActiveEditor())) { - for (auto* cnv : editor->canvases) { - cnv->patch.splitViewIndex = editor->splitView.getTabComponentSplitIndex(cnv->getTabbar()); - sortedPatches.add({ &cnv->patch, cnv->getTabIndex() }); - } - } - - std::sort(sortedPatches.begin(), sortedPatches.end(), [](auto const& a, auto const& b) { - auto& [patchA, idxA] = a; - auto& [patchB, idxB] = b; - - if (patchA->splitViewIndex == patchB->splitViewIndex) - return idxA < idxB; - - return patchA->splitViewIndex < patchB->splitViewIndex; - }); - - patches.getLock().enter(); - int i = 0; - for (auto& [patch, tabIdx] : sortedPatches) { - - if (i >= patches.size()) - break; - - patches.set(i, patch); - i++; + editor->getTabComponent().update(); } - patches.getLock().exit(); } // This creates new instances of the plugin.. diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 4ca5c709ad..9d3af33242 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -100,7 +100,6 @@ class PluginProcessor : public AudioProcessor, public AsyncUpdater return nbus > 0; } - void savePatchTabPositions(); void updatePatchUndoRedoState(); void settingsFileReloaded() override; @@ -128,8 +127,8 @@ class PluginProcessor : public AudioProcessor, public AsyncUpdater void parseDataBuffer(XmlElement const& xml) override; std::unique_ptr extraData; - pd::Patch::Ptr loadPatch(String patch, PluginEditor* editor, int splitIndex = -1); - pd::Patch::Ptr loadPatch(URL const& patchURL, PluginEditor* editor, int splitIndex = -1); + pd::Patch::Ptr loadPatch(String patch, PluginEditor* editor, int splitIndex = 0); + pd::Patch::Ptr loadPatch(URL const& patchURL, PluginEditor* editor, int splitIndex = 0); void titleChanged() override; @@ -139,9 +138,6 @@ class PluginProcessor : public AudioProcessor, public AsyncUpdater Colour getBackgroundColour() override; Colour getTextColour() override; Colour getOutlineColour() override; - - // All opened patches - Array patches; int lastUIWidth = 1000, lastUIHeight = 650; diff --git a/Source/Sidebar/DocumentationBrowser.h b/Source/Sidebar/DocumentationBrowser.h index 2e632fbef3..8c35d8529d 100644 --- a/Source/Sidebar/DocumentationBrowser.h +++ b/Source/Sidebar/DocumentationBrowser.h @@ -100,7 +100,8 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri fileList.onClick = [this](ValueTree& tree){ auto file = File(tree.getProperty("Path").toString()); if (file.existsAsFile() && file.hasFileExtension("pd")) { - pd->loadPatch(URL(file), findParentComponentOfClass()); + auto* editor = findParentComponentOfClass(); + editor->getTabComponent().openPatch(URL(file)); SettingsFile::getInstance()->addToRecentlyOpened(file); } else if(file.isDirectory()) diff --git a/Source/Sidebar/Palettes.h b/Source/Sidebar/Palettes.h index ae0d5ff55c..4de046f76a 100644 --- a/Source/Sidebar/Palettes.h +++ b/Source/Sidebar/Palettes.h @@ -19,7 +19,7 @@ #include "PluginEditor.h" #include "Sidebar/PaletteItem.h" #include "Utility/OfflineObjectRenderer.h" -#include "Components/ZoomableDragAndDropContainer.h" +#include "Utility/ZoomableDragAndDropContainer.h" #include "Components/Buttons.h" class AddItemButton : public Component { diff --git a/Source/Standalone/PlugDataApp.cpp b/Source/Standalone/PlugDataApp.cpp index edfc6012b8..266df3a581 100644 --- a/Source/Standalone/PlugDataApp.cpp +++ b/Source/Standalone/PlugDataApp.cpp @@ -90,7 +90,7 @@ class PlugDataApp : public JUCEApplication { auto* editor = dynamic_cast(mainWindow->mainComponent->getEditor()); if (pd && editor && file.existsAsFile()) { auto* editor = dynamic_cast(mainWindow->mainComponent->getEditor()); - pd->loadPatch(URL(file), editor); + editor->getTabComponent().openPatch(URL(file)); SettingsFile::getInstance()->addToRecentlyOpened(file); } } @@ -185,7 +185,7 @@ class PlugDataApp : public JUCEApplication { auto* editor = dynamic_cast(mainWindow->mainComponent->getEditor()); if (auto* pd = dynamic_cast(pluginHolder->processor.get())) { - pd->loadPatch(URL(toOpen), editor); + editor->getTabComponent().openPatch(URL(toOpen)); SettingsFile::getInstance()->addToRecentlyOpened(toOpen); openedPatches.add(toOpen.getFullPathName()); } @@ -205,7 +205,7 @@ class PlugDataApp : public JUCEApplication { if (toOpen.existsAsFile() && toOpen.hasFileExtension("pd") && !openedPatches.contains(toOpen.getFullPathName())) { auto* pd = dynamic_cast(pluginHolder->processor.get()); auto* editor = dynamic_cast(mainWindow->mainComponent->getEditor()); - pd->loadPatch(URL(toOpen), editor); + editor->tabComponent.openPatch(URL(toOpen)); SettingsFile::getInstance()->addToRecentlyOpened(toOpen); } } diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp new file mode 100644 index 0000000000..09c98bfb3a --- /dev/null +++ b/Source/TabComponent.cpp @@ -0,0 +1,651 @@ +#include +#include "PluginEditor.h" +#include "PluginProcessor.h" +#include "Canvas.h" +#include "Sidebar/Sidebar.h" +#include "Dialogs/Dialogs.h" +#include "Utility/Autosave.h" +#include "Components/ObjectDragAndDrop.h" +#include "NVGSurface.h" + +TabComponent::TabComponent(PluginEditor* editor) : editor(editor), pd(editor->pd) +{ + for(int i = 0; i < tabbars.size(); i++) + { + addChildComponent(newTabButtons[i]); + newTabButtons[i].onClick = [this, i](){ + activeSplitIndex = i; + newPatch(); + }; + } + + addMouseListener(this, true); +} + +Canvas* TabComponent::newPatch() +{ + return openPatch(pd::Instance::defaultPatch); +} + +Canvas* TabComponent::openPatch(const URL& path) +{ + auto patchFile = path.getLocalFile(); + for (auto* cnv : canvases) { + if (cnv->patch.getCurrentFile() == patchFile) { + pd->logError("Patch is already open"); + showTab(cnv); + return cnv; + } + } + + auto patch = pd->loadPatch(path, editor); + return openPatch(patch); +} + +Canvas* TabComponent::openPatch(const String& patchContent) +{ + auto patch = pd->loadPatch(patchContent, editor); + patch->setUntitled(); + return openPatch(patch); +} + +Canvas* TabComponent::openPatch(pd::Patch::Ptr existingPatch) +{ + // Check if subpatch is already opened + for (auto* cnv : canvases) { + if (cnv->patch == *existingPatch) { + pd->logError("Patch is already open"); + showTab(cnv); + return cnv; + } + } + + pd->patches.addIfNotAlreadyThere(existingPatch); + auto* cnv = canvases.add(new Canvas(editor, existingPatch)); + + auto patchTitle = existingPatch->getTitle(); + // Open help files and references in Locked Mode + if (patchTitle.contains("-help") || patchTitle.equalsIgnoreCase("reference")) + cnv->locked.setValue(true); + + existingPatch->splitViewIndex = activeSplitIndex; + + update(); + pd->titleChanged(); + + showTab(cnv, activeSplitIndex); + closeEmptySplits(); + + cnv->jumpToOrigin(); + + return cnv; +} + +void TabComponent::openPatch() +{ + Dialogs::showOpenDialog([this](URL resultURL) { + auto result = resultURL.getLocalFile(); + if (result.exists() && result.getFileExtension().equalsIgnoreCase(".pd")) { + editor->autosave->checkForMoreRecentAutosave(result, [this, result, resultURL]() { + openPatch(resultURL); + SettingsFile::getInstance()->addToRecentlyOpened(result); + }); + } + }, + true, false, "*.pd", "Patch", this); +} + +void TabComponent::nextTab() { + + auto splitIndex = activeSplitIndex && splits[1]; + auto& tabbar = tabbars[splitIndex]; + auto oldTabIndex = 0; + for(int i = 0; i < tabbar.size(); i++) + { + if(tabbar[i]->cnv == splits[splitIndex]) + { + oldTabIndex = i; + } + } + + auto newTabIndex = oldTabIndex + 1; + if(newTabIndex < tabbar.size()) + { + showTab(tabbar[newTabIndex]->cnv); + } + else { + showTab(tabbar[0]->cnv); + } +} + +void TabComponent::previousTab() { + auto splitIndex = activeSplitIndex && splits[1]; + auto& tabbar = tabbars[splitIndex]; + auto oldTabIndex = 0; + for(int i = 0; i < tabbar.size(); i++) + { + if(tabbar[i]->cnv == splits[splitIndex]) + { + oldTabIndex = i; + } + } + + auto newTabIndex = oldTabIndex - 1; + if(newTabIndex >= 0) + { + showTab(tabbar[newTabIndex]->cnv); + } + else { + showTab(tabbar[tabbar.size() - 1]->cnv); + } +} + + +void TabComponent::update() +{ + tabbars[0].clear(); + tabbars[1].clear(); + + // Load all patches from pd patch array + for(auto& patch : pd->patches) + { + Canvas* cnv = nullptr; + for(auto* canvas : canvases) + { + if(canvas->patch == *patch) + { + cnv = canvas; + } + } + + if(!cnv) { + cnv = canvases.add(new Canvas(editor, patch)); + cnv->jumpToOrigin(); + } + + // Create tab buttons + auto* newTabButton = new TabBarButtonComponent(cnv, this); + tabbars[patch->splitViewIndex == 1].add(newTabButton); + addAndMakeVisible(newTabButton); + } + + closeEmptySplits(); + + resized(); // Update tab and canvas layout +} + +void TabComponent::closeEmptySplits() +{ + if(!tabbars[0].size() && tabbars[1].size()) // Check if split can be closed + { + // Move all tabs to left split + for(int i = tabbars[1].size() - 1; i >= 0; i--) + { + tabbars[1][i]->cnv->patch.splitViewIndex = 0; // Save split index + tabbars[0].insert(0, tabbars[1].removeAndReturn(i)); // Move to other split + } + + showTab(tabbars[0][0]->cnv, 0); + } + if(tabbars[0].size() && !splits[0]) { + showTab(tabbars[0][0]->cnv, 0); + } + if(tabbars[1].size() && !splits[1]) + { + showTab(tabbars[1][0]->cnv, 1); + } + if(!tabbars[1].size() && splits[1]) // Check if right split is valid + { + showTab(nullptr, 1); + } + if(!tabbars[0].size() && splits[0]) // Check if left split is valid + { + showTab(nullptr, 0); + } + // Check for tabs that are shown inside the wrong split, dragging tabs can cause that + for(int i = 0; i < tabbars.size(); i++) + { + for(auto* tab : tabbars[i]) + { + if(tab->cnv == splits[!i] && tabbars[!i].size()) + { + showTab(tabbars[!i][0]->cnv, !i); + break; + } + } + } +} + +void TabComponent::showTab(Canvas* cnv, int splitIndex) +{ + if(splits[splitIndex]) removeChildComponent(splits[splitIndex]->viewport.get()); + + splits[splitIndex] = cnv; + + if(cnv) { + addAndMakeVisible(cnv->viewport.get()); + cnv->setVisible(true); + cnv->grabKeyboardFocus(); + cnv->patch.splitViewIndex = splitIndex; + activeSplitIndex = splitIndex; + } + + resized(); + repaint(); + + editor->nvgSurface.invalidateAll(); +} + +Canvas* TabComponent::getCurrentCanvas() +{ + return activeSplitIndex && splits[1] ? splits[1] : splits[0]; +} + +Array TabComponent::getCanvases() +{ + Array canvas; + canvas.addArray(canvases); + return canvas; +} + +void TabComponent::renderArea(NVGcontext* nvg, Rectangle area) +{ + if(splits[0]) + { + nvgSave(nvg); + nvgScissor(nvg, 0, 0, splits[1] ? (splitSize - 3) : getWidth(), getHeight()); + splits[0]->performRender(nvg, area); + nvgRestore(nvg); + } + if(splits[1]) + { + nvgSave(nvg); + nvgTranslate(nvg, splitSize + 3, 0); + nvgScissor(nvg, 0, 0, getWidth() - (splitSize + 3), getHeight()); + + splits[1]->performRender(nvg, area.translated(-(splitSize + 3), 0)); + nvgRestore(nvg); + } + + if(!splitDropBounds.isEmpty()) + { + nvgBeginPath(nvg); + nvgRect(nvg, splitDropBounds.getX(), splitDropBounds.getY(), splitDropBounds.getWidth(), splitDropBounds.getHeight()); + nvgFillColor(nvg, NVGComponent::convertColour(findColour(PlugDataColour::dataColourId).withAlpha(0.1f))); + nvgFill(nvg); + } + + if(splits[1]) + { + nvgBeginPath(nvg); + nvgRect(nvg, splitSize - 3, 0, 6, getHeight()); + nvgFillColor(nvg, NVGComponent::convertColour(findColour(PlugDataColour::canvasBackgroundColourId))); + nvgFill(nvg); + + auto activeSplitBounds = splits[activeSplitIndex]->viewport->getBounds().translated(0, -30); + nvgBeginPath(nvg); + nvgRect(nvg, activeSplitBounds.getX(), activeSplitBounds.getY(), activeSplitBounds.getWidth(), activeSplitBounds.getHeight()); + nvgStrokeWidth(nvg, 3.0f); + nvgStrokeColor(nvg, NVGComponent::convertColour(findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.25f))); + nvgStroke(nvg); + } +} + +void TabComponent::mouseDown(const MouseEvent& e) +{ + if(e.eventTime.getMillisecondCounter() == lastMouseTime) return; + lastMouseTime = e.eventTime.getMillisecondCounter(); + + auto localPos = e.getEventRelativeTo(this).getPosition(); + if(localPos.x > splitSize - 3 && localPos.x < splitSize + 3) + { + draggingSplitResizer = true; + setMouseCursor(MouseCursor::LeftRightResizeCursor); + } + else if(splits[1] && localPos.x > splitSize) + { + setActiveSplit(splits[1]); + } + else { + setActiveSplit(splits[0]); + } +} + +void TabComponent::mouseUp(const MouseEvent& e) +{ + draggingSplitResizer = false; + e.eventComponent->setMouseCursor(MouseCursor::NormalCursor); +} + +void TabComponent::mouseDrag(const MouseEvent& e) +{ + if(e.eventTime.getMillisecondCounter() == lastMouseTime) return; + lastMouseTime = e.eventTime.getMillisecondCounter(); + + auto localPos = e.getEventRelativeTo(this).getPosition(); + if(draggingSplitResizer) { + splitSize = localPos.x; + resized(); + } +} + +void TabComponent::mouseMove(const MouseEvent& e) +{ + if(e.eventTime.getMillisecondCounter() == lastMouseTime) return; + lastMouseTime = e.eventTime.getMillisecondCounter(); + + auto localPos = e.getEventRelativeTo(this).getPosition(); + if(localPos.x > splitSize - 3 && localPos.x < splitSize + 3) + { + e.eventComponent->setMouseCursor(MouseCursor::LeftRightResizeCursor); + } + else { + e.eventComponent->setMouseCursor(MouseCursor::NormalCursor); + } +} + + +void TabComponent::parentSizeChanged() +{ + // TODO: keep split proportion? + splitSize = getWidth() / 2; +} + +void TabComponent::resized() +{ + auto isSplit = splits[1] != nullptr; + auto bounds = getLocalBounds(); + auto tabbarBounds = bounds.removeFromTop(30); + auto& animator = Desktop::getInstance().getAnimator(); + + for(int i = 0; i < tabbars.size(); i++) + { + auto& tabButtons = tabbars[i]; + auto splitBounds = tabbarBounds.removeFromLeft((isSplit && i == 0) ? splitSize : getWidth()); + newTabButtons[i].setBounds(splitBounds.removeFromLeft(30)); + auto tabWidth = splitBounds.getWidth() / std::max(1, tabButtons.size()); + + for(auto* tabButton : tabButtons) + { + auto targetBounds = splitBounds.removeFromLeft(tabWidth); + if(tabButton->isDragging) { + tabButton->setSize(tabWidth, 30); + if(splits[1]) { + tabButton->dragger.updateMouseDownPosition(tabButton->getLocalBounds().getCentre()); + } + continue; // We reserve space for it, but don't set the bounds to create a ghost tab + } + + if(draggingOverTabbar) + { + animator.animateComponent(tabButton, targetBounds, 1.0f, 200, false, 3.0, 0.0); + } + else { + tabButton->setBounds(targetBounds); + } + } + } + + for(auto& split : splits) + { + if(split) + { + split->viewport->setBounds(bounds.removeFromLeft((isSplit && split == splits[0]) ? (splitSize - 3) : getWidth())); + bounds.removeFromLeft(6); // For split resizer + } + } + + newTabButtons[0].setVisible(!tabbars[0].isEmpty()); + newTabButtons[1].setVisible(!tabbars[1].isEmpty()); +} + +void TabComponent::closeTab(Canvas* cnv) { + auto patch = cnv->refCountedPatch; + + editor->sidebar->hideParameters(); + editor->sidebar->clearSearchOutliner(); + + patch->setVisible(false); + + cnv->setCachedComponentImage(nullptr); // Clear nanovg invalidation listener, just to be sure + canvases.removeObject(cnv); + + pd->patches.removeFirstMatchingValue(patch); + pd->updateObjectImplementations(); + + update(); +} + + + +void TabComponent::closeAllTabs() { + editor->closeAllTabs(); +} + +void TabComponent::setActiveSplit(Canvas* cnv) { + if(cnv != splits[activeSplitIndex]) { + activeSplitIndex = cnv == splits[1] ? 1 : 0; + editor->nvgSurface.invalidateAll(); + } +} + +Canvas* TabComponent::getCanvasAtScreenPosition(Point screenPosition) +{ + auto x = getLocalPoint(nullptr, screenPosition).x; + auto split = x < splitSize || !splits[1]; + return split ? splits[0] : splits[1]; + // TODO: return nullptr if OOB +} + +Array TabComponent::getVisibleCanvases() +{ + Array result; + if(splits[0]) result.add(splits[0]); + if(splits[1]) result.add(splits[1]); + return result; +} + + +bool TabComponent::isInterestedInDragSource(SourceDetails const& dragSourceDetails) +{ + if (dynamic_cast(dragSourceDetails.sourceComponent.get())) + { + return true; + } + + if(dynamic_cast(dragSourceDetails.sourceComponent.get())) + { + return true; + } + + return false; +} + +void TabComponent::itemDropped(SourceDetails const& dragSourceDetails) +{ + if(auto* objectDnD = dynamic_cast(dragSourceDetails.sourceComponent.get())) + { + auto screenPosition = localPointToGlobal(dragSourceDetails.localPosition); + + auto* cnv = getCanvasAtScreenPosition(screenPosition); + if (!cnv) + return; + + auto mousePos = (cnv->getLocalPoint(this, dragSourceDetails.localPosition) - cnv->canvasOrigin); + + // Extract the array from the var + auto patchWithSize = *dragSourceDetails.description.getArray(); + auto patchSize = Point(patchWithSize[0], patchWithSize[1]); + auto patchData = patchWithSize[2].toString(); + auto patchName = patchWithSize[3].toString(); + + cnv->dragAndDropPaste(patchData, mousePos, patchSize.x, patchSize.y, patchName); + setActiveSplit(cnv); + return; + } + + auto* tab = dynamic_cast(dragSourceDetails.sourceComponent.get()); + + auto showCanvas = [this](Canvas* cnv, int splitIndex){ + if(splits[splitIndex]) removeChildComponent(splits[splitIndex]->viewport.get()); + splits[splitIndex] = cnv; + if(cnv) { + addAndMakeVisible(cnv->viewport.get()); + cnv->setVisible(true); + cnv->grabKeyboardFocus(); + cnv->patch.splitViewIndex = splitIndex; // Save split index + activeSplitIndex = splitIndex; + editor->nvgSurface.invalidateAll(); + } + }; + + if(getLocalBounds().removeFromRight(getWidth() - splitSize).contains(dragSourceDetails.localPosition)) // Dragging to right split + { + if(tabbars[0].size() && splits[0] && tabbars[0].indexOf(tab) >= 0 && (dragSourceDetails.localPosition.y > 30 || splits[1])) + { + tabbars[1].add(tabbars[0].removeAndReturn(tabbars[0].indexOf(tab))); // Move tab to right tabbar + if(tabbars[0].size()) showCanvas(tabbars[0][0]->cnv, 0); // Show first tab of left tabbar + showCanvas(tab->cnv, 1); // Show the moved tab on right tabbar + resized(); + } + } + else { + if(tabbars[1].size() && splits[1] && tabbars[1].indexOf(tab) >= 0) { + tabbars[0].add(tabbars[1].removeAndReturn(tabbars[1].indexOf(tab))); // Move tab to left tabbar + if(tabbars[1].size()) showCanvas(tabbars[1][0]->cnv, 1); // Show first tab of right tabbar, if there are any tabs left + else showCanvas(nullptr, 1); // If no tabs are left on right tabbar + + showCanvas(tab->cnv, 0); // Show moved tab in left tabbar + resized(); + } + else if(tabbars[0].size() > 1 && splits[0] && !splits[1] && tabbars[0].indexOf(tab) >= 0 && (dragSourceDetails.localPosition.y > 30)) // If we try to create a left splits when there are no splits open + { + showCanvas(tab->cnv, 0); // Show dragged tab on left split + + // Move all other tabs to right split + for(int i = tabbars[0].size() - 1; i >= 0; i--) + { + if(tab != tabbars[0][i]) { + tabbars[0][i]->cnv->patch.splitViewIndex = 1; // Save split index + tabbars[1].insert(0, tabbars[0].removeAndReturn(i)); // Move to other split + } + } + + showCanvas(tabbars[1][0]->cnv, 1); // Show first tab of right split + resized(); + } + } + + closeEmptySplits(); + saveTabPositions(); + + draggingOverTabbar = false; + splitDropBounds = Rectangle(); + editor->nvgSurface.invalidateAll(); +} + +void TabComponent::itemDragEnter(SourceDetails const& dragSourceDetails) +{ + itemDragMove(dragSourceDetails); +} + +void TabComponent::itemDragExit(SourceDetails const& dragSourceDetails) +{ + auto* tab = dynamic_cast(dragSourceDetails.sourceComponent.get()); + tab->setVisible(false); + splitDropBounds = Rectangle(); + draggingOverTabbar = false; +} + +void TabComponent::saveTabPositions() +{ + Array> sortedPatches; + for(int i = 0; i < tabbars.size(); i++) { + for(int j = 0; j < tabbars[i].size(); j++) + { + if(auto* cnv = tabbars[i][j]->cnv.getComponent()) { + cnv->patch.splitViewIndex = i; + sortedPatches.add({ cnv->refCountedPatch, j }); + } + } + } + + std::sort(sortedPatches.begin(), sortedPatches.end(), [](auto const& a, auto const& b) { + auto& [patchA, idxA] = a; + auto& [patchB, idxB] = b; + + if (patchA->splitViewIndex == patchB->splitViewIndex) + return idxA < idxB; + + return patchA->splitViewIndex < patchB->splitViewIndex; + }); + + pd->patches.getLock().enter(); + int i = 0; + for (auto& [patch, tabIdx] : sortedPatches) { + + if (i >= pd->patches.size()) + break; + + pd->patches.set(i, patch); + i++; + } + pd->patches.getLock().exit(); +} + +void TabComponent::itemDragMove(SourceDetails const& dragSourceDetails) +{ + auto* tab = dynamic_cast(dragSourceDetails.sourceComponent.get()); + if(!tab) return; + + Rectangle oldSplitDropBounds = splitDropBounds; + + if(getLocalBounds().removeFromTop(30).contains(dragSourceDetails.localPosition)) // Dragging over tabbar + { + draggingOverTabbar = true; + splitDropBounds = Rectangle(); + tab->setVisible(true); + + if(tab->parent != this) + { + // TODO: split refactor, handle drag to other window! + } + + auto centreX = tab->getBounds().getCentreX(); + auto tabBarWidth = splits[1] ? getWidth() / 2 : getWidth(); + int hoveredSplit = splits[1] && centreX > splitSize; + + // Calculate target tab index based on tabbar width and tab centre position + auto targetTabPos = tabBarWidth / std::max(1, tabbars[hoveredSplit].size()); + auto tabPos = (centreX - (hoveredSplit ? tabBarWidth : 0)) / targetTabPos; + + auto oldPos = tabbars[hoveredSplit].indexOf(tab); + if(oldPos != tabPos) { + if(oldPos != -1) { // Dragging inside same tabbar + tabbars[hoveredSplit].move(oldPos, tabPos); + resized(); + } + else if(splits[1]) { // Dragging to another tabbar + tabbars[hoveredSplit].insert(tabPos, tabbars[!hoveredSplit].removeAndReturn(tabbars[!hoveredSplit].indexOf(tab))); + resized(); + } + } + } + else if(getLocalBounds().removeFromRight(getWidth() - splitSize).contains(dragSourceDetails.localPosition)) // Dragging over right split + { + draggingOverTabbar = false; + splitDropBounds = getLocalBounds().removeFromRight(splitSize); + tab->setVisible(false); + } + else { // Dragging over left split + draggingOverTabbar = false; + splitDropBounds = getLocalBounds().removeFromLeft(splitSize); + tab->setVisible(false); + } + + if(splitDropBounds != oldSplitDropBounds) + { + // Repaint for updated split drop bounds + editor->nvgSurface.invalidateAll(); + } +} diff --git a/Source/TabComponent.h b/Source/TabComponent.h new file mode 100644 index 0000000000..0589c189c5 --- /dev/null +++ b/Source/TabComponent.h @@ -0,0 +1,243 @@ +#pragma once + +#include "Utility/ZoomableDragAndDropContainer.h" + +class TabBarButtonComponent; +class TabComponent : public Component, public DragAndDropTarget +{ +public: + TabComponent(PluginEditor* editor); + + Canvas* newPatch(); + + Canvas* openPatch(const URL& path); + Canvas* openPatch(const String& patchContent); + Canvas* openPatch(pd::Patch::Ptr existingPatch); + void openPatch(); + + void update(); + + void renderArea(NVGcontext* nvg, Rectangle bounds); + + void nextTab(); + void previousTab(); + + void closeTab(Canvas* cnv); + void showTab(Canvas* cnv, int splitIndex = 0); + void setActiveSplit(Canvas* cnv); + + void closeAllTabs(); + + Canvas* getCurrentCanvas(); + Canvas* getCanvasAtScreenPosition(Point screenPosition); + + Array getCanvases(); + Array getVisibleCanvases(); + + void resized() override; + void parentSizeChanged() override; + +private: + + void saveTabPositions(); + void closeEmptySplits(); + + bool isInterestedInDragSource(SourceDetails const& dragSourceDetails) override; + void itemDropped(SourceDetails const& dragSourceDetails) override; + void itemDragEnter(SourceDetails const& dragSourceDetails) override; + void itemDragExit(SourceDetails const& dragSourceDetails) override; + void itemDragMove(SourceDetails const& dragSourceDetails) override; + + void mouseDown(const MouseEvent& e) override; + void mouseUp(const MouseEvent& e) override; + void mouseDrag(const MouseEvent& e) override; + void mouseMove(const MouseEvent& e) override; + + class TabBarButtonComponent : public Component + { + + struct TabDragConstrainer : public ComponentBoundsConstrainer + { + TabDragConstrainer(TabComponent* parent) : parent(parent) + { + + } + void checkBounds (Rectangle& bounds, const Rectangle&, const Rectangle& limits, bool, bool, bool, bool) override + { + bounds = bounds.withPosition(std::clamp(bounds.getX(), 30, parent->getWidth() - bounds.getWidth()), 0); + } + + TabComponent* parent; + }; + + class CloseTabButton : public SmallIconButton { + + using SmallIconButton::SmallIconButton; + + void paint(Graphics& g) override + { + auto font = Fonts::getIconFont().withHeight(12); + g.setFont(font); + + if (!isEnabled()) { + g.setColour(Colours::grey); + } else if (getToggleState()) { + g.setColour(findColour(PlugDataColour::toolbarActiveColourId)); + } else if (isMouseOver()) { + g.setColour(findColour(PlugDataColour::toolbarTextColourId).brighter(0.8f)); + } else { + g.setColour(findColour(PlugDataColour::toolbarTextColourId)); + } + + int const yIndent = jmin(4, proportionOfHeight(0.3f)); + int const cornerSize = jmin(getHeight(), getWidth()) / 2; + + int const fontHeight = roundToInt(font.getHeight() * 0.6f); + int const leftIndent = jmin(fontHeight, 2 + cornerSize / (isConnectedOnLeft() ? 4 : 2)); + int const rightIndent = jmin(fontHeight, 2 + cornerSize / (isConnectedOnRight() ? 4 : 2)); + int const textWidth = getWidth() - leftIndent - rightIndent; + + if (textWidth > 0) + g.drawFittedText(getButtonText(), leftIndent, yIndent, textWidth, getHeight() - yIndent * 2, Justification::centred, 2); + } + }; + + public: + TabBarButtonComponent(Canvas* cnv, TabComponent* parent) : cnv(cnv), parent(parent), tabDragConstrainer(parent) + { + closeButton.onClick = [cnv = SafePointer(cnv), parent](){ + if(cnv) parent->closeTab(cnv); + }; + closeButton.addMouseListener(this, false); + closeButton.setSize(28, 28); + addAndMakeVisible(closeButton); + setRepaintsOnMouseActivity(true); + } + + void paint(Graphics& g) override + { + auto mouseOver = isMouseOver(); + auto active = isActive(); + if (active) { + g.setColour(findColour(PlugDataColour::activeTabBackgroundColourId)); + } else if (mouseOver) { + g.setColour(findColour(PlugDataColour::activeTabBackgroundColourId).interpolatedWith(findColour(PlugDataColour::toolbarBackgroundColourId), 0.4f)); + } else { + g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); + } + + PlugDataLook::fillSmoothedRectangle(g, getLocalBounds().toFloat().reduced(4.5f), Corners::defaultCornerRadius); + + auto area = getLocalBounds().reduced(4, 1).toFloat(); + + // Use a gradient to make it fade out when it gets near to the close button + auto fadeX = (mouseOver || active) ? area.getRight() - 25 : area.getRight() - 8; + auto textColour = findColour(PlugDataColour::toolbarTextColourId); + g.setGradientFill(ColourGradient(textColour, fadeX - 18, area.getY(), Colours::transparentBlack, fadeX, area.getY(), false)); + + auto text = cnv->patch.getTitle(); + + g.setFont(Fonts::getCurrentFont().withHeight(14.0f)); + g.drawText(text, area.reduced(4, 0), Justification::centred, false); + } + + void resized() override + { + closeButton.setCentrePosition(getLocalBounds().getCentre().withX(getWidth() - 15).translated(0, 1)); + } + + ScaledImage generateTabBarButtonImage() + { + auto scale = 2.0f; + // we calculate the best size for the tab DnD image + auto text = cnv->patch.getTitle(); + Font font(Fonts::getCurrentFont()); + auto length = font.getStringWidth(text) + 32; + auto const boundsOffset = 10; + + // we need to expand the bounds, but reset the position to top left + // then we offset the mouse drag by the same amount + // this is to allow area for the shadow to render correctly + auto textBounds = Rectangle(0, 0, length, 28); + auto bounds = textBounds.expanded(boundsOffset).withZeroOrigin(); + auto image = Image(Image::PixelFormat::ARGB, bounds.getWidth() * scale, bounds.getHeight() * scale, true); + auto g = Graphics(image); + g.addTransform(AffineTransform::scale(scale)); + Path path; + path.addRoundedRectangle(bounds.reduced(10), 5.0f); + StackShadow::renderDropShadow(g, path, Colour(0, 0, 0).withAlpha(0.3f), 7, { 0, 1 }, scale); + g.setOpacity(1.0f); + + g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); + PlugDataLook::fillSmoothedRectangle(g, textBounds.withPosition(10, 10).toFloat(), Corners::defaultCornerRadius); + + g.setColour(findColour(PlugDataColour::toolbarTextColourId)); + + g.setFont(font); + g.drawText(text, textBounds.withPosition(10, 10), Justification::centred, false); + + return ScaledImage(image, scale); + } + + void mouseDown(const MouseEvent& e) override + { + toFront(false); + parent->showTab(cnv, parent->tabbars[1].contains(this)); + dragger.startDraggingComponent(this, e); + } + + void mouseDrag(const MouseEvent& e) override + { + if (e.getDistanceFromDragStart() > 10 && !isDragging) { + isDragging = true; + auto dragContainer = ZoomableDragAndDropContainer::findParentDragContainerFor(this); + + tabImage = generateTabBarButtonImage(); + dragContainer->startDragging(1, this, tabImage, tabImage, true, nullptr); + } + else if(parent->draggingOverTabbar) { + dragger.dragComponent(this, e, &tabDragConstrainer); + } + } + + void mouseUp(const MouseEvent& e) override + { + isDragging = false; + setVisible(true); + parent->resized(); // call resized so the dropped tab will animate into its correct position + } + + bool isActive() const + { + return cnv && (parent->splits[0] == cnv || parent->splits[1] == cnv); + } + + // close button, etc. + SafePointer cnv; + TabComponent* parent; + ScaledImage tabImage; + bool isDragging = false; + ComponentDragger dragger; + TabDragConstrainer tabDragConstrainer; + + CloseTabButton closeButton = CloseTabButton(Icons::Clear); + }; + + std::array newTabButtons = {MainToolbarButton(Icons::Add), MainToolbarButton(Icons::Add)}; + + std::array, 2> tabbars; + std::array, 2> splits = {nullptr, nullptr}; + + bool draggingOverTabbar = false; + bool draggingSplitResizer = false; + Rectangle splitDropBounds; + + int splitSize = 0; + int activeSplitIndex = 0; + uint32 lastMouseTime = 0; + + OwnedArray canvases; + + PluginEditor* editor; + PluginProcessor* pd; +}; diff --git a/Source/Tabbar/ResizableTabbedComponent.cpp b/Source/Tabbar/ResizableTabbedComponent.cpp deleted file mode 100644 index 8e6e28a027..0000000000 --- a/Source/Tabbar/ResizableTabbedComponent.cpp +++ /dev/null @@ -1,528 +0,0 @@ -/* - // Copyright (c) 2023 Alex Mitchell 2021-2023 Timothy Schoen - // For information on usage and redistribution, and for a DISCLAIMER OF ALL - // WARRANTIES, see the file, "LICENSE.txt," in this distribution. -*/ - -#include "ResizableTabbedComponent.h" -#include "PluginEditor.h" -#include "SplitViewResizer.h" -#include "PluginProcessor.h" -#include "Components/SearchEditor.h" -#include "Sidebar/Palettes.h" -#include "Sidebar/DocumentationBrowser.h" -#include "Tabbar.h" -#include "TabBarButtonComponent.h" - -#include "Components/ObjectDragAndDrop.h" - -#define ENABLE_SPLITS_DROPZONE_DEBUGGING 0 - -ResizableTabbedComponent::ResizableTabbedComponent(PluginEditor* editor, TabComponent* mainTabComponent) - : NVGComponent(this), editor(editor) -{ - if (mainTabComponent != nullptr) - tabComponent.reset(mainTabComponent); - else - tabComponent = std::make_unique(editor); - - addAndMakeVisible(*tabComponent); - - setInterceptsMouseClicks(true, true); - addMouseListener(this, true); -} - -ResizableTabbedComponent::~ResizableTabbedComponent() -{ - removeMouseListener(this); -} - -bool ResizableTabbedComponent::isInterestedInDragSource(SourceDetails const& dragSourceDetails) -{ - auto windowTab = dynamic_cast(dragSourceDetails.sourceComponent.get()); - auto draggedObject = dynamic_cast(dragSourceDetails.sourceComponent.get()); - - if (windowTab || draggedObject) - return true; - - return false; -} - -void ResizableTabbedComponent::itemDropped(SourceDetails const& dragSourceDetails) -{ - isDragAndDropOver = false; - editor->nvgSurface.triggerRepaint(); - - if (dynamic_cast(dragSourceDetails.sourceComponent.get())) { - switch (activeZone) { - case DropZones::Right: - splitMode = Split::SplitMode::Horizontal; - moveTabToNewSplit(dragSourceDetails); - break; - case DropZones::Left: - splitMode = Split::SplitMode::Horizontal; - moveTabToNewSplit(dragSourceDetails); - break; - case DropZones::Top: - moveTabToNewSplit(dragSourceDetails); - // splitMode = Split::SplitMode::Vertical; - break; - case DropZones::Bottom: - moveTabToNewSplit(dragSourceDetails); - // splitMode = Split::SplitMode::Vertical; - break; - case DropZones::TabBar: - splitMode = Split::SplitMode::None; - break; - case DropZones::Centre: - { - auto *sourceTabButton = dynamic_cast(dragSourceDetails.sourceComponent.get()); - auto tabCanvas = sourceTabButton->getTabComponent()->getCurrentCanvas(); - - if (sourceTabButton->getTabComponent()->getNumTabs() < 2) { - tabCanvas->editor->splitView.setFocus(this); - tabCanvas->editor->splitView.removeSplit(sourceTabButton->getTabComponent()); - tabCanvas->editor->splitView.resized(); - for (auto *split: editor->splitView.splits) { - split->setBoundsWithFactors(getParentComponent()->getLocalBounds()); - } - } - moveToSplit(this, tabCanvas); - getTabComponent()->setCanvasActive(tabCanvas); - break; - } - default: - break; - } - } else if (dynamic_cast(dragSourceDetails.sourceComponent.get())) { - if (!tabComponent) - return; - - auto cnv = tabComponent->getCurrentCanvas(); - if (!cnv) - return; - - auto mousePos = (cnv->getLocalPoint(this, dragSourceDetails.localPosition) - cnv->canvasOrigin); - - // Extract the array from the var - auto patchWithSize = *dragSourceDetails.description.getArray(); - auto patchSize = Point(patchWithSize[0], patchWithSize[1]); - auto patchData = patchWithSize[2].toString(); - auto patchName = patchWithSize[3].toString(); - - cnv->dragAndDropPaste(patchData, mousePos, patchSize.x, patchSize.y, patchName); - } -} - -void ResizableTabbedComponent::moveToSplit(ResizableTabbedComponent* targetSplit, Canvas* canvas) -{ - if (!targetSplit) { - createNewSplit(DropZones::Right, canvas); - } else { - if (auto* sourceTabBar = canvas->getTabbar()) { - auto sourceTabIndex = canvas->getTabIndex(); - sourceTabBar->removeTab(sourceTabIndex); - sourceTabBar->setCurrentTabIndex(sourceTabIndex > (sourceTabBar->getNumTabs() - 1) ? sourceTabIndex - 1 : sourceTabIndex); - } - - auto tabTitle = canvas->patch.getTitle(); - targetSplit->getTabComponent()->addTab(tabTitle, canvas->viewport.get(), 0); - canvas->viewport->setVisible(true); - - targetSplit->resized(); - targetSplit->getTabComponent()->resized(); - } -} -void ResizableTabbedComponent::createNewSplit(DropZones activeZone, Canvas* canvas) -{ - if (auto* sourceTabBar = canvas->getTabbar()) { - auto sourceTabIndex = canvas->getTabIndex(); - sourceTabBar->removeTab(sourceTabIndex); - sourceTabBar->setCurrentTabIndex(sourceTabIndex > (sourceTabBar->getNumTabs() - 1) ? sourceTabIndex - 1 : sourceTabIndex); - } - - auto* newSplit = new ResizableTabbedComponent(editor); - SplitViewResizer* resizer; - - // depending on if the dropzone is left or right we have to use the opposite resizer - if (activeZone == DropZones::Right) { - // connect resizers (if they exist) to the new split and / or replace the existing resizer of existing split - resizer = new SplitViewResizer(this, newSplit, Split::SplitMode::Horizontal, 1); - newSplit->resizerLeft = resizer; - newSplit->resizerRight = resizerRight; - - // if we have a right resizer, that means we are inserting a split between two existing splits - // so update the split that the right resizer has with the new split - if (resizerRight) { - resizerRight->splits[0] = newSplit; - } - resizerRight = resizer; - } else if (activeZone == DropZones::Left) { - resizer = new SplitViewResizer(this, newSplit, Split::SplitMode::Horizontal, 0); - newSplit->resizerRight = resizer; - newSplit->resizerLeft = resizerLeft; - - if (resizerLeft) { - resizerLeft->splits[1] = newSplit; - } - resizerLeft = resizer; - } else { - return; - } - - // update the bounds of the new and existing split using the resizer factors - newSplit->setBoundsWithFactors(getParentComponent()->getLocalBounds()); - setBoundsWithFactors(getParentComponent()->getLocalBounds()); - - // add the split to the splitview and make it active (both owned arrays of SplitView) - editor->splitView.addSplit(newSplit); - editor->splitView.addResizer(resizer); - - editor->pd->patches.add(canvas->patch); - auto newPatch = editor->pd->patches.getLast(); - auto* newCanvas = editor->canvases.add(new Canvas(editor, *newPatch, nullptr)); - - auto tabTitle = canvas->patch.getTitle(); - newSplit->getTabComponent()->addTab(tabTitle, newCanvas->viewport.get(), 0); - canvas->viewport->setVisible(true); - newCanvas->jumpToOrigin(); - - newSplit->resized(); - newSplit->getTabComponent()->resized(); -} - -void ResizableTabbedComponent::moveTabToNewSplit(SourceDetails const& dragSourceDetails) -{ - // get the dragging tab - auto* sourceTabButton = dynamic_cast(dragSourceDetails.sourceComponent.get()); - int sourceTabIndex = sourceTabButton->getIndex(); - auto* sourceTabContent = sourceTabButton->getTabComponent(); - int sourceNumTabs = sourceTabContent->getNumTabs(); - bool shouldDelete = (sourceNumTabs - 1) == 0; - bool dropZoneCentre = (activeZone == DropZones::Centre) ? true : false; - auto* tabCanvas = sourceTabContent->getCanvas(sourceTabIndex); - - if (dropZoneCentre && tabComponent.get() != sourceTabContent) { - auto tabTitle = tabCanvas->patch.getTitle(); - auto newTabIdx = tabComponent->getNumTabs(); - - if(tabCanvas->editor != editor) - { - editor->pd->patches.add(tabCanvas->patch); - auto newPatch = editor->pd->patches.getLast(); - auto* newCanvas = editor->canvases.add(new Canvas(editor, *newPatch, nullptr)); - editor->addTab(newCanvas); - newCanvas->jumpToOrigin(); - } - else { - tabComponent->addTab(tabTitle, sourceTabContent->getCanvas(sourceTabIndex)->viewport.get(), newTabIdx); - tabComponent->setCurrentTabIndex(newTabIdx); - } - - editor->splitView.setFocus(this); - - sourceTabContent->removeTab(sourceTabIndex); - sourceTabContent->setCurrentTabIndex(sourceTabIndex > (sourceTabContent->getNumTabs() - 1) ? sourceTabIndex - 1 : sourceTabIndex); - for (auto* split : editor->splitView.splits) { - split->setBoundsWithFactors(getParentComponent()->getLocalBounds()); - } - } else if (activeZone == DropZones::Left || activeZone == DropZones::Right) { - createNewSplit(static_cast(activeZone), sourceTabContent->getCanvas(sourceTabIndex)); - } - else - { - return; - } - - if (shouldDelete) { - tabCanvas->editor->splitView.setFocus(this); - tabCanvas->editor->splitView.removeSplit(sourceTabContent); - tabCanvas->editor->splitView.resized(); - for (auto* split : editor->splitView.splits) { - split->setBoundsWithFactors(getParentComponent()->getLocalBounds()); - } - } - - tabCanvas->editor->canvases.removeObject(tabCanvas); - - // set all current canvas viewports to visible, (if they already are this shouldn't do anything) - for (auto* split : editor->splitView.splits) { - if (auto tabComponent = split->getTabComponent()) { - auto tabIndex = tabComponent->getCurrentTabIndex(); - if (tabIndex < 0 && tabComponent->getNumTabs() > 0) { - tabComponent->setCurrentTabIndex(0); - } - if (auto cnv = tabComponent->getCanvas(tabIndex)) { - cnv->viewport->setVisible(true); - split->resized(); - split->getTabComponent()->resized(); - } - } - } - - editor->pd->savePatchTabPositions(); -} - -String ResizableTabbedComponent::getZoneName(int zone) -{ - // for console debugging - String zoneName; - switch (zone) { - case DropZones::Left: - zoneName = "left"; - break; - case DropZones::Top: - zoneName = "top"; - break; - case DropZones::Right: - zoneName = "right"; - break; - case DropZones::Bottom: - zoneName = "bottom"; - break; - case DropZones::Centre: - zoneName = "centre"; - break; - case DropZones::TabBar: - zoneName = "tab bar"; - break; - } - return zoneName; -} - -int ResizableTabbedComponent::findZoneFromSource(SourceDetails const& dragSourceDetails) -{ - for (auto const& [zone, dropZone] : dropZones) { - if (dropZone.contains(dragSourceDetails.localPosition.toFloat())) { - return zone; - } - } - return -1; -} - -void ResizableTabbedComponent::mouseDown(MouseEvent const& e) -{ - editor->splitView.setFocus(this); -} - -void ResizableTabbedComponent::setBoundsWithFactors(Rectangle bounds) -{ - if (resizerLeft) - leftFactor = resizerLeft->resizerPosition; - else - leftFactor = leftFactorDefault; - - if (resizerRight) - rightFactor = resizerRight->resizerPosition; - else - rightFactor = rightFactorDefault; - - if (resizerTop) - topFactor = resizerTop->resizerPosition; - else - topFactor = topFactorDefault; - - if (resizerBottom) - bottomFactor = resizerBottom->resizerPosition; - else - bottomFactor = bottomFactorDefault; - - auto leftPointX = bounds.getWidth() * leftFactor; - auto leftPointY = bounds.getHeight() * topFactor; - auto width = (bounds.getWidth() * rightFactor) - leftPointX; - auto height = (bounds.getHeight() * bottomFactor) - leftPointY; - - auto factorBounds = Rectangle(leftPointX, leftPointY, width, height); - setBounds(factorBounds.toNearestIntEdges()); -} - -void ResizableTabbedComponent::resized() -{ - auto bounds = getLocalBounds(); - if (oldBounds == bounds) - return; - - oldBounds = bounds; - - updateDropZones(); - - tabComponent->setBounds(getLocalBounds()); -} - -void ResizableTabbedComponent::render(NVGcontext* nvg) -{ - if (isDragAndDropOver) { - nvgBeginPath(nvg); - nvgFillColor(nvg, convertColour(findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.15f))); - - Rectangle highlight; - switch (activeZone) { - case DropZones::Left: - highlight = splitBoundsLeft; - break; - case DropZones::Top: - // highlight = splitBoundsTop; - if (editor->splitView.splits.size() > 1) { - highlight = splitBoundsFull; - } - break; - case DropZones::Right: - highlight = splitBoundsRight; - break; - case DropZones::Bottom: - // highlight = splitBoundsBottom; - if (editor->splitView.splits.size() > 1) { - highlight = splitBoundsFull; - } - break; - case DropZones::Centre: - case DropZones::TabBar: - if (editor->splitView.splits.size() > 1) { - highlight = getLocalBounds(); - } - break; - } - if(!highlight.isEmpty()) { - nvgRect(nvg, highlight.getX(), highlight.getY(), highlight.getWidth(), highlight.getHeight()); - nvgFill(nvg); - } - } -} - -/* -void ResizableTabbedComponent::paintOverChildren(Graphics& g) -{ -#if (ENABLE_SPLITS_DROPZONE_DEBUGGING == 1) - for (auto const& [zone, path] : dropZones) { - static Random rng; - uint8 R = rng.nextInt(255); - uint8 G = rng.nextInt(255); - uint8 B = rng.nextInt(255); - g.setColour(Colour(R, G, B, (uint8)0x50)); - g.fillPath(path); - } -#endif - - - - g.fillRect(highlight); - } -} */ - - -void ResizableTabbedComponent::itemDragEnter(SourceDetails const& dragSourceDetails) -{ - editor->splitView.setFocus(this); - // if we are dragging a tabbar, update the highlight split - if (dynamic_cast(dragSourceDetails.sourceComponent.get())) { - isDragAndDropOver = true; - editor->nvgSurface.triggerRepaint(); - } -} - -void ResizableTabbedComponent::itemDragExit(SourceDetails const& dragSourceDetails) -{ - if (dynamic_cast(dragSourceDetails.sourceComponent.get())) { - isDragAndDropOver = false; - editor->nvgSurface.triggerRepaint(); - } -} - -void ResizableTabbedComponent::itemDragMove(SourceDetails const& dragSourceDetails) -{ - // if we are dragging a tabbed window or from the document browser - if (auto sourceTabButton = dynamic_cast(dragSourceDetails.sourceComponent.get())) { - auto sourceTabContent = sourceTabButton->getTabComponent(); - int sourceNumTabs = sourceTabContent->getNumTabs(); - - auto zone = findZoneFromSource(dragSourceDetails); - - if (editor->splitView.canSplit() && sourceNumTabs > 1) { - if (activeZone != zone) { - activeZone = zone; - editor->nvgSurface.triggerRepaint(); - // std::cout << "dragging over: " << getZoneName(zone) << std::endl; - } - } else if (sourceTabButton->getTabComponent() != tabComponent.get()) { - auto foundZone = zone == DropZones::TabBar ? DropZones::None : DropZones::Centre; - if (activeZone != foundZone) { - activeZone = foundZone; - editor->nvgSurface.triggerRepaint(); - } - } else if (sourceTabButton->getTabComponent() == tabComponent.get()) { - if (activeZone != DropZones::None) { - activeZone = DropZones::None; - editor->nvgSurface.triggerRepaint(); - } - } - } -} - -void ResizableTabbedComponent::updateDropZones() -{ - auto objectBounds = getLocalBounds(); - - auto vHalf = objectBounds.getHeight() * 0.5f; - auto hHalf = objectBounds.getWidth() * 0.5f; - splitBoundsTop = objectBounds.withBottom(vHalf); - splitBoundsBottom = objectBounds.withTop(vHalf); - splitBoundsRight = objectBounds.withLeft(hHalf); - splitBoundsLeft = objectBounds.withRight(hHalf); - splitBoundsFull = objectBounds; - - Rectangle innerRect; - auto localBounds = objectBounds.toFloat(); - auto tabbarBounds = localBounds.removeFromTop(tabComponent->getTabBarDepth()); - auto canvasBounds = localBounds.withTop(tabComponent->getTabBarDepth()); - auto bounds = canvasBounds.reduced(canvasBounds.getWidth() * 0.25f, canvasBounds.getHeight() * 0.25f); - innerRect.setBounds(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); - - /* - DROP ZONE ARRANGEMENT - ┌─────────────────────────┠- │3 0│ - │ \ / │ - │ \ TOP / │ - │ \ / │ - │ ┌───────────────┠│ - │ │7 4│ │ - │ L │ TAB CENTRE │ R │ - │ │6 5│ │ - │ └───────────────┘ │ - │ / \ │ - │ / BOTTOM \ │ - │ / \ │ - │2 1│ - └─────────────────────────┘ - */ - - auto point_0 = canvasBounds.getTopRight(); - auto point_1 = canvasBounds.getBottomRight(); - auto point_2 = canvasBounds.getBottomLeft(); - auto point_3 = canvasBounds.getTopLeft(); - auto point_4 = innerRect.getTopRight(); - auto point_5 = innerRect.getBottomRight(); - auto point_6 = innerRect.getBottomLeft(); - auto point_7 = innerRect.getTopLeft(); - - Path zoneLeft, zoneTop, zoneRight, zoneBottom, zoneTabCentre, zoneTab; - - zoneLeft.addQuadrilateral(point_3.x, point_3.y, point_7.x, point_7.y, point_6.x, point_6.y, point_2.x, point_2.y); - zoneTop.addQuadrilateral(point_3.x, point_3.y, point_0.x, point_0.y, point_4.x, point_4.y, point_7.x, point_7.y); - zoneRight.addQuadrilateral(point_4.x, point_4.y, point_0.x, point_0.y, point_1.x, point_1.y, point_5.x, point_5.y); - zoneBottom.addQuadrilateral(point_6.x, point_6.y, point_5.x, point_5.y, point_1.x, point_1.y, point_2.x, point_2.y); - zoneTabCentre.addRectangle(innerRect); - zoneTab.addRectangle(tabbarBounds); - - dropZones[0] = std::tuple(DropZones::Left, zoneLeft); - dropZones[1] = std::tuple(DropZones::Top, zoneTop); - dropZones[2] = std::tuple(DropZones::Right, zoneRight); - dropZones[3] = std::tuple(DropZones::Bottom, zoneBottom); - dropZones[4] = std::tuple(DropZones::Centre, zoneTabCentre); - dropZones[5] = std::tuple(DropZones::TabBar, zoneTab); -} - -TabComponent* ResizableTabbedComponent::getTabComponent() -{ - return tabComponent.get(); -} diff --git a/Source/Tabbar/ResizableTabbedComponent.h b/Source/Tabbar/ResizableTabbedComponent.h deleted file mode 100644 index 13ce41e6fa..0000000000 --- a/Source/Tabbar/ResizableTabbedComponent.h +++ /dev/null @@ -1,96 +0,0 @@ -/* - // Copyright (c) 2021-2023 Timothy Schoen and Alex Mitchell - // For information on usage and redistribution, and for a DISCLAIMER OF ALL - // WARRANTIES, see the file, "LICENSE.txt," in this distribution. -*/ - -#pragma once - -#include -#include "Tabbar.h" -#include "Utility/SplitModeEnum.h" - -class TabComponent; -class PluginEditor; -class SplitViewResizer; -class ResizableTabbedComponent : public Component, public NVGComponent - , public DragAndDropTarget { -public: - ResizableTabbedComponent(PluginEditor* editor, TabComponent* splitTabComponent = nullptr); - - ~ResizableTabbedComponent(); - - void mouseDown(MouseEvent const& e) override; - - void resized() override; - void render(NVGcontext* nvg) override; - - void itemDragMove(SourceDetails const& dragSourceDetails) override; - bool isInterestedInDragSource(SourceDetails const& dragSourceDetails) override; - void itemDropped(SourceDetails const& dragSourceDetails) override; - void itemDragEnter(SourceDetails const& dragSourceDetails) override; - void itemDragExit(SourceDetails const& dragSourceDetails) override; - - void updateSubViewSizes(); - - TabComponent* getTabComponent(); - - void setBoundsWithFactors(Rectangle mainBounds); - - SplitViewResizer* resizerLeft = nullptr; - SplitViewResizer* resizerRight = nullptr; - SplitViewResizer* resizerTop = nullptr; - SplitViewResizer* resizerBottom = nullptr; - - enum DropZones { Left = 1, - Top = 2, - Right = 4, - Bottom = 8, - Centre = 16, - TabBar = 32, - None = 64 }; - - void updateDropZones(); - - void moveToSplit(ResizableTabbedComponent* targetSplit, Canvas* canvas); - void createNewSplit(DropZones activeZone, Canvas* canvas); - -private: - Split::SplitMode splitMode = Split::SplitMode::None; - - float dragFactor = 0.5f; - - int findZoneFromSource(SourceDetails const& dragSourceDetails); - - void moveTabToNewSplit(SourceDetails const& source); - - static String getZoneName(int zone); - - int splitWidth = 0; - - int activeZone = -1; - bool isDragAndDropOver = false; - - std::unique_ptr tabComponent; - - PluginEditor* editor; - - std::tuple dropZones[6]; - - Rectangle oldObjectBounds; - Rectangle oldBounds; - - float const topFactorDefault = 0.0f; - float const bottomFactorDefault = 1.0f; - float const leftFactorDefault = 0.0f; - float const rightFactorDefault = 1.0f; - - float topFactor = 0.0f; - float bottomFactor = 1.0f; - float leftFactor = 0.0f; - float rightFactor = 1.0f; - - Rectangle splitBoundsTop, splitBoundsBottom, splitBoundsRight, splitBoundsLeft, splitBoundsFull; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ResizableTabbedComponent) -}; diff --git a/Source/Tabbar/SplitView.cpp b/Source/Tabbar/SplitView.cpp deleted file mode 100644 index 00287c2232..0000000000 --- a/Source/Tabbar/SplitView.cpp +++ /dev/null @@ -1,246 +0,0 @@ -/* - // Copyright (c) 2021-2023 Timothy Schoen - // For information on usage and redistribution, and for a DISCLAIMER OF ALL - // WARRANTIES, see the file, "LICENSE.txt," in this distribution. -*/ - -#include -#include "Utility/Config.h" -#include "Utility/Fonts.h" - -#include "PluginEditor.h" -#include "SplitView.h" -#include "Canvas.h" -#include "PluginProcessor.h" -#include "Sidebar/Sidebar.h" - -class SplitViewFocusOutline : public Component, public NVGComponent - , public ComponentListener { -public: - SplitViewFocusOutline() : NVGComponent(this) - { - setInterceptsMouseClicks(false, false); - } - - ~SplitViewFocusOutline() - { - if (tabbedComponent) - tabbedComponent->removeComponentListener(this); - } - - void setActive(ResizableTabbedComponent* tabComponent) - { - setVisible(true); - - if (tabbedComponent != tabComponent) { - if (tabbedComponent) - tabbedComponent->removeComponentListener(this); - - tabComponent->addComponentListener(this); - setBounds(tabComponent->getBounds()); - tabbedComponent = tabComponent; - } - } - - void componentMovedOrResized(Component& component, bool moved, bool resized) override - { - if (&component == tabbedComponent) { - setBounds(component.getBounds()); - } - } - - void paint(Graphics& g) override - { - g.setColour(findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.3f)); - g.drawRect(getLocalBounds(), 2.5f); - } - - void render(NVGcontext* nvg) override - { - nvgStrokeColor(nvg, convertColour(findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.3f))); - nvgStrokeWidth(nvg, 2.5f); - nvgBeginPath(nvg); - nvgScissor(nvg, 0, 0, getWidth(), getHeight()); - nvgRect(nvg, 0, 0, getWidth(), getHeight()); - nvgStroke(nvg); - } - -private: - SafePointer tabbedComponent = nullptr; -}; - -SplitView::SplitView(PluginEditor* parent) - : NVGComponent(this), editor(parent) -{ - rootComponent = new ResizableTabbedComponent(editor); - splits.add(rootComponent); - addAndMakeVisible(rootComponent); - // this will cause the default welcome screen to be selected - // which we don't want - // either we check if the tabcomponent is welcome mode, or we check if it's nullptr down the line - activeTabComponent = rootComponent; - - focusOutline = std::make_unique(); - addChildComponent(focusOutline.get()); - focusOutline->setAlwaysOnTop(true); - - addMouseListener(this, true); -} - -SplitView::~SplitView() = default; - - -void SplitView::render(NVGcontext* nvg) -{ - for(auto* split : splits) - { - nvgSave(nvg); - auto splitPos = editor->nvgSurface.getLocalPoint(split, Point(0, 0)); - nvgTranslate(nvg, splitPos.x, splitPos.y); - split->render(nvg); // Render active tab dnd areas - nvgRestore(nvg); - } - - if(focusOutline && focusOutline->isVisible()) { - nvgSave(nvg); - auto focusOutlinePos = editor->nvgSurface.getLocalPoint(focusOutline.get(), Point(0, 0)); - nvgTranslate(nvg, focusOutlinePos.x, focusOutlinePos.y); - focusOutline->render(nvg); - nvgRestore(nvg); - } -} - -bool SplitView::canSplit() -{ - return splits.size() < 2; -} - -void SplitView::removeSplit(TabComponent* toRemove) -{ - ResizableTabbedComponent* toBeRemoved = nullptr; - for (auto* split : splits) { - if (split->getTabComponent() == toRemove) { - toBeRemoved = split; - splits.removeObject(split, false); - break; - } - } - if (toBeRemoved) { - if (toBeRemoved->resizerRight) { - toBeRemoved->resizerRight->splits[1]->resizerLeft = toBeRemoved->resizerLeft; - setFocus(toBeRemoved->resizerRight->splits[1]); - // We prioritize the deletion of the Right Resizer, and hence also need to - // update the pointer to the split - if (toBeRemoved->resizerLeft) { - toBeRemoved->resizerLeft->splits[0]->resizerRight->splits[1] = toBeRemoved->resizerRight->splits[1]; - } - resizers.removeObject(toBeRemoved->resizerRight, true); - } else if (toBeRemoved->resizerLeft) { - toBeRemoved->resizerLeft->splits[0]->resizerRight = toBeRemoved->resizerRight; - setFocus(toBeRemoved->resizerLeft->splits[0]); - resizers.removeObject(toBeRemoved->resizerLeft, true); - } - } - delete toBeRemoved; - - if (splits.size() == 1) - focusOutline->setVisible(false); -} - -void SplitView::addSplit(ResizableTabbedComponent* split) -{ - splits.add(split); - addAndMakeVisible(split); - setFocus(split); -} - -void SplitView::createNewSplit(Canvas* canvas) -{ - activeTabComponent->createNewSplit(ResizableTabbedComponent::Right, canvas); -} - -void SplitView::addResizer(SplitViewResizer* resizer) -{ - resizers.add(resizer); - addAndMakeVisible(resizer); - resizer->setBounds(getLocalBounds()); -} - -int SplitView::getTabComponentSplitIndex(TabComponent* tabComponent) -{ - for (int i = 0; i < splits.size(); i++) { - if (splits[i]->getTabComponent() == tabComponent) { - return i; - } - } - - // This might happen when running multiple window - return 0; -} - -void SplitView::resized() -{ - auto b = getLocalBounds(); - for (auto* split : splits) { - split->setBoundsWithFactors(b); - } - for (auto* resizer : resizers) { - resizer->setBounds(b); - } -} - -void SplitView::setFocus(ResizableTabbedComponent* selectedTabComponent) -{ - if (activeTabComponent != selectedTabComponent) { - activeTabComponent = selectedTabComponent; - focusOutline->setActive(activeTabComponent); - editor->updateCommandStatus(); - editor->nvgSurface.triggerRepaint(); - } -} - -void SplitView::closeEmptySplits() -{ - // if we have one split, allow welcome screen to show - if (splits.size() == 1) - return; - - auto removedSplit = false; - - // search over all splits, and see if they have tab components with tabs, if not, delete - for (int i = splits.size() - 1; i >= 0; i--) { - auto* split = splits[i]; - if (auto* tabComponent = split->getTabComponent()) { - if (tabComponent->getNumTabs() == 0 && splits.size() > 1) { - removeSplit(tabComponent); - removedSplit = true; - } - } - } - - // reset the other splits bounds factors - if (removedSplit) { - for (auto* split : splits) { - split->setBoundsWithFactors(getLocalBounds()); - } - } -} - -ResizableTabbedComponent* SplitView::getSplitAtScreenPosition(Point position) -{ - for (auto* split : splits) { - if (split->getScreenBounds().contains(position)) { - return split; - } - } - - return nullptr; -} - -TabComponent* SplitView::getActiveTabbar() -{ - if (activeTabComponent) - return activeTabComponent->getTabComponent(); - - return nullptr; -} diff --git a/Source/Tabbar/SplitView.h b/Source/Tabbar/SplitView.h deleted file mode 100644 index 291b2fd82d..0000000000 --- a/Source/Tabbar/SplitView.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - // Copyright (c) 2021-2023 Timothy Schoen - // For information on usage and redistribution, and for a DISCLAIMER OF ALL - // WARRANTIES, see the file, "LICENSE.txt," in this distribution. -*/ - -#pragma once - -#include -#include "SplitViewResizer.h" -#include "ResizableTabbedComponent.h" - -class PluginEditor; -class Canvas; -class SplitViewFocusOutline; -class SplitViewResizer; -class SplitView : public Component, public NVGComponent { -public: - explicit SplitView(PluginEditor* parent); - ~SplitView() override; - - TabComponent* getActiveTabbar(); - - void createNewSplit(Canvas* cnv); - void addSplit(ResizableTabbedComponent* toSplit); - void addResizer(SplitViewResizer* resizer); - - void removeSplit(TabComponent* toRemove); - - bool canSplit(); - - void setFocus(ResizableTabbedComponent* selectedTabComponent); - - - void render(NVGcontext* nvg) override; - - void closeEmptySplits(); - - int getTabComponentSplitIndex(TabComponent* tabComponent); - - ResizableTabbedComponent* getSplitAtScreenPosition(Point position); - - OwnedArray splits; - OwnedArray resizers; - - float splitViewWidth = 0.5f; - void resized() override; - -private: - Rectangle selectedSplit; - SafePointer activeTabComponent = nullptr; - ResizableTabbedComponent* rootComponent; - - std::unique_ptr focusOutline; - - PluginEditor* editor; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SplitView) -}; diff --git a/Source/Tabbar/SplitViewResizer.cpp b/Source/Tabbar/SplitViewResizer.cpp deleted file mode 100644 index 30413e4a06..0000000000 --- a/Source/Tabbar/SplitViewResizer.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/* - // Copyright (c) 2021-2023 Timothy Schoen and Alex Mitchell - // For information on usage and redistribution, and for a DISCLAIMER OF ALL - // WARRANTIES, see the file, "LICENSE.txt," in this distribution. -*/ - -#include "SplitViewResizer.h" -#include "ResizableTabbedComponent.h" - -SplitViewResizer::SplitViewResizer(ResizableTabbedComponent* originalComponent, ResizableTabbedComponent* newComponent, Split::SplitMode mode, int flipped) -{ - setAlwaysOnTop(true); - - splitMode = mode; - resizerPosition = 0.5f; - auto resizerRightPostion = 1.0f; - auto resizerLeftPosition = 0.0f; - - if (auto originalRight = originalComponent->resizerRight) { - resizerRightPostion = originalRight->resizerPosition; - } - - if (auto originalLeft = originalComponent->resizerLeft) { - resizerLeftPosition = originalLeft->resizerPosition; - } - - resizerPosition = (resizerRightPostion - resizerLeftPosition) * 0.5f + resizerLeftPosition; - - if (flipped == 1) { - splits[0] = originalComponent; - splits[1] = newComponent; - } else if (flipped == 0) { - splits[0] = newComponent; - splits[1] = originalComponent; - } - setWantsKeyboardFocus(false); -} - -MouseCursor SplitViewResizer::getMouseCursor() -{ - switch (splitMode) { - case Split::SplitMode::Horizontal: - return MouseCursor::LeftRightResizeCursor; - case Split::SplitMode::Vertical: - return MouseCursor::UpDownResizeCursor; - default: - return MouseCursor::NormalCursor; - } -} - -bool SplitViewResizer::hitTest(int x, int y) -{ - if (splitMode == Split::SplitMode::None) - return false; - return resizeArea.contains(Point(x, y)); -} - -void SplitViewResizer::resized() -{ - if (splitMode == Split::SplitMode::None) - return; - - if (splitMode == Split::SplitMode::Horizontal) { - resizeArea.setBounds(getWidth() * resizerPosition, 0, thickness, getHeight()); - } else { - resizeArea.setBounds(0, getHeight() * resizerPosition, getWidth(), thickness); - } - -#if (ENABLE_SPLIT_RESIZER_DEBBUGING == 1) - repaint(); -#endif -} - -void SplitViewResizer::paint(Graphics& g) -{ -#if (ENABLE_SPLIT_RESIZER_DEBBUGING == 1) - if (splitMode == Split::SplitMode::None) - return; - - g.setColour(Colours::red); - g.fillRect(resizeArea); -#endif -} - -bool SplitViewResizer::setResizerPosition(float newPosition, bool checkLeft) -{ - auto left = splits[0]->resizerLeft; - auto right = splits[1]->resizerRight; - - auto leftPos = 0.0f; - auto rightPos = 1.0f; - - if (left) - leftPos = left->resizerPosition; - if (right) - rightPos = right->resizerPosition; - - // check calculated sise of all resizers, if they are less than min width, don't resize. - // if we hit a resizer that is null, this means we are at an edge - - // check all to left - if ((newPosition / getWidth()) - leftPos < (minimumWidth / getWidth()) && checkLeft) { - - if (splits[0]->resizerLeft) { - hitEdge = splits[0]->resizerLeft->setResizerPosition(newPosition - minimumWidth); - } else { - return true; - } - - if (hitEdge) - return true; - - resizeArea.setPosition(newPosition, 0); - resizerPosition = newPosition / getWidth(); - return false; - } - // check all to right - if (rightPos - (newPosition / getWidth()) < (minimumWidth / getWidth())) { - if (splits[1]->resizerRight) { - hitEdge = splits[1]->resizerRight->setResizerPosition(newPosition + minimumWidth, false); - } else { - return true; - } - - if (hitEdge) - return true; - - resizeArea.setPosition(newPosition, 0); - resizerPosition = newPosition / getWidth(); - return false; - } - - resizeArea.setPosition(newPosition, 0); - resizerPosition = newPosition / getWidth(); - return false; -} - -void SplitViewResizer::mouseDrag(MouseEvent const& e) -{ - if (rateReducer.tooFast()) - return; - - auto posX = e.position.x; - - if (splitMode == Split::SplitMode::Horizontal) { - hitEdge = false; - setResizerPosition(posX); - getParentComponent()->resized(); - } - -#if (ENABLE_SPLIT_RESIZER_DEBBUGING == 1) - repaint(); -#endif -} diff --git a/Source/Tabbar/SplitViewResizer.h b/Source/Tabbar/SplitViewResizer.h deleted file mode 100644 index 8e68599d75..0000000000 --- a/Source/Tabbar/SplitViewResizer.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include -#include "Tabbar.h" -#include "Utility/SplitModeEnum.h" -#include "Utility/RateReducer.h" - -class ResizableTabbedComponent; -class SplitViewResizer : public Component { -public: - static inline constexpr int thickness = 6; - enum ResizerEdge { Left, - Right, - Top, - Bottom }; - float resizerPosition; - Split::SplitMode splitMode = Split::SplitMode::None; - - SplitViewResizer(ResizableTabbedComponent* left, ResizableTabbedComponent* right, Split::SplitMode mode = Split::SplitMode::None, int flipped = -1); - - bool setResizerPosition(float newPosition, bool checkLeft = true); - - MouseCursor getMouseCursor() override; - - bool hitTest(int x, int y) override; - - void resized() override; - - void paint(Graphics& g) override; - - Rectangle resizeArea; - - ResizableTabbedComponent* splits[2] = { nullptr }; - -private: - - void mouseDrag(MouseEvent const& e) override; - - float minimumWidth = 100.0f; - - bool hitEdge = false; - - RateReducer rateReducer = RateReducer(60); - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SplitViewResizer) -}; diff --git a/Source/Tabbar/TabBarButtonComponent.cpp b/Source/Tabbar/TabBarButtonComponent.cpp deleted file mode 100644 index c68ec0b992..0000000000 --- a/Source/Tabbar/TabBarButtonComponent.cpp +++ /dev/null @@ -1,371 +0,0 @@ -/* - // Copyright (c) 2021-2023 Timothy Schoen and Alex Mitchell - // For information on usage and redistribution, and for a DISCLAIMER OF ALL - // WARRANTIES, see the file, "LICENSE.txt," in this distribution. -*/ - -#include "TabBarButtonComponent.h" -#include "Tabbar.h" -#include "Dialogs/Dialogs.h" -#include "Utility/StackShadow.h" -#include "Utility/Fonts.h" - -// #define ENABLE_TABBAR_DEBUGGING 1 - -class CloseTabButton : public SmallIconButton { - - using SmallIconButton::SmallIconButton; - - void paint(Graphics& g) override - { - auto font = Fonts::getIconFont().withHeight(12); - g.setFont(font); - - if (!isEnabled()) { - g.setColour(Colours::grey); - } else if (getToggleState()) { - g.setColour(findColour(PlugDataColour::toolbarActiveColourId)); - } else if (isMouseOver()) { - g.setColour(findColour(PlugDataColour::toolbarTextColourId).brighter(0.8f)); - } else { - g.setColour(findColour(PlugDataColour::toolbarTextColourId)); - } - - int const yIndent = jmin(4, proportionOfHeight(0.3f)); - int const cornerSize = jmin(getHeight(), getWidth()) / 2; - - int const fontHeight = roundToInt(font.getHeight() * 0.6f); - int const leftIndent = jmin(fontHeight, 2 + cornerSize / (isConnectedOnLeft() ? 4 : 2)); - int const rightIndent = jmin(fontHeight, 2 + cornerSize / (isConnectedOnRight() ? 4 : 2)); - int const textWidth = getWidth() - leftIndent - rightIndent; - - if (textWidth > 0) - g.drawFittedText(getButtonText(), leftIndent, yIndent, textWidth, getHeight() - yIndent * 2, Justification::centred, 2); - } -}; - -TabBarButtonComponent::TabBarButtonComponent(TabComponent* tabbar, String const& name, TabbedButtonBar& bar) - : TabBarButton(name, bar) - , tabComponent(tabbar) - , ghostTabAnimator(&dynamic_cast(&bar)->ghostTabAnimator) -{ - ghostTabAnimator->addChangeListener(this); - - setTooltip(name); - - closeTabButton = std::make_unique(Icons::Clear); - closeTabButton->setColour(TextButton::buttonColourId, Colour()); - closeTabButton->setColour(TextButton::buttonOnColourId, Colour()); - closeTabButton->setColour(TextButton::textColourOffId, findColour(PlugDataColour::toolbarTextColourId)); - closeTabButton->setColour(TextButton::textColourOnId, findColour(PlugDataColour::toolbarActiveColourId)); - closeTabButton->setColour(ComboBox::outlineColourId, Colour()); - closeTabButton->setConnectedEdges(12); - closeTabButton->setSize(28, 28); - closeTabButton->addMouseListener(this, false); - closeTabButton->onClick = [this]() mutable { - closeTab(); - }; - - addChildComponent(closeTabButton.get()); - updateCloseButtonState(); -} - -TabBarButtonComponent::~TabBarButtonComponent() -{ - closeTabButton->removeMouseListener(this); - ghostTabAnimator->removeChangeListener(this); -} - -void TabBarButtonComponent::changeListenerCallback(ChangeBroadcaster* source) -{ - if (source == ghostTabAnimator) { - if (!ghostTabAnimator->isAnimating() && closeButtonUpdatePending) { - closeTabButton->setVisible(isMouseOver(true) || getToggleState()); - closeButtonUpdatePending = false; - } - } -} - -void TabBarButtonComponent::updateCloseButtonState() -{ - if (!ghostTabAnimator->isAnimating()) { - closeTabButton->setVisible(isMouseOver(true) || getToggleState()); - } else { - closeButtonUpdatePending = true; - } -} - -void TabBarButtonComponent::closeTab() -{ - // We cant use the index from earlier because it might have changed! - int const tabIdx = getIndex(); - auto* cnv = tabComponent->getCanvas(tabIdx); - auto* editor = tabComponent->getEditor(); - - if (tabIdx == -1) - return; - - if (cnv) { - MessageManager::callAsync([_cnv = SafePointer(cnv), _editor = SafePointer(editor)]() mutable { - // Don't show save dialog, if patch is still open in another view - if (_cnv && _cnv->patch.isDirty()) { - Dialogs::showAskToSaveDialog( - &_editor->openedDialog, _editor, _cnv->patch.getTitle(), - [_cnv, _editor](int result) mutable { - if (!_cnv) - return; - if (result == 2) - _editor->saveProject([_cnv, _editor]() mutable { _editor->closeTab(_cnv); }); - else if (result == 1) - _editor->closeTab(_cnv); - }, - 0, true); - } else { - _editor->closeTab(_cnv); - } - }); - } -} - -void TabBarButtonComponent::setFocusForTabSplit() -{ - for (auto* split : getTabComponent()->getEditor()->splitView.splits) { - if (split->getTabComponent() == getTabComponent()) { - getTabComponent()->getEditor()->splitView.setFocus(split); - } - } -} - -void TabBarButtonComponent::setTabText(String const& text) -{ - setTooltip(text); - setButtonText(text); -} - -TabComponent* TabBarButtonComponent::getTabComponent() -{ - return tabComponent; -} - -void TabBarButtonComponent::mouseEnter(MouseEvent const& e) -{ - updateCloseButtonState(); - repaint(); -} - -void TabBarButtonComponent::mouseExit(MouseEvent const& e) -{ - updateCloseButtonState(); - repaint(); -} - -void TabBarButtonComponent::lookAndFeelChanged() -{ - closeTabButton->setColour(TextButton::textColourOffId, findColour(PlugDataColour::toolbarTextColourId)); - closeTabButton->setColour(TextButton::textColourOnId, findColour(PlugDataColour::toolbarActiveColourId)); -} - -void TabBarButtonComponent::resized() -{ - closeTabButton->setCentrePosition(getBounds().getCentre().withX(getBounds().getWidth() - 15).translated(0, 1)); -} - -ScaledImage TabBarButtonComponent::generateTabBarButtonImage() -{ - auto scale = 2.0f; - // we calculate the best size for the tab DnD image - auto text = getButtonText(); - Font font(Fonts::getDefaultFont()); - auto length = font.getStringWidth(getButtonText()) + 32; - auto const boundsOffset = 10; - - // we need to expand the bounds, but reset the position to top left - // then we offset the mouse drag by the same amount - // this is to allow area for the shadow to render correctly - auto textBounds = Rectangle(0, 0, length, 28); - auto bounds = textBounds.expanded(boundsOffset).withZeroOrigin(); - auto image = Image(Image::PixelFormat::ARGB, bounds.getWidth() * scale, bounds.getHeight() * scale, true); - auto g = Graphics(image); - g.addTransform(AffineTransform::scale(scale)); - Path path; - path.addRoundedRectangle(bounds.reduced(14), 5.0f); - StackShadow::renderDropShadow(g, path, Colour(0, 0, 0).withAlpha(0.3f), 7, { 0, 1 }, scale); - g.setOpacity(1.0f); - drawTabButton(g, textBounds.withPosition(10, 10)); - - drawTabButtonText(g, textBounds.withPosition(3, 5)); - // g.drawImage(snapshot, bounds.toFloat(), RectanglePlacement::doNotResize | RectanglePlacement::centred); - -#if ENABLE_TABBAR_DEBUGGING == 1 - g.setColour(Colours::red); - g.drawRect(bounds.toFloat(), 1.0f); -#endif - - return {image, scale}; -} - -void TabBarButtonComponent::mouseDown(MouseEvent const& e) -{ - if (e.originalComponent != this) - return; - - if (e.mods.isMiddleButtonDown()) { - closeTab(); - } - - if (e.mods.isPopupMenu()) { - PopupMenu tabMenu; - -#if JUCE_MAC - String revealTip = "Reveal in Finder"; -#elif JUCE_WINDOWS - String revealTip = "Reveal in Explorer"; -#else - String revealTip = "Reveal in file browser"; -#endif - - auto* cnv = getTabComponent()->getCanvas(getIndex()); - if (!cnv) - return; - - bool canReveal = cnv->patch.getCurrentFile().existsAsFile(); - - tabMenu.addItem(revealTip, canReveal, false, [cnv]() { - cnv->patch.getCurrentFile().revealToUser(); - }); - - tabMenu.addSeparator(); - - PopupMenu parentPatchMenu; - - if(auto patch = cnv->patch.getPointer()) - { - auto* parent = patch.get(); - while((parent = parent->gl_owner)) - { - parentPatchMenu.addItem(String::fromUTF8(parent->gl_name->s_name), [parent, cnv](){ - auto* editor = cnv->editor; - auto* pd = reinterpret_cast(editor->pd); - - for (auto* searchedCanvas : editor->canvases) { - if (searchedCanvas->patch.getPointer().get() == parent) { - searchedCanvas->getTabbar()->setCurrentTabIndex(searchedCanvas->getTabIndex()); - return; - } - } - - auto* patch = new pd::Patch(pd::WeakReference(parent, pd), pd, false); - auto* newCanvas = editor->canvases.add(new Canvas(editor, patch)); - editor->addTab(newCanvas); - newCanvas->jumpToOrigin(); - newCanvas->getTabbar()->setCurrentTabIndex(newCanvas->getTabIndex()); - }); - } - } - - tabMenu.addSubMenu("Parent patches", parentPatchMenu, parentPatchMenu.getNumItems()); - - tabMenu.addSeparator(); - - auto canSplitTab = cnv->editor->getSplitView()->splits.size() > 1 || getTabComponent()->getNumTabs() > 1; - tabMenu.addItem("Split left", canSplitTab, false, [cnv]() { - auto splitIdx = cnv->editor->splitView.getTabComponentSplitIndex(cnv->getTabbar()); - auto* currentSplit = cnv->editor->splitView.splits[splitIdx]; - auto* targetSplit = cnv->editor->splitView.splits[0]; - currentSplit->moveToSplit(targetSplit, cnv); - }); - tabMenu.addItem("Split right", canSplitTab, false, [cnv]() { - auto splitIdx = cnv->editor->splitView.getTabComponentSplitIndex(cnv->getTabbar()); - auto* currentSplit = cnv->editor->splitView.splits[splitIdx]; - auto* targetSplit = cnv->editor->splitView.splits.size() > 1 ? cnv->editor->splitView.splits[1] : nullptr; - currentSplit->moveToSplit(targetSplit, cnv); - }); - - tabMenu.addSeparator(); - - - tabMenu.addItem("Close patch", true, false, [cnv]() { - cnv->editor->closeTab(cnv); - }); - - tabMenu.addItem("Close all other patches", true, false, [cnv]() { - cnv->editor->closeAllTabs(false, cnv); - }); - - tabMenu.addItem("Close all patches", true, false, [cnv]() { - cnv->editor->closeAllTabs(false); - }); - - // Show the popup menu at the mouse position - tabMenu.showMenuAsync(PopupMenu::Options().withMinimumWidth(150).withMaximumNumColumns(1)); - } else if (e.mods.isLeftButtonDown()) { - getTabComponent()->setCurrentTabIndex(getIndex()); - } -} - -void TabBarButtonComponent::mouseDrag(MouseEvent const& e) -{ - if (e.getDistanceFromDragStart() > 10 && !isDragging) { - isDragging = true; - closeTabButton->setVisible(false); - var tabIndex = getIndex(); - auto dragContainer = ZoomableDragAndDropContainer::findParentDragContainerFor(this); - - tabImage = generateTabBarButtonImage(); - dragContainer->startDragging(tabIndex, this, tabImage, tabImage, true, nullptr); - } -} - -void TabBarButtonComponent::mouseUp(MouseEvent const& e) -{ - // we need to set visibility in the LNF due to using Juce overflow extra menu which uses visibility internally - getProperties().set("dragged", var(false)); - isDragging = false; -} - -// FIXME: we are only using this to draw the DnD tab image -void TabBarButtonComponent::drawTabButton(Graphics& g, Rectangle customBounds) -{ - bool isActive = getToggleState(); - - if (isActive) { - g.setColour(findColour(PlugDataColour::activeTabBackgroundColourId)); - } else if (isMouseOver(true)) { - g.setColour(findColour(PlugDataColour::activeTabBackgroundColourId).interpolatedWith(findColour(PlugDataColour::toolbarBackgroundColourId), 0.4f)); - } else { - g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); - } - - auto bounds = getLocalBounds(); - - if (!customBounds.isEmpty()) - bounds = customBounds; - - g.fillRoundedRectangle(bounds.toFloat().reduced(4.5f), Corners::defaultCornerRadius); -} - -// FIXME: we are only using this to draw the DnD tab image -void TabBarButtonComponent::drawTabButtonText(Graphics& g, Rectangle customBounds) -{ - auto bounds = getLocalBounds(); - if (!customBounds.isEmpty()) - bounds = customBounds; - - auto area = bounds.reduced(4, 2).toFloat(); - - Font font(getLookAndFeel().getTabButtonFont(*this, area.getHeight())); - font.setUnderline(hasKeyboardFocus(false)); - - AffineTransform t = AffineTransform::translation(area.getX(), area.getY()); - - g.setColour(findColour(PlugDataColour::toolbarTextColourId)); - g.setFont(font); - g.addTransform(t); - - auto buttonText = getButtonText().trim(); - - g.drawFittedText(buttonText, - area.getX(), area.getY() - 2, (int)area.getWidth(), (int)area.getHeight(), - Justification::centred, - jmax(1, ((int)area.getHeight()) / 12)); -} diff --git a/Source/Tabbar/TabBarButtonComponent.h b/Source/Tabbar/TabBarButtonComponent.h deleted file mode 100644 index 47ec9ab19f..0000000000 --- a/Source/Tabbar/TabBarButtonComponent.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - // Copyright (c) 2021-2023 Timothy Schoen and Alex Mitchell - // For information on usage and redistribution, and for a DISCLAIMER OF ALL - // WARRANTIES, see the file, "LICENSE.txt," in this distribution. -*/ - -#pragma once - -#include -#include "Utility/Config.h" - -class CloseTabButton; -class TabComponent; -class TabBarButtonComponent : public TabBarButton - , public ChangeListener { -public: - TabBarButtonComponent(TabComponent* tabComponent, String const& name, TabbedButtonBar& bar); - - ~TabBarButtonComponent() override; - - TabComponent* getTabComponent(); - - void resized() override; - - void updateCloseButtonState(); - - void mouseDrag(MouseEvent const& e) override; - void mouseEnter(MouseEvent const& e) override; - void mouseExit(MouseEvent const& e) override; - void mouseUp(MouseEvent const& e) override; - void mouseDown(MouseEvent const& e) override; - - void changeListenerCallback(ChangeBroadcaster* source) override; - - void lookAndFeelChanged() override; - - void setTabText(String const& text); - - void setFocusForTabSplit(); - - void drawTabButton(Graphics& g, Rectangle customBounds = Rectangle()); - void drawTabButtonText(Graphics& g, Rectangle customBounds = Rectangle()); - ScaledImage generateTabBarButtonImage(); - - void closeTab(); - -private: - TabComponent* tabComponent; - ComponentAnimator* ghostTabAnimator; - std::unique_ptr closeTabButton; - ScaledImage tabImage; - bool isDragging = false; - bool closeButtonUpdatePending = false; -}; diff --git a/Source/Tabbar/Tabbar.cpp b/Source/Tabbar/Tabbar.cpp deleted file mode 100644 index 17d8ce6093..0000000000 --- a/Source/Tabbar/Tabbar.cpp +++ /dev/null @@ -1,527 +0,0 @@ -/* - // Copyright (c) 2021-2023 Timothy Schoen - // For information on usage and redistribution, and for a DISCLAIMER OF ALL - // WARRANTIES, see the file, "LICENSE.txt," in this distribution. -*/ - -#include -#include "Utility/Config.h" -#include "Utility/Fonts.h" -#include "Tabbar.h" -#include "Canvas.h" -#include "PluginEditor.h" -#include "PluginProcessor.h" -#include "Sidebar/Sidebar.h" -#include "TabBarButtonComponent.h" -#include "Utility/StackShadow.h" -#include "Utility/Autosave.h" - -class ButtonBar::GhostTab : public Component { -public: - explicit GhostTab(PlugDataLook& lnfRef) - : lnf(lnfRef) - { - } - - void setTabButtonToGhost(TabBarButton* tabButton) - { - tab = tabButton; - // this should never happen, if it does then the index for the tab is wrong ( getTab(idx) will return nullptr ) - // which can happen if the tabbar goes into overflow, because we don't know exactly when that will happen - if (tab) { - setBounds(tab->getBounds()); - repaint(); - } - } - - int getIndex() - { - if (tab) - return tab->getIndex(); - - return -1; - } - - void resized() override - { - shadowPath.clear(); - shadowPath.addRoundedRectangle(getLocalBounds().reduced(8).toFloat(), Corners::defaultCornerRadius); - } - - void paint(Graphics& g) override - { - if (tab) { - StackShadow::renderDropShadow(g, shadowPath, Colour(0, 0, 0).withAlpha(0.3f), 5); - lnf.drawTabButton(*tab, g, true, true, true); - } - } - -private: - SafePointer tab; - PlugDataLook& lnf; - - Path shadowPath; -}; - -ButtonBar::ButtonBar(TabComponent& tabComp, TabbedButtonBar::Orientation o) - : TabbedButtonBar(o) - , owner(tabComp) -{ - ghostTab = std::make_unique(dynamic_cast(LookAndFeel::getDefaultLookAndFeel())); - addChildComponent(ghostTab.get()); - ghostTab->setAlwaysOnTop(true); - - ghostTabAnimator.addChangeListener(this); - - setInterceptsMouseClicks(true, true); -} - -bool ButtonBar::isInterestedInDragSource(SourceDetails const& dragSourceDetails) -{ - if (dynamic_cast(dragSourceDetails.sourceComponent.get())) - return true; - - return false; -} - -void ButtonBar::changeListenerCallback(ChangeBroadcaster* source) -{ - if (&ghostTabAnimator == source) { - if (!ghostTabAnimator.isAnimating()) { - ghostTab->setVisible(false); - auto* tabButton = getTabButton(ghostTabIdx); - auto ghostTabFinalPos = ghostTab->getBounds(); - - // we need to reset the final position of the tab, as we have stored the ghosttabs entry position into it - tabButton->setBounds(ghostTabFinalPos); - tabButton->getProperties().set("dragged", var(false)); - } - } -} - -void ButtonBar::itemDropped(SourceDetails const& dragSourceDetails) -{ - auto animateTabToPosition = [this]() { - auto* tabButton = getTabButton(ghostTabIdx); - tabButton->getProperties().set("dragged", var(true)); - - ghostTabAnimator.animateComponent(ghostTab.get(), ghostTab->getBounds().withPosition(Point(ghostTab->getIndex() * (getWidth() / getNumVisibleTabs()), 0)), 1.0f, 200, false, 3.0f, 0.0f); - }; - - // this has a whole lot of code replication from ResizableTabbedComponent.cpp, good candidate for refactoring! - if (!inOtherSplit) { - animateTabToPosition(); - } else { - auto sourceTabButton = static_cast(dragSourceDetails.sourceComponent.get()); - int sourceTabIndex = sourceTabButton->getIndex(); - auto sourceTabContent = SafePointer(sourceTabButton->getTabComponent()); - int sourceNumTabs = sourceTabContent->getNumVisibleTabs(); - bool otherWindow = sourceTabContent->getTopLevelComponent() != getTopLevelComponent(); - auto ghostTabBounds = ghostTab->getBounds(); - - auto* tabCanvas = sourceTabContent->getCanvas(sourceTabIndex); - - auto* newEditor = owner.getEditor(); - newEditor->pd->patches.add(tabCanvas->patch); - auto newPatch = newEditor->pd->patches.getLast(); - auto* newCanvas = newEditor->canvases.add(new Canvas(newEditor, *newPatch, nullptr)); - - inOtherSplit = false; - // we remove the ghost tab, which is NOT a proper tab, (it only a tab, and doesn't have a viewport) - owner.removeTab(ghostTabIdx); - - auto tabTitle = tabCanvas->patch.getTitle(); - // we then re-add the ghost tab, but this time we add it from the owner (tabComponent) - // which allows us to inject the viewport - owner.addTab(tabTitle, newCanvas->viewport.get(), ghostTabIdx); - owner.setCurrentTabIndex(ghostTabIdx); - - // we need to give the ghost tab the new tab button, as the old one will be deleted before - // the ghost tabs animation has finished - // this is easier than keeping the old tab alive - auto newTab = owner.tabs->getTabButton(ghostTabIdx); - newTab->setBounds(ghostTabBounds); - ghostTab->setTabButtonToGhost(newTab); - - tabCanvas->editor->closeTab(tabCanvas); - - if(sourceTabContent) { - auto sourceCurrentIndex = sourceTabIndex > (sourceTabContent->getNumVisibleTabs() - 1) ? sourceTabIndex - 1 : sourceTabIndex; - sourceTabContent->setCurrentTabIndex(sourceCurrentIndex); - } - - if (sourceNumTabs < 2 && !otherWindow) { - // we don't animate the ghostTab moving into position, as the geometry of the splits is changing - ghostTab->setVisible(false); - ghostTabAnimator.cancelAllAnimations(true); - - if(sourceTabContent) owner.editor->splitView.removeSplit(sourceTabContent); - for (auto* split : owner.editor->splitView.splits) { - split->setBoundsWithFactors(owner.editor->splitView.getLocalBounds()); - } - } else { - animateTabToPosition(); - } - - // set all current canvas viewports to visible, (if they already are this shouldn't do anything) - for (auto* split : owner.editor->splitView.splits) { - if (auto tabComponent = split->getTabComponent()) { - if (auto* cnv = tabComponent->getCanvas(tabComponent->getCurrentTabIndex())) { - cnv->viewport->setVisible(true); - split->resized(); - split->getTabComponent()->resized(); - } - } - } - } -} - -void ButtonBar::itemDragEnter(SourceDetails const& dragSourceDetails) -{ - if (auto* tab = dynamic_cast(dragSourceDetails.sourceComponent.get())) { - ghostTabAnimator.cancelAllAnimations(false); - owner.setFocused(); - // if this tabbar is DnD on itself, we don't need to add a new tab - // we move the existing tab - if (tab->getTabComponent() == &owner) { - tab->getProperties().set("dragged", var(true)); - inOtherSplit = false; - ghostTabIdx = tab->getIndex(); - ghostTab->setTabButtonToGhost(tab); - } else { - // we calculate where the tab will go when its added, - // so we need to add 1 to the number of existing tabs - // to take the added tab into account - - // WARNING: because we are using the overflow (show extra items menu) - // we need to find out how many tabs are visible, not how many there are all together - auto targetTabPos = getWidth() / (getNumVisibleTabs() + 1); - // FIXME: This is a hack. When tab is added to tabbar right edge, and it goes into overflow - // we don't know when that will happen. - // So we force the right most tab to think it's always two less when it gets added - auto tabPos = jmin(dragSourceDetails.localPosition.getX() / targetTabPos, getNumVisibleTabs() - 2); - tabPos = jmax(0, tabPos); - - inOtherSplit = true; - auto unusedComponent = std::make_unique(); - owner.addTab(tab->getButtonText(), unusedComponent.get(), tabPos); - - auto* fakeTab = getTabButton(tabPos); - tab->getProperties().set("dragged", var(true)); - ghostTab->setTabButtonToGhost(fakeTab); - ghostTabIdx = tabPos; - } - ghostTab->setVisible(true); - } -} - -void ButtonBar::itemDragExit(SourceDetails const& dragSourceDetails) -{ - if (auto* tab = dynamic_cast(dragSourceDetails.sourceComponent.get())) { - ghostTab->setVisible(false); - tab->getProperties().set("dragged", var(true)); - if (inOtherSplit) { - inOtherSplit = false; - owner.removeTab(ghostTabIdx); - } - } -} - -int ButtonBar::getNumVisibleTabs() -{ - int numVisibleTabs = 0; - for (int i = 0; i < getNumTabs(); i++) { - if (getTabButton(i)->isVisible()) - numVisibleTabs++; - } - return numVisibleTabs; -} - -void ButtonBar::itemDragMove(SourceDetails const& dragSourceDetails) -{ - if (auto* tab = dynamic_cast(dragSourceDetails.sourceComponent.get())) { - auto ghostTabCentreOffset = ghostTab->getWidth() / 2; - auto targetTabPos = getWidth() / std::max(1, getNumVisibleTabs()); - auto tabPos = ghostTab->getBounds().getCentreX() / targetTabPos; - - auto leftPos = dragSourceDetails.localPosition.getX() - ghostTabCentreOffset; - auto rightPos = dragSourceDetails.localPosition.getX() + ghostTabCentreOffset; - auto tabCentre = tab->getBounds().getCentreY(); - if (leftPos >= 0 && rightPos <= getWidth()) { - ghostTab->setCentrePosition(dragSourceDetails.localPosition.getX(), tabCentre); - } else if (leftPos < 0) { - ghostTab->setCentrePosition(0 + ghostTabCentreOffset, tabCentre); - } else { - ghostTab->setCentrePosition(getWidth() - ghostTabCentreOffset, tabCentre); - } - if (tabPos != ghostTabIdx) { - owner.moveTab(ghostTabIdx, tabPos); - ghostTabIdx = tabPos; - } - tab->getProperties().set("dragged", var(true)); - getTabButton(tabPos)->getProperties().set("dragged", var(true)); - } -} - -void ButtonBar::currentTabChanged(int newCurrentTabIndex, String const& newTabName) -{ - owner.changeCallback(newCurrentTabIndex, newTabName); -} - -TabBarButton* ButtonBar::createTabButton(String const& tabName, int tabIndex) -{ - auto tabBarButton = new TabBarButtonComponent(&owner, tabName, *owner.tabs.get()); - return tabBarButton; -} - -TabComponent::TabComponent(PluginEditor* parent) - : editor(parent) -{ - tabs.reset(new ButtonBar(*this, TabbedButtonBar::Orientation::TabsAtTop)); - addAndMakeVisible(tabs.get()); - - addAndMakeVisible(newButton); - newButton.setColour(TextButton::buttonColourId, Colours::transparentBlack); - newButton.setTooltip("New patch"); - newButton.onClick = [this]() { - newTab(); - }; - - setVisible(false); - setTabBarDepth(0); - tabs->addMouseListener(this, true); -} - -TabComponent::~TabComponent() -{ - tabs->removeMouseListener(this); - clearTabs(); - tabs.reset(); -} - -void TabComponent::setFocused() -{ - for (auto* split : editor->splitView.splits) { - if (split->getTabComponent() == this) - editor->splitView.setFocus(split); - } -} - -int TabComponent::getCurrentTabIndex() -{ - return tabs->getCurrentTabIndex(); -} - -void TabComponent::setCurrentTabIndex(int idx) -{ - tabs->setCurrentTabIndex(idx); - for (int i = 0; i < tabs->getNumTabs(); i++) { - dynamic_cast(tabs->getTabButton(i))->updateCloseButtonState(); - } -} - -void TabComponent::setCanvasActive(Canvas* cnv) -{ - auto cnvIdx = getIndexOfCanvas(cnv); - if (cnvIdx != -1) - setCurrentTabIndex(cnvIdx); -} - -int TabComponent::getNumVisibleTabs() -{ - int numVisibleTabs = 0; - for (int i = 0; i < getNumTabs(); i++) { - if (tabs->getTabButton(i)->isVisible()) - numVisibleTabs++; - } - return numVisibleTabs; -} - -void TabComponent::clearTabs() -{ - if (panelComponent != nullptr) { - panelComponent->setVisible(false); - removeChildComponent(panelComponent.get()); - panelComponent = nullptr; - } - - tabs->clearTabs(); - - contentComponents.clear(); -} - -PluginEditor* TabComponent::getEditor() -{ - return editor; -} - -void TabComponent::newTab() -{ - editor->newProject(); -} - -void TabComponent::addTab(String const& tabName, Component* contentComponent, int insertIndex) -{ - contentComponents.insert(insertIndex, WeakReference(contentComponent)); - - tabs->addTab(tabName, findColour(ResizableWindow::backgroundColourId), insertIndex); - - setTabBarDepth(30); // Make sure tabbar isn't invisible - resized(); -} - -void TabComponent::removeTab(int idx) -{ - contentComponents.remove(idx); - tabs->removeTab(idx); -} - -void TabComponent::moveTab(int currentIndex, int newIndex) -{ - contentComponents.move(currentIndex, newIndex); - tabs->moveTab(currentIndex, newIndex, true); -} - -void TabComponent::onTabChange(int tabIndex) -{ - editor->updateCommandStatus(); - - // Show welcome panel if there are no tabs open - if (tabs->getNumTabs() == 0) { - setTabBarDepth(0); - tabs->setVisible(false); - } else { - tabs->setVisible(true); - setTabBarDepth(30); - // we need to update the dropzones, because no resize will be automatically triggered when there is a tab added from welcome screen - if (auto* parentHolder = dynamic_cast(getParentComponent())) - parentHolder->updateDropZones(); - } - - editor->resized(); - - auto* cnv = getCurrentCanvas(); - if (!cnv || tabIndex == -1 || editor->pd->isPerformingGlobalSync) - return; - - cnv->needsSearchUpdate = true; - cnv->grabKeyboardFocus(); - - for (auto* split : editor->splitView.splits) { - auto tabBar = split->getTabComponent(); - if (tabBar->getCurrentCanvas()) - tabBar->getCurrentCanvas()->tabChanged(); - } - - -} - -void TabComponent::changeCallback(int newCurrentTabIndex, String const& newTabName) -{ - auto* newPanelComp = getTabContentComponent(getCurrentTabIndex()); - - if (newPanelComp != panelComponent) { - if (panelComponent != nullptr) { - removeChildComponent(panelComponent); - } - - panelComponent = newPanelComp; - - if (panelComponent != nullptr) { - // do these ops as two stages instead of addAndMakeVisible() so that the - // component has always got a parent when it gets the visibilityChanged() callback - addChildComponent(panelComponent); - panelComponent->sendLookAndFeelChange(); - panelComponent->setVisible(true); - panelComponent->toFront(true); - editor->updateCommandStatus(); - } - } - currentTabChanged(newCurrentTabIndex, newTabName); -} - -void TabComponent::setTabBarDepth(int newDepth) -{ - if (tabDepth != newDepth) { - tabDepth = newDepth; - resized(); - } -} - -void TabComponent::currentTabChanged(int newCurrentTabIndex, String const& newCurrentTabName) -{ - triggerAsyncUpdate(); -} - -void TabComponent::handleAsyncUpdate() -{ - onTabChange(tabs->getCurrentTabIndex()); -} - -void TabComponent::resized() -{ - auto content = getLocalBounds(); - newButton.setBounds(3, 0, tabDepth, tabDepth); // slighly offset to make it centred next to the tabs - - auto tabBounds = content.removeFromTop(tabDepth).withTrimmedLeft(tabDepth).translated(0, -1); - tabs->setBounds(tabBounds); - - for (int c = 0; c < tabs->getNumTabs(); c++) { - if (auto* comp = getTabContentComponent(c)) { - if (auto* positioner = comp->getPositioner()) { - positioner->applyNewBounds(content); - } else { - comp->setBounds(content); - } - } - } -} - -Component* TabComponent::getTabContentComponent(int tabIndex) const noexcept -{ - return contentComponents[tabIndex].get(); -} - -int TabComponent::getIndexOfCanvas(Canvas* cnv) -{ - if (!cnv->viewport || !cnv->editor) - return -1; - - for (int i = 0; i < tabs->getNumTabs(); i++) { - if (getTabContentComponent(i) == cnv->viewport.get()) { - return i; - } - } - - return -1; -} - -Canvas* TabComponent::getCanvas(int idx) -{ - auto* viewport = dynamic_cast(getTabContentComponent(idx)); - - if (!viewport) - return nullptr; - - return dynamic_cast(viewport->getViewedComponent()); -} - -Canvas* TabComponent::getCurrentCanvas() -{ - auto* viewport = dynamic_cast(getCurrentContentComponent()); - - if (!viewport) - return nullptr; - - return dynamic_cast(viewport->getViewedComponent()); -} - -void TabComponent::setTabText(int tabIndex, String const& newName) -{ - dynamic_cast(tabs->getTabButton(tabIndex))->setTabText(newName); -} - -String TabComponent::getTabText(int tabIndex) -{ - return dynamic_cast(tabs->getTabButton(tabIndex))->getButtonText(); -} diff --git a/Source/Tabbar/Tabbar.h b/Source/Tabbar/Tabbar.h deleted file mode 100644 index c4d2761a6b..0000000000 --- a/Source/Tabbar/Tabbar.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - // Copyright (c) 2021-2022 Timothy Schoen - // For information on usage and redistribution, and for a DISCLAIMER OF ALL - // WARRANTIES, see the file, "LICENSE.txt," in this distribution. -*/ - -#pragma once - -#include -#include "Constants.h" -#include "LookAndFeel.h" - -#include "Utility/GlobalMouseListener.h" -#include "Components/BouncingViewport.h" -#include "Components/Buttons.h" -#include "Canvas.h" -#include "PluginEditor.h" -#include "Components/ZoomableDragAndDropContainer.h" - -class PluginEditor; - -class ButtonBar : public TabbedButtonBar - , public DragAndDropTarget - , public ChangeListener { -public: - ButtonBar(TabComponent& tabComp, TabbedButtonBar::Orientation o); - - bool isInterestedInDragSource(SourceDetails const& dragSourceDetails) override; - void itemDropped(SourceDetails const& dragSourceDetails) override; - void itemDragEnter(SourceDetails const& dragSourceDetails) override; - void itemDragExit(SourceDetails const& dragSourceDetails) override; - void itemDragMove(SourceDetails const& dragSourceDetails) override; - - void currentTabChanged(int newCurrentTabIndex, String const& newTabName) override; - - void changeListenerCallback(ChangeBroadcaster* source) override; - - TabBarButton* createTabButton(String const& tabName, int tabIndex) override; - - int getNumVisibleTabs(); - - ComponentAnimator ghostTabAnimator; - -private: - TabComponent& owner; - - class GhostTab; - std::unique_ptr ghostTab; - int ghostTabIdx = -1; - bool inOtherSplit = false; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ButtonBar) -}; - -class TabComponent : public Component - , public AsyncUpdater { - - MainToolbarButton newButton = MainToolbarButton(Icons::Add); - - PluginEditor* editor; - -public: - TabComponent(PluginEditor* editor); - ~TabComponent() override; - - void onTabChange(int tabIndex); - void newTab(); - void addTab(String const& tabName, Component* contentComponent, int insertIndex); - void moveTab(int oldIndex, int newIndex); - void clearTabs(); - void setTabBarDepth(int newDepth); - Component* getTabContentComponent(int tabIndex) const noexcept; - Component* getCurrentContentComponent() const noexcept { return panelComponent.get(); } - int getCurrentTabIndex(); - void setCurrentTabIndex(int idx); - void setCanvasActive(Canvas* cnv); - int getNumTabs() const noexcept { return tabs->getNumTabs(); } - int getNumVisibleTabs(); - void removeTab(int idx); - int getTabBarDepth() const noexcept { return tabDepth; } - void changeCallback(int newCurrentTabIndex, String const& newTabName); - - void currentTabChanged(int newCurrentTabIndex, String const& newCurrentTabName); - void handleAsyncUpdate() override; - void resized() override; - - int getIndexOfCanvas(Canvas* cnv); - - void setTabText(int tabIndex, String const& newName); - String getTabText(int tabIndex); - - Canvas* getCanvas(int idx); - - Canvas* getCurrentCanvas(); - - void setFocused(); - - PluginEditor* getEditor(); - - Image tabSnapshot; - ScaledImage tabSnapshotScaled; - -private: - - int tabDepth = 30; - - friend ButtonBar; - - Array> contentComponents; - std::unique_ptr tabs; - WeakReference panelComponent; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TabComponent) -}; diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Tabbar/WelcomePanel.h index ba3e1ecda4..050bc8c47d 100644 --- a/Source/Tabbar/WelcomePanel.h +++ b/Source/Tabbar/WelcomePanel.h @@ -8,6 +8,7 @@ #include "Utility/Autosave.h" #include "Utility/CachedTextRender.h" #include "Utility/NanoVGGraphicsContext.h" +#include "Components/BouncingViewport.h" class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater { @@ -216,8 +217,8 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater newPatchTile = std::make_unique("New Patch", "Create a new empty patch", newIcon, findColour(PlugDataColour::panelTextColourId), 0.33f, false); openPatchTile = std::make_unique("Open Patch", "Browse for a patch to open", openIcon, findColour(PlugDataColour::panelTextColourId), 0.33f, false); - newPatchTile->onClick = [this]() { editor->newProject(); }; - openPatchTile->onClick = [this](){ editor->openProject(); }; + newPatchTile->onClick = [this]() { editor->getTabComponent().newPatch(); }; + openPatchTile->onClick = [this](){ editor->getTabComponent().openPatch(); }; addAndMakeVisible(*newPatchTile); addAndMakeVisible(*openPatchTile); @@ -253,7 +254,7 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater auto* tile = tiles.add(new WelcomePanelTile(patchFile.getFileName(), timeDescription, silhoutteSvg, snapshotColour, 1.0f, favourited)); tile->onClick = [this, patchFile]() mutable { editor->autosave->checkForMoreRecentAutosave(patchFile, [this, patchFile]() { - editor->pd->loadPatch(URL(patchFile), editor); + editor->getTabComponent().openPatch(URL(patchFile)); SettingsFile::getInstance()->addToRecentlyOpened(patchFile); editor->pd->titleChanged(); }); diff --git a/Source/Utility/Autosave.h b/Source/Utility/Autosave.h index 439f9f1ff2..6b27802287 100644 --- a/Source/Utility/Autosave.h +++ b/Source/Utility/Autosave.h @@ -1,6 +1,7 @@ #pragma once #include #include "Dialogs/Dialogs.h" +#include "Components/BouncingViewport.h" class Autosave : public Timer , public AsyncUpdater @@ -193,6 +194,7 @@ class AutosaveHistoryComponent : public Component { auto patch = editor->pd->loadPatch(String::fromUTF8(static_cast(ostream.getData()), ostream.getDataSize()), editor); patch->setTitle(patchPath.fromLastOccurrenceOf("/", false, false)); patch->setCurrentFile(URL(patchPath)); + editor->getTabComponent().update(); MessageManager::callAsync([editor]() { // Close the whole chain of dialogs diff --git a/Source/Utility/SplitModeEnum.h b/Source/Utility/SplitModeEnum.h deleted file mode 100644 index d33f1bf6c2..0000000000 --- a/Source/Utility/SplitModeEnum.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -namespace Split { -enum SplitMode { - None, - Horizontal, - Vertical -}; -} \ No newline at end of file diff --git a/Source/Utility/ValueTreeNodeBranchLine.cpp b/Source/Utility/ValueTreeNodeBranchLine.cpp deleted file mode 100644 index 6b414c3abd..0000000000 --- a/Source/Utility/ValueTreeNodeBranchLine.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// -// Created by alexmitchell on 17/01/24. -// - -#include "ValueTreeNodeBranchLine.h" -#include "ValueTreeViewer.h" - -void ValueTreeNodeBranchLine::mouseUp(MouseEvent const& e) -{ - // single click to collapse directory / node - if (e.getNumberOfClicks() == 1) { - node->closeNode(); - auto nodePos = node->getPositionInViewport(); - auto* viewerComponent = node->findParentComponentOfClass(); - auto mousePosInViewport = e.getEventRelativeTo(&viewerComponent->getViewport()).getPosition().getY(); - - // option to set the position of the collapsed node to top of view (not at mouse) - //viewerComponent->getViewport().setViewPosition(0, nodePos); - - viewerComponent->getViewport().setViewPosition(0, nodePos - mousePosInViewport + (node->getHeight() * 0.5f)); - viewerComponent->repaint(); - } -} - -void ValueTreeNodeBranchLine::paint(Graphics& g) -{ - if (!treeLine.isEmpty()) { - auto colour = (isHover && !node->isOpenInSearchMode()) ? findColour(PlugDataColour::objectSelectedOutlineColourId) : findColour(PlugDataColour::panelTextColourId).withAlpha(0.25f); - - g.reduceClipRegion(treeLineImage, AffineTransform()); - g.fillAll(colour); - } -} \ No newline at end of file diff --git a/Source/Utility/ValueTreeNodeBranchLine.h b/Source/Utility/ValueTreeNodeBranchLine.h deleted file mode 100644 index bc62e61513..0000000000 --- a/Source/Utility/ValueTreeNodeBranchLine.h +++ /dev/null @@ -1,69 +0,0 @@ -// -// Created by alexmitchell on 17/01/24. -// - -#pragma once - -#include -#include "Constants.h" - -class ValueTreeNodeComponent; -class ValueTreeNodeBranchLine : public Component, public SettableTooltipClient -{ -public: - explicit ValueTreeNodeBranchLine(ValueTreeNodeComponent* node) : node(node) - { - } - - void paint(Graphics& g) override; - - void mouseEnter(const MouseEvent& e) override - { - isHover = true; - repaint(); - } - - void mouseExit(const MouseEvent& e) override - { - isHover = false; - repaint(); - } - - void mouseMove(const MouseEvent& e) override - { - if (!isHover) { - isHover = true; - repaint(); - } - } - - void mouseUp(const MouseEvent& e) override; - - void resized() override - { - treeLine.clear(); - - if (getParentComponent()->isVisible()) { - // create a line to show the current branch - auto b = getLocalBounds(); - auto lineEnd = Point(4.0f, b.getHeight() - 3.0f); - treeLine.startNewSubPath(4.0f, 0.0f); - treeLine.lineTo(lineEnd); - - if(!b.isEmpty()) { - treeLineImage = Image(Image::PixelFormat::ARGB, b.getWidth(), b.getHeight(), true); - Graphics treeLineG(treeLineImage); - treeLineG.setColour(Colours::white); - treeLineG.strokePath(treeLine, PathStrokeType(1.0f)); - auto ballEnd = Rectangle(0, 0, 5, 5).withCentre(lineEnd); - treeLineG.fillEllipse(ballEnd); - } - } - } - -private: - ValueTreeNodeComponent* node; - Path treeLine; - Image treeLineImage; - bool isHover = false; -}; diff --git a/Source/Utility/ValueTreeViewer.h b/Source/Utility/ValueTreeViewer.h index 58c19f4bca..81a0f08a1e 100644 --- a/Source/Utility/ValueTreeViewer.h +++ b/Source/Utility/ValueTreeViewer.h @@ -10,12 +10,93 @@ struct ValueTreeOwnerView : public Component std::function onDragStart = [](ValueTree&){}; }; -#include "ValueTreeNodeBranchLine.h" #include "Fonts.h" #include "../Components/BouncingViewport.h" class ValueTreeNodeComponent : public Component { + class ValueTreeNodeBranchLine : public Component, public SettableTooltipClient + { + public: + explicit ValueTreeNodeBranchLine(ValueTreeNodeComponent* node) : node(node) + { + } + + void paint(Graphics& g) override + { + if (!treeLine.isEmpty()) { + auto colour = (isHover && !node->isOpenInSearchMode()) ? findColour(PlugDataColour::objectSelectedOutlineColourId) : findColour(PlugDataColour::panelTextColourId).withAlpha(0.25f); + + g.reduceClipRegion(treeLineImage, AffineTransform()); + g.fillAll(colour); + } + } + + + void mouseEnter(const MouseEvent& e) override + { + isHover = true; + repaint(); + } + + void mouseExit(const MouseEvent& e) override + { + isHover = false; + repaint(); + } + + void mouseMove(const MouseEvent& e) override + { + if (!isHover) { + isHover = true; + repaint(); + } + } + + void mouseUp(MouseEvent const& e) override + { + // single click to collapse directory / node + if (e.getNumberOfClicks() == 1) { + node->closeNode(); + auto nodePos = node->getPositionInViewport(); + auto* viewport = node->findParentComponentOfClass(); + auto mousePosInViewport = e.getEventRelativeTo(viewport).getPosition().getY(); + + viewport->setViewPosition(0, nodePos - mousePosInViewport + (node->getHeight() * 0.5f)); + viewport->repaint(); + } + } + + void resized() override + { + treeLine.clear(); + + if (getParentComponent()->isVisible()) { + // create a line to show the current branch + auto b = getLocalBounds(); + auto lineEnd = Point(4.0f, b.getHeight() - 3.0f); + treeLine.startNewSubPath(4.0f, 0.0f); + treeLine.lineTo(lineEnd); + + if(!b.isEmpty()) { + treeLineImage = Image(Image::PixelFormat::ARGB, b.getWidth(), b.getHeight(), true); + Graphics treeLineG(treeLineImage); + treeLineG.setColour(Colours::white); + treeLineG.strokePath(treeLine, PathStrokeType(1.0f)); + auto ballEnd = Rectangle(0, 0, 5, 5).withCentre(lineEnd); + treeLineG.fillEllipse(ballEnd); + } + } + } + + private: + ValueTreeNodeComponent* node; + Path treeLine; + Image treeLineImage; + bool isHover = false; + }; + + public: ValueTreeNodeComponent(const ValueTree& node, ValueTreeNodeComponent* parentNode, String const& prepend = String()) : valueTreeNode(node), parent(parentNode) { diff --git a/Source/Components/ZoomableDragAndDropContainer.cpp b/Source/Utility/ZoomableDragAndDropContainer.cpp similarity index 89% rename from Source/Components/ZoomableDragAndDropContainer.cpp rename to Source/Utility/ZoomableDragAndDropContainer.cpp index a4ae03e639..323646f7dc 100644 --- a/Source/Components/ZoomableDragAndDropContainer.cpp +++ b/Source/Utility/ZoomableDragAndDropContainer.cpp @@ -23,25 +23,14 @@ ============================================================================== */ #include "ZoomableDragAndDropContainer.h" -#include "Utility/RateReducer.h" +#include "RateReducer.h" #include "Constants.h" #include "LookAndFeel.h" -// this is to find if we are over a split, and if so, we zoom the dragged image to the canvas zoom value -#include "Tabbar/ResizableTabbedComponent.h" - -// if the drag is over a tab bar, then we don't show the drag image -#include "Tabbar/Tabbar.h" - -// to reset the active split to what it was originally -#include "Tabbar/TabBarButtonComponent.h" - -// if we are inside the splitview, don't reset the scale of the dragged image, regardless of what we are over -#include "Tabbar/SplitView.h" // objects are only drag and dropped onto a canvas, so we dynamic cast straight away to see if the dragged object is from an object -#include "ObjectDragAndDrop.h" +#include "Components/ObjectDragAndDrop.h" bool juce_performDragDropFiles(StringArray const&, bool const copyFiles, bool& shouldStop); bool juce_performDragDropText(String const&, bool& shouldStop); @@ -131,10 +120,12 @@ class ZoomableDragAndDropContainer::DragImageComponent : public Component if (finalTarget != nullptr) { currentlyOverComp = nullptr; finalTarget->itemDropped(details); - } else if (auto* tab = dynamic_cast(details.sourceComponent.get())) { + } + /* TODO: split refactor + else if (auto* tab = dynamic_cast(details.sourceComponent.get())) { if (ProjectInfo::isStandalone) owner.createNewWindow(tab); - } + } */ // careful - this object could now be deleted.. } } @@ -162,36 +153,29 @@ class ZoomableDragAndDropContainer::DragImageComponent : public Component } } - if (target == previousTarget) { - return; - } else { - previousTarget = target; - } - - if (isZoomable) { - auto* split = dynamic_cast(target); + auto* tabbar = dynamic_cast(target); + + if (tabbar && isZoomable) { if (newTarget) { - if (split && split->getTabComponent() && split->getTabComponent()->getCurrentCanvas()) { - auto zoomScale = ::getValue(split->getTabComponent()->getCurrentCanvas()->zoomScale); + if (tabbar->getCurrentCanvas()) { + auto zoomScale = ::getValue(tabbar->getCurrentCanvas()->zoomScale); updateScale(zoomScale, true); return; } } - if (auto splitView = owner.getSplitView()) { - // don't reset the scale of the dragged image if we are inside of the splitview area - // there are some objects (splitview resizer, and edges of canvas objects) that - // register as a target that's null. - // this is a fix for that - if (splitView->getScreenBounds().contains(currentScreenPos.toInt())) { - return; - } + + if (tabbar->getScreenBounds().contains(currentScreenPos.toInt())) { + return; } } - if (dynamic_cast(target)) { + + if(tabbar && e.getEventRelativeTo(tabbar).y < 30) + { updateScale(0.0f, true); - return; } - updateScale(1.0f, true); + else { + updateScale(1.0f, true); + } } } @@ -235,12 +219,12 @@ class ZoomableDragAndDropContainer::DragImageComponent : public Component } void updateScale(float newScale, bool withAnimation) - { + { if (approximatelyEqual(newScale, previousScale)) return; previousScale = newScale; - + auto newWidth = image.getScaledBounds().getWidth() * newScale; auto newHeight = image.getScaledBounds().getHeight() * newScale; auto zoomedImageBounds = getLocalBounds().withSizeKeepingCentre(newWidth, newHeight); @@ -681,10 +665,6 @@ void ZoomableDragAndDropContainer::dragOperationStarted(DragAndDropTarget::Sourc void ZoomableDragAndDropContainer::dragOperationEnded(DragAndDropTarget::SourceDetails const& sourceComponent) { - // dragOperationEnded only runs when the DnD is unsuccessful - if (auto* tab = dynamic_cast(sourceComponent.sourceComponent.get())) { - tab->setFocusForTabSplit(); - } } MouseInputSource const* ZoomableDragAndDropContainer::getMouseInputSourceForDrag(Component* sourceComponent, diff --git a/Source/Components/ZoomableDragAndDropContainer.h b/Source/Utility/ZoomableDragAndDropContainer.h similarity index 96% rename from Source/Components/ZoomableDragAndDropContainer.h rename to Source/Utility/ZoomableDragAndDropContainer.h index 1c65c459fb..6f916789a0 100644 --- a/Source/Components/ZoomableDragAndDropContainer.h +++ b/Source/Utility/ZoomableDragAndDropContainer.h @@ -49,8 +49,8 @@ @tags{GUI} */ -class TabBarButtonComponent; -class SplitView; +class Canvas; +class TabComponent; class ZoomableDragAndDropContainer { public: /** Creates a ZoomableDragAndDropContainer. @@ -175,9 +175,9 @@ class ZoomableDragAndDropContainer { */ static ZoomableDragAndDropContainer* findParentDragContainerFor(Component* childComponent); - virtual void createNewWindow(TabBarButtonComponent* tabButton) { } + virtual void createNewWindow(Canvas* tabButton) { } - virtual SplitView* getSplitView() { return nullptr; } + virtual TabComponent& getTabComponent() = 0; /** This performs an asynchronous drag-and-drop of a set of files to some external application. From 9c1e4e6209019a3554e67127aeff5907d7f5091f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Wed, 5 Jun 2024 18:26:01 +0200 Subject: [PATCH 0873/1030] Small fixes --- Source/LookAndFeel.cpp | 220 --------------------------------- Source/Objects/GraphOnParent.h | 15 ++- Source/PluginEditor.cpp | 15 +-- Source/PluginMode.h | 2 - Source/PluginProcessor.cpp | 2 +- Source/TabComponent.cpp | 138 ++++++++++++++++++++- Source/TabComponent.h | 15 ++- Source/Utility/Autosave.h | 2 +- 8 files changed, 152 insertions(+), 257 deletions(-) diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index 80143ac2bd..1d6c4a2626 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -467,226 +467,6 @@ void PlugDataLook::positionDocumentWindowButtons(DocumentWindow& window, } } -/* -Rectangle PlugDataLook::getTabButtonExtraComponentBounds(TabBarButton const& button, Rectangle& textArea, Component& comp) -{ - Rectangle extraComp; - - auto area = textArea.reduced(4); - - auto orientation = button.getTabbedButtonBar().getOrientation(); - - if (button.getExtraComponentPlacement() == TabBarButton::beforeText) { - switch (orientation) { - case TabbedButtonBar::TabsAtBottom: - case TabbedButtonBar::TabsAtTop: - extraComp = area.removeFromLeft(comp.getWidth()); - break; - case TabbedButtonBar::TabsAtLeft: - extraComp = area.removeFromBottom(comp.getHeight()); - break; - case TabbedButtonBar::TabsAtRight: - extraComp = area.removeFromTop(comp.getHeight()); - break; - default: - jassertfalse; - break; - } - } else { - switch (orientation) { - case TabbedButtonBar::TabsAtBottom: - case TabbedButtonBar::TabsAtTop: - extraComp = area.removeFromRight(comp.getWidth()); - break; - case TabbedButtonBar::TabsAtLeft: - extraComp = area.removeFromTop(comp.getHeight()); - break; - case TabbedButtonBar::TabsAtRight: - extraComp = area.removeFromBottom(comp.getHeight()); - break; - default: - jassertfalse; - break; - } - } - - return extraComp; -} - -Button* PlugDataLook::createTabBarExtrasButton() -{ - - class TabBarExtrasButton : public TextButton { - public: - TabBarExtrasButton() - { - setButtonText(Icons::ThinDown); - setTriggeredOnMouseDown(true); - } - - void moved() override - { - static bool insideMove = false; - if (insideMove) - return; - - if (auto* parent = getParentComponent()) { - insideMove = true; - auto position = parent->getLocalBounds().getTopRight() - Point(28, 0); - setTopLeftPosition(position); - insideMove = false; - } - } - - void resized() override - { - // Try to force the size to 28x28, while also not allowing any recursion - // JUCE gives us very little control over the position/size otherwise... - static bool insideResize = false; - if (insideResize) - return; - insideResize = true; - setSize(28, 28); - insideResize = false; - } - - void paint(Graphics& g) override - { - bool hiddenTabSelected = false; - if (auto* tabbar = findParentComponentOfClass()) { - - auto currentTabIndex = tabbar->getCurrentTabIndex(); - if (currentTabIndex >= 0) { - auto* currentTab = tabbar->getTabButton(currentTabIndex); - hiddenTabSelected = !currentTab->isVisible(); - } - } - - if (isMouseOverOrDragging() || hiddenTabSelected) { - g.setColour(findColour(PlugDataColour::toolbarHoverColourId)); - fillSmoothedRectangle(g, getLocalBounds().reduced(3).toFloat(), Corners::defaultCornerRadius); - } else { - g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); - } - - g.setFont(Fonts::getIconFont().withHeight(15)); - g.setColour(findColour(PlugDataColour::toolbarTextColourId)); - - g.drawText(getButtonText(), getLocalBounds().reduced(3), Justification::centred); - } - - void mouseDown(MouseEvent const& e) override - { - // TODO: split refactor fix - class HiddenTabMenuItem : public PopupMenu::CustomComponent { - - String tabTitle; - - public: - int index; - TabbedButtonBar& tabbar; - - HiddenTabMenuItem(String const& text, int idx, TabbedButtonBar& buttonBar) - : tabTitle(text) - , index(idx) - , tabbar(buttonBar) - { - closeTabButton.setButtonText(Icons::Clear); - closeTabButton.getProperties().set("Style", "Icon"); - closeTabButton.getProperties().set("FontScale", 0.44f); - closeTabButton.setColour(TextButton::buttonColourId, Colour()); - closeTabButton.setColour(TextButton::buttonOnColourId, Colour()); - closeTabButton.setColour(ComboBox::outlineColourId, Colour()); - closeTabButton.setConnectedEdges(12); - closeTabButton.setSize(26, 26); - closeTabButton.addMouseListener(this, false); - closeTabButton.onClick = [this]() mutable { - dynamic_cast(tabbar.getTabButton(index))->closeTab(); - }; - - addChildComponent(closeTabButton); - } - - void resized() override - { - closeTabButton.setTopLeftPosition(getWidth() - 26, -2); - } - - void getIdealSize(int& idealWidth, int& idealHeight) override - { - idealWidth = 150; - idealHeight = 24; - } - - void mouseDown(MouseEvent const& e) override - { - if (e.originalComponent == &closeTabButton) - return; - - tabbar.setCurrentTabIndex(index); - triggerMenuItem(); - } - - void mouseEnter(MouseEvent const& e) override - { - closeTabButton.setVisible(true); - } - - void mouseExit(MouseEvent const& e) override - { - closeTabButton.setVisible(false); - } - - void paint(Graphics& g) override - { - bool isActive = tabbar.getCurrentTabIndex() == index; - - if (isActive) { - g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId)); - } else if (isItemHighlighted()) { - g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId).interpolatedWith(findColour(PlugDataColour::popupMenuBackgroundColourId), 0.4f)); - } else { - g.setColour(findColour(PlugDataColour::popupMenuBackgroundColourId)); - } - - fillSmoothedRectangle(g, getLocalBounds().reduced(1).toFloat(), Corners::defaultCornerRadius); - - auto area = getLocalBounds().reduced(4, 1).toFloat(); - - Font font = Font(14); - - g.setColour(findColour(TabbedButtonBar::tabTextColourId)); - g.setFont(font); - g.drawText(tabTitle.trim(), area.reduced(4, 0), Justification::centred, false); - } - - TextButton closeTabButton; - }; - - if (auto* parent = findParentComponentOfClass()) { - PopupMenu m; - - auto tabNames = parent->getTabNames(); - for (int i = 0; i < parent->getNumTabs(); ++i) { - auto* tab = parent->getTabButton(i); - - if (!tab->isVisible()) { - m.addCustomItem(i + 1, std::make_unique(tabNames[i], i, *parent), nullptr, tabNames[i]); - } - - //m.addItem (PopupMenu::Item (tabNames[i]).setTicked (i == parent->getCurrentTabIndex()) .setAction ([this, i, parent] { parent->setCurrentTabIndex (i); })); - } - - m.showMenuAsync(PopupMenu::Options() - .withDeletionCheck(*this) - .withTargetComponent(this)); - } - } - }; - - return new TabBarExtrasButton(); -} */ - Font PlugDataLook::getTabButtonFont(TabBarButton&, float height) { return Fonts::getCurrentFont().withHeight(13.5f); diff --git a/Source/Objects/GraphOnParent.h b/Source/Objects/GraphOnParent.h index 6509afe34d..273215a385 100644 --- a/Source/Objects/GraphOnParent.h +++ b/Source/Objects/GraphOnParent.h @@ -173,15 +173,14 @@ class GraphOnParent final : public ObjectBase { void tabChanged() override { isOpenedInSplitView = false; - /* TODO: split restucture! - for (auto* split : cnv->editor->splitView.splits) { - if (auto* cnv = split->getTabComponent().getCurrentCanvas()) { - if (cnv->patch == *getPatch()) { - isOpenedInSplitView = true; - } + for (auto* visibleCanvas : cnv->editor->getTabComponent().getVisibleCanvases()) { + if(visibleCanvas->patch == *getPatch()) + { + isOpenedInSplitView = true; + break; } - } */ - + } + updateCanvas(); repaint(); } diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index c17c9ed4b6..934646e9ba 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -779,20 +779,7 @@ void PluginEditor::modifierKeysChanged(ModifierKeys const& modifiers) // Updates command status asynchronously void PluginEditor::handleAsyncUpdate() { - // Reflect patch dirty state in tab title - /* TODO: split restucture fix - for (auto split : splitView.splits) { - auto tabbar = split->getTabComponent(); - for (int n = 0; n < tabbar->getNumTabs(); n++) { - auto* cnv = tabbar->getCanvas(n); - if (!cnv) - return; - - auto isDirty = cnv->patch.isDirty(); - auto tabText = tabbar->getTabText(n); - tabbar->setTabText(n, tabText.trimCharactersAtEnd("*") + (isDirty ? "*" : "")); - } - } */ + tabComponent.repaint(); // So tab dirty titles can be reflected if (auto* cnv = getCurrentCanvas()) { bool locked = getValue(cnv->locked); diff --git a/Source/PluginMode.h b/Source/PluginMode.h index 6793e619a4..81792a2930 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -186,8 +186,6 @@ class PluginMode : public Component, public NVGComponent { editor->setBounds(windowBounds); } - editor->getTabComponent().resized(); - if (originalCanvas) { // Reset the canvas properties to before plugin mode was entered originalCanvas->patch.openInPluginMode = false; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 160f9ff5b1..6876538f8e 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1909,7 +1909,7 @@ void PluginProcessor::reloadAbstractions(File changedPatch, t_glist* except) void PluginProcessor::titleChanged() { for (auto* editor : getEditors()) { - editor->getTabComponent().update(); + editor->getTabComponent().triggerAsyncUpdate(); } } diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 09c98bfb3a..3aec8358b0 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -17,6 +17,11 @@ TabComponent::TabComponent(PluginEditor* editor) : editor(editor), pd(editor->pd activeSplitIndex = i; newPatch(); }; + + addChildComponent(tabOverflowButtons[i]); + tabOverflowButtons[i].onClick = [this, i](){ + showHiddenTabsMenu(i); + }; } addMouseListener(this, true); @@ -70,7 +75,7 @@ Canvas* TabComponent::openPatch(pd::Patch::Ptr existingPatch) existingPatch->splitViewIndex = activeSplitIndex; - update(); + triggerAsyncUpdate(); pd->titleChanged(); showTab(cnv, activeSplitIndex); @@ -140,8 +145,7 @@ void TabComponent::previousTab() { } } - -void TabComponent::update() +void TabComponent::handleAsyncUpdate() { tabbars[0].clear(); tabbars[1].clear(); @@ -347,7 +351,7 @@ void TabComponent::mouseMove(const MouseEvent& e) void TabComponent::parentSizeChanged() { - // TODO: keep split proportion? + // TODO: split, keep proportion? splitSize = getWidth() / 2; } @@ -363,10 +367,33 @@ void TabComponent::resized() auto& tabButtons = tabbars[i]; auto splitBounds = tabbarBounds.removeFromLeft((isSplit && i == 0) ? splitSize : getWidth()); newTabButtons[i].setBounds(splitBounds.removeFromLeft(30)); + + // TODO: use a better formula for overflow + auto totalWidth = splitBounds.getWidth(); auto tabWidth = splitBounds.getWidth() / std::max(1, tabButtons.size()); + auto minTabWidth = 120; + + if(minTabWidth * tabButtons.size() > totalWidth) + { + int tabsThatFit = totalWidth / 150; + tabWidth = (totalWidth - 30) / std::max(1, std::min(static_cast(tabButtons.size()), tabsThatFit)); + } + + bool wasOverflown = false; for(auto* tabButton : tabButtons) { + if(tabWidth > splitBounds.getWidth()) + { + wasOverflown = true; + } + if(wasOverflown) + { + tabButton->setVisible(false); + continue; + } + tabButton->setVisible(true); + auto targetBounds = splitBounds.removeFromLeft(tabWidth); if(tabButton->isDragging) { tabButton->setSize(tabWidth, 30); @@ -384,6 +411,9 @@ void TabComponent::resized() tabButton->setBounds(targetBounds); } } + + tabOverflowButtons[i].setVisible(wasOverflown); + tabOverflowButtons[i].setBounds(splitBounds.removeFromRight(wasOverflown ? 30 : 0)); } for(auto& split : splits) @@ -413,7 +443,7 @@ void TabComponent::closeTab(Canvas* cnv) { pd->patches.removeFirstMatchingValue(patch); pd->updateObjectImplementations(); - update(); + triggerAsyncUpdate(); } @@ -649,3 +679,101 @@ void TabComponent::itemDragMove(SourceDetails const& dragSourceDetails) editor->nvgSurface.invalidateAll(); } } + +void TabComponent::showHiddenTabsMenu(int splitIndex) { + + class HiddenTabMenuItem : public PopupMenu::CustomComponent { + + String tabTitle; + SafePointer cnv; + + public: + TabComponent& tabbar; + + HiddenTabMenuItem(SafePointer canvas, String const& text, TabComponent& tabs) + : tabTitle(text) + , cnv(canvas) + , tabbar(tabs) + { + closeTabButton.setButtonText(Icons::Clear); + closeTabButton.setSize(26, 26); + closeTabButton.addMouseListener(this, false); + closeTabButton.onClick = [this]() mutable { + tabbar.closeTab(cnv.getComponent()); + }; + + addChildComponent(closeTabButton); + } + + void resized() override + { + closeTabButton.setTopLeftPosition(getWidth() - 26, -2); + } + + void getIdealSize(int& idealWidth, int& idealHeight) override + { + idealWidth = 150; + idealHeight = 24; + } + + void mouseDown(MouseEvent const& e) override + { + if (e.originalComponent == &closeTabButton) + return; + + tabbar.showTab(cnv); + triggerMenuItem(); + } + + void mouseEnter(MouseEvent const& e) override + { + closeTabButton.setVisible(true); + } + + void mouseExit(MouseEvent const& e) override + { + closeTabButton.setVisible(false); + } + + void paint(Graphics& g) override + { + bool isActive = tabbar.getVisibleCanvases().contains(cnv); + + if (isActive) { + g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId)); + } else if (isItemHighlighted()) { + g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId).interpolatedWith(findColour(PlugDataColour::popupMenuBackgroundColourId), 0.4f)); + } else { + g.setColour(findColour(PlugDataColour::popupMenuBackgroundColourId)); + } + + PlugDataLook::fillSmoothedRectangle(g, getLocalBounds().reduced(1).toFloat(), Corners::defaultCornerRadius); + + auto area = getLocalBounds().reduced(4, 1).toFloat(); + + Font font = Font(14); + + g.setColour(findColour(TabbedButtonBar::tabTextColourId)); + g.setFont(font); + g.drawText(tabTitle.trim(), area.reduced(4, 0), Justification::centred, false); + } + + SmallIconButton closeTabButton; + }; + + PopupMenu m; + + + auto& tabbar = tabbars[splitIndex]; + for (int i = 0; i < tabbar.size(); ++i) { + auto* tab = tabbar[i]; + if (!tab->isVisible()) { + auto title = tab->cnv->patch.getTitle(); + m.addCustomItem(i + 1, std::make_unique(tab->cnv, title, *this), nullptr, title); + } + } + + m.showMenuAsync(PopupMenu::Options() + .withDeletionCheck(*this) + .withTargetComponent(&tabOverflowButtons[splitIndex])); +} diff --git a/Source/TabComponent.h b/Source/TabComponent.h index 0589c189c5..bf12826461 100644 --- a/Source/TabComponent.h +++ b/Source/TabComponent.h @@ -3,7 +3,7 @@ #include "Utility/ZoomableDragAndDropContainer.h" class TabBarButtonComponent; -class TabComponent : public Component, public DragAndDropTarget +class TabComponent : public Component, public DragAndDropTarget, public AsyncUpdater { public: TabComponent(PluginEditor* editor); @@ -15,8 +15,6 @@ class TabComponent : public Component, public DragAndDropTarget Canvas* openPatch(pd::Patch::Ptr existingPatch); void openPatch(); - void update(); - void renderArea(NVGcontext* nvg, Rectangle bounds); void nextTab(); @@ -34,11 +32,13 @@ class TabComponent : public Component, public DragAndDropTarget Array getCanvases(); Array getVisibleCanvases(); +private: + + void handleAsyncUpdate() override; + void resized() override; void parentSizeChanged() override; -private: - void saveTabPositions(); void closeEmptySplits(); @@ -53,6 +53,8 @@ class TabComponent : public Component, public DragAndDropTarget void mouseDrag(const MouseEvent& e) override; void mouseMove(const MouseEvent& e) override; + void showHiddenTabsMenu(int splitIndex); + class TabBarButtonComponent : public Component { @@ -135,7 +137,7 @@ class TabComponent : public Component, public DragAndDropTarget auto textColour = findColour(PlugDataColour::toolbarTextColourId); g.setGradientFill(ColourGradient(textColour, fadeX - 18, area.getY(), Colours::transparentBlack, fadeX, area.getY(), false)); - auto text = cnv->patch.getTitle(); + auto text = cnv->patch.getTitle() + (cnv->patch.isDirty() ? String("*") : String()); g.setFont(Fonts::getCurrentFont().withHeight(14.0f)); g.drawText(text, area.reduced(4, 0), Justification::centred, false); @@ -224,6 +226,7 @@ class TabComponent : public Component, public DragAndDropTarget }; std::array newTabButtons = {MainToolbarButton(Icons::Add), MainToolbarButton(Icons::Add)}; + std::array tabOverflowButtons = {MainToolbarButton(Icons::ThinDown), MainToolbarButton(Icons::ThinDown)}; std::array, 2> tabbars; std::array, 2> splits = {nullptr, nullptr}; diff --git a/Source/Utility/Autosave.h b/Source/Utility/Autosave.h index 6b27802287..2390532b39 100644 --- a/Source/Utility/Autosave.h +++ b/Source/Utility/Autosave.h @@ -194,7 +194,7 @@ class AutosaveHistoryComponent : public Component { auto patch = editor->pd->loadPatch(String::fromUTF8(static_cast(ostream.getData()), ostream.getDataSize()), editor); patch->setTitle(patchPath.fromLastOccurrenceOf("/", false, false)); patch->setCurrentFile(URL(patchPath)); - editor->getTabComponent().update(); + editor->getTabComponent().triggerAsyncUpdate(); MessageManager::callAsync([editor]() { // Close the whole chain of dialogs From c1745427675db5b7eca149c5fd7d2112115f0725 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 01:03:48 +0200 Subject: [PATCH 0874/1030] More fixes, multi-window support --- Source/Pd/Patch.h | 1 + Source/PluginEditor.cpp | 80 +----- Source/PluginEditor.h | 5 - Source/Standalone/PlugDataApp.cpp | 4 +- Source/TabComponent.cpp | 241 ++++++++++-------- Source/TabComponent.h | 105 +++++++- .../Utility/ZoomableDragAndDropContainer.cpp | 10 +- Source/Utility/ZoomableDragAndDropContainer.h | 2 - 8 files changed, 243 insertions(+), 205 deletions(-) diff --git a/Source/Pd/Patch.h b/Source/Pd/Patch.h index f11b790020..7da8fea688 100644 --- a/Source/Pd/Patch.h +++ b/Source/Pd/Patch.h @@ -120,6 +120,7 @@ class Patch : public ReferenceCountedObject { bool closePatchOnDelete; bool openInPluginMode = false; int splitViewIndex = 0; + int windowIndex = 0; String lastUndoSequence; String lastRedoSequence; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 934646e9ba..1be18a7c27 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -375,12 +375,18 @@ void PluginEditor::renderArea(NVGcontext* nvg, Rectangle area) nvgRestore(nvg); } } - else + else if(!tabComponent.isUpdatePending()) { nvgSave(nvg); welcomePanel->render(nvg); nvgRestore(nvg); } + else { + nvgBeginPath(nvg); + nvgRect(nvg, 0, 0, nvgSurface.getWidth(), nvgSurface.getHeight()); + nvgFillColor(nvg, NVGComponent::convertColour(findColour(PlugDataColour::canvasBackgroundColourId))); + nvgFill(nvg); + } } } @@ -674,31 +680,6 @@ TabComponent& PluginEditor::getTabComponent() return tabComponent; } -void PluginEditor::createNewWindow(Canvas* cnv) -{ - if (!ProjectInfo::isStandalone) - return; - - auto* newEditor = new PluginEditor(*pd); - auto* newWindow = ProjectInfo::createNewWindow(newEditor); - - auto* window = dynamic_cast(getTopLevelComponent()); - - pd->openedEditors.add(newEditor); - - newWindow->addToDesktop(window->getDesktopWindowStyleFlags()); - newWindow->setVisible(true); - - /* TODO: split refactor fix - auto* newCanvas = newEditor->getTabComponent().openPatch(subpatch); - newCanvas->jumpToOrigin(); */ - - newWindow->setTopLeftPosition(Desktop::getInstance().getMousePosition() - Point(500, 60)); - newWindow->toFront(true); - - tabComponent.closeTab(cnv); -} - bool PluginEditor::isActiveWindow() { bool isDraggingTab = ZoomableDragAndDropContainer::isDragAndDropActive(); @@ -715,53 +696,6 @@ Canvas* PluginEditor::getCurrentCanvas() return tabComponent.getCurrentCanvas(); } -void PluginEditor::closeAllTabs(bool quitAfterComplete, Canvas* patchToExclude, std::function afterComplete) -{ - if (!getCanvases().size()) { - afterComplete(); - if (quitAfterComplete) { - JUCEApplication::quit(); - } - return; - } - if (patchToExclude && getCanvases().size() == 1) { - afterComplete(); - return; - } - - auto canvas = SafePointer(getCanvases().getLast()); - - auto patch = canvas->refCountedPatch; - - auto deleteFunc = [this, canvas, quitAfterComplete, patchToExclude, afterComplete]() { - if (canvas && !(patchToExclude && canvas == patchToExclude)) { - tabComponent.closeTab(canvas); - } - - closeAllTabs(quitAfterComplete, patchToExclude, afterComplete); - }; - - if (canvas) { - MessageManager::callAsync([this, canvas, patch, deleteFunc]() mutable { - if (patch->isDirty()) { - Dialogs::showAskToSaveDialog( - &openedDialog, this, patch->getTitle(), - [canvas, deleteFunc](int result) mutable { - if (!canvas) - return; - if (result == 2) - canvas->save([deleteFunc]() mutable { deleteFunc(); }); - else if (result == 1) - deleteFunc(); - }, - 0, true); - } else { - deleteFunc(); - } - }); - } -} - void PluginEditor::valueChanged(Value& v) { // Update theme diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index ab84e7399d..646ca3c619 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -96,9 +96,6 @@ class PluginEditor : public AudioProcessorEditor void mouseDrag(MouseEvent const& e) override; void mouseDown(MouseEvent const& e) override; - void closeAllTabs( - bool quitAfterComplete = false, Canvas* patchToExclude = nullptr, std::function afterComplete = []() {}); - void quit(bool askToSave); Array getCanvases(); @@ -117,8 +114,6 @@ class PluginEditor : public AudioProcessorEditor void fileDragMove(StringArray const& files, int x, int y) override; void fileDragExit(StringArray const&) override; - void createNewWindow(Canvas* cnv) override; - TabComponent& getTabComponent() override; DragAndDropTarget* findNextDragAndDropTarget(Point screenPos) override; diff --git a/Source/Standalone/PlugDataApp.cpp b/Source/Standalone/PlugDataApp.cpp index 266df3a581..7dbc6dbbd3 100644 --- a/Source/Standalone/PlugDataApp.cpp +++ b/Source/Standalone/PlugDataApp.cpp @@ -262,13 +262,13 @@ void PlugDataWindow::closeAllPatches() } if (openedEditors.size() == 1) { - editor->closeAllTabs(true, nullptr, [this, editor, &openedEditors]() { + editor->getTabComponent().closeAllTabs(true, nullptr, [this, editor, &openedEditors]() { editor->nvgSurface.detachContext(); removeFromDesktop(); openedEditors.removeObject(editor); }); } else { - editor->closeAllTabs(false, nullptr, [this, editor, &openedEditors]() { + editor->getTabComponent().closeAllTabs(false, nullptr, [this, editor, &openedEditors]() { editor->nvgSurface.detachContext(); removeFromDesktop(); openedEditors.removeObject(editor); diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 3aec8358b0..fbfc94e988 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -7,6 +7,7 @@ #include "Utility/Autosave.h" #include "Components/ObjectDragAndDrop.h" #include "NVGSurface.h" +#include "Standalone/PlugDataWindow.h" TabComponent::TabComponent(PluginEditor* editor) : editor(editor), pd(editor->pd) { @@ -74,6 +75,7 @@ Canvas* TabComponent::openPatch(pd::Patch::Ptr existingPatch) cnv->locked.setValue(true); existingPatch->splitViewIndex = activeSplitIndex; + existingPatch->windowIndex = pd->getEditors().indexOf(editor); triggerAsyncUpdate(); pd->titleChanged(); @@ -86,6 +88,42 @@ Canvas* TabComponent::openPatch(pd::Patch::Ptr existingPatch) return cnv; } +void TabComponent::moveToLeftSplit(TabBarButtonComponent* tab) +{ + if(tabbars[1].size() && splits[1] && tabbars[1].indexOf(tab) >= 0) { + tabbars[0].add(tabbars[1].removeAndReturn(tabbars[1].indexOf(tab))); // Move tab to left tabbar + if(tabbars[1].size()) showTab(tabbars[1][0]->cnv, 1); // Show first tab of right tabbar, if there are any tabs left + else showTab(nullptr, 1); // If no tabs are left on right tabbar + + showTab(tab->cnv, 0); // Show moved tab in left tabbar + } + else if(tabbars[0].size() > 1 && splits[0] && !splits[1] && tabbars[0].indexOf(tab) >= 0) // If we try to create a left splits when there are no splits open + { + showTab(tab->cnv, 0); // Show dragged tab on left split + + // Move all other tabs to right split + for(int i = tabbars[0].size() - 1; i >= 0; i--) + { + if(tab != tabbars[0][i]) { + tabbars[0][i]->cnv->patch.splitViewIndex = 1; // Save split index + tabbars[1].insert(0, tabbars[0].removeAndReturn(i)); // Move to other split + } + } + + showTab(tabbars[1][0]->cnv, 1); // Show first tab of right split + } +} + +void TabComponent::moveToRightSplit(TabBarButtonComponent* tab) +{ + if(tabbars[0].size() && splits[0] && tabbars[0].indexOf(tab) >= 0) + { + tabbars[1].add(tabbars[0].removeAndReturn(tabbars[0].indexOf(tab))); // Move tab to right tabbar + if(tabbars[0].size()) showTab(tabbars[0][0]->cnv, 0); // Show first tab of left tabbar + showTab(tab->cnv, 1); // Show the moved tab on right tabbar + } +} + void TabComponent::openPatch() { Dialogs::showOpenDialog([this](URL resultURL) { @@ -114,14 +152,7 @@ void TabComponent::nextTab() { } auto newTabIndex = oldTabIndex + 1; - if(newTabIndex < tabbar.size()) - { - showTab(tabbar[newTabIndex]->cnv); - } - else { - showTab(tabbar[0]->cnv); - } -} + showTab(newTabIndex < tabbar.size() ? tabbar[newTabIndex]->cnv : tabbar[0]->cnv);} void TabComponent::previousTab() { auto splitIndex = activeSplitIndex && splits[1]; @@ -136,13 +167,34 @@ void TabComponent::previousTab() { } auto newTabIndex = oldTabIndex - 1; - if(newTabIndex >= 0) - { - showTab(tabbar[newTabIndex]->cnv); - } - else { - showTab(tabbar[tabbar.size() - 1]->cnv); - } + showTab(newTabIndex >= 0 ? tabbar[newTabIndex]->cnv : tabbar[tabbar.size() - 1]->cnv); +} + +void TabComponent::createNewWindow(Component* draggedTab) +{ + auto* tab = dynamic_cast(draggedTab); + + if (!tab || !ProjectInfo::isStandalone) return; + + auto* newEditor = new PluginEditor(*pd); + auto* newWindow = ProjectInfo::createNewWindow(newEditor); + auto* window = dynamic_cast(getTopLevelComponent()); + + pd->openedEditors.add(newEditor); + + newWindow->addToDesktop(window->getDesktopWindowStyleFlags()); + newWindow->setVisible(true); + + auto patch = tab->cnv->refCountedPatch; + closeTab(tab->cnv); + + patch->windowIndex = pd->getEditors().size() - 1; + + auto* newCanvas = newEditor->getTabComponent().openPatch(patch); + newCanvas->jumpToOrigin(); + + newWindow->setTopLeftPosition(Desktop::getInstance().getMousePosition() - Point(500, 60)); + newWindow->toFront(true); } void TabComponent::handleAsyncUpdate() @@ -150,9 +202,13 @@ void TabComponent::handleAsyncUpdate() tabbars[0].clear(); tabbars[1].clear(); + auto editorIndex = pd->getEditors().indexOf(editor); + // Load all patches from pd patch array for(auto& patch : pd->patches) { + if(patch->windowIndex != editorIndex) continue; + Canvas* cnv = nullptr; for(auto* canvas : canvases) { @@ -174,7 +230,6 @@ void TabComponent::handleAsyncUpdate() } closeEmptySplits(); - resized(); // Update tab and canvas layout } @@ -286,7 +341,8 @@ void TabComponent::renderArea(NVGcontext* nvg, Rectangle area) nvgFillColor(nvg, NVGComponent::convertColour(findColour(PlugDataColour::canvasBackgroundColourId))); nvgFill(nvg); - auto activeSplitBounds = splits[activeSplitIndex]->viewport->getBounds().translated(0, -30); + auto activeSplitBounds = activeSplitIndex ? Rectangle(splitSize, 0, getWidth() - splitSize, getHeight() - 31) : Rectangle(0, 0, splitSize, getHeight() - 31); + nvgBeginPath(nvg); nvgRect(nvg, activeSplitBounds.getX(), activeSplitBounds.getY(), activeSplitBounds.getWidth(), activeSplitBounds.getHeight()); nvgStrokeWidth(nvg, 3.0f); @@ -329,6 +385,7 @@ void TabComponent::mouseDrag(const MouseEvent& e) auto localPos = e.getEventRelativeTo(this).getPosition(); if(draggingSplitResizer) { splitSize = localPos.x; + splitProportion = getWidth() / static_cast(localPos.x); resized(); } } @@ -348,11 +405,9 @@ void TabComponent::mouseMove(const MouseEvent& e) } } - void TabComponent::parentSizeChanged() { - // TODO: split, keep proportion? - splitSize = getWidth() / 2; + splitSize = getWidth() / splitProportion; } void TabComponent::resized() @@ -368,10 +423,9 @@ void TabComponent::resized() auto splitBounds = tabbarBounds.removeFromLeft((isSplit && i == 0) ? splitSize : getWidth()); newTabButtons[i].setBounds(splitBounds.removeFromLeft(30)); - // TODO: use a better formula for overflow auto totalWidth = splitBounds.getWidth(); auto tabWidth = splitBounds.getWidth() / std::max(1, tabButtons.size()); - auto minTabWidth = 120; + auto minTabWidth = 75; if(minTabWidth * tabButtons.size() > totalWidth) { @@ -446,10 +500,50 @@ void TabComponent::closeTab(Canvas* cnv) { triggerAsyncUpdate(); } +void TabComponent::closeAllTabs(bool quitAfterComplete, Canvas* patchToExclude, std::function afterComplete) +{ + if (!canvases.size()) { + afterComplete(); + if (quitAfterComplete) { + JUCEApplication::quit(); + } + return; + } + if (patchToExclude && canvases.size() == 1) { + afterComplete(); + return; + } + + auto canvas = SafePointer(canvases.getLast()); + auto patch = canvas->refCountedPatch; + auto deleteFunc = [this, canvas, quitAfterComplete, patchToExclude, afterComplete]() { + if (canvas && !(patchToExclude && canvas == patchToExclude)) { + closeTab(canvas); + } -void TabComponent::closeAllTabs() { - editor->closeAllTabs(); + closeAllTabs(quitAfterComplete, patchToExclude, afterComplete); + }; + + if (canvas) { + MessageManager::callAsync([this, canvas, patch, deleteFunc]() mutable { + if (patch->isDirty()) { + Dialogs::showAskToSaveDialog( + &editor->openedDialog, this, patch->getTitle(), + [canvas, deleteFunc](int result) mutable { + if (!canvas) + return; + if (result == 2) + canvas->save([deleteFunc]() mutable { deleteFunc(); }); + else if (result == 1) + deleteFunc(); + }, + 0, true); + } else { + deleteFunc(); + } + }); + } } void TabComponent::setActiveSplit(Canvas* cnv) { @@ -461,10 +555,11 @@ void TabComponent::setActiveSplit(Canvas* cnv) { Canvas* TabComponent::getCanvasAtScreenPosition(Point screenPosition) { - auto x = getLocalPoint(nullptr, screenPosition).x; - auto split = x < splitSize || !splits[1]; + auto localPoint = getLocalPoint(nullptr, screenPosition); + if(!getLocalBounds().contains(localPoint)) return nullptr; + + auto split = localPoint.x < splitSize || !splits[1]; return split ? splits[0] : splits[1]; - // TODO: return nullptr if OOB } Array TabComponent::getVisibleCanvases() @@ -475,20 +570,9 @@ Array TabComponent::getVisibleCanvases() return result; } - bool TabComponent::isInterestedInDragSource(SourceDetails const& dragSourceDetails) { - if (dynamic_cast(dragSourceDetails.sourceComponent.get())) - { - return true; - } - - if(dynamic_cast(dragSourceDetails.sourceComponent.get())) - { - return true; - } - - return false; + return dynamic_cast(dragSourceDetails.sourceComponent.get()) || dynamic_cast(dragSourceDetails.sourceComponent.get()); } void TabComponent::itemDropped(SourceDetails const& dragSourceDetails) @@ -498,8 +582,7 @@ void TabComponent::itemDropped(SourceDetails const& dragSourceDetails) auto screenPosition = localPointToGlobal(dragSourceDetails.localPosition); auto* cnv = getCanvasAtScreenPosition(screenPosition); - if (!cnv) - return; + if (!cnv) return; auto mousePos = (cnv->getLocalPoint(this, dragSourceDetails.localPosition) - cnv->canvasOrigin); @@ -516,56 +599,14 @@ void TabComponent::itemDropped(SourceDetails const& dragSourceDetails) auto* tab = dynamic_cast(dragSourceDetails.sourceComponent.get()); - auto showCanvas = [this](Canvas* cnv, int splitIndex){ - if(splits[splitIndex]) removeChildComponent(splits[splitIndex]->viewport.get()); - splits[splitIndex] = cnv; - if(cnv) { - addAndMakeVisible(cnv->viewport.get()); - cnv->setVisible(true); - cnv->grabKeyboardFocus(); - cnv->patch.splitViewIndex = splitIndex; // Save split index - activeSplitIndex = splitIndex; - editor->nvgSurface.invalidateAll(); - } - }; - - if(getLocalBounds().removeFromRight(getWidth() - splitSize).contains(dragSourceDetails.localPosition)) // Dragging to right split + if(getLocalBounds().removeFromRight(getWidth() - splitSize).contains(dragSourceDetails.localPosition) && (dragSourceDetails.localPosition.y > 30 || splits[1])) // Dragging to right split { - if(tabbars[0].size() && splits[0] && tabbars[0].indexOf(tab) >= 0 && (dragSourceDetails.localPosition.y > 30 || splits[1])) - { - tabbars[1].add(tabbars[0].removeAndReturn(tabbars[0].indexOf(tab))); // Move tab to right tabbar - if(tabbars[0].size()) showCanvas(tabbars[0][0]->cnv, 0); // Show first tab of left tabbar - showCanvas(tab->cnv, 1); // Show the moved tab on right tabbar - resized(); - } + moveToRightSplit(tab); } - else { - if(tabbars[1].size() && splits[1] && tabbars[1].indexOf(tab) >= 0) { - tabbars[0].add(tabbars[1].removeAndReturn(tabbars[1].indexOf(tab))); // Move tab to left tabbar - if(tabbars[1].size()) showCanvas(tabbars[1][0]->cnv, 1); // Show first tab of right tabbar, if there are any tabs left - else showCanvas(nullptr, 1); // If no tabs are left on right tabbar - - showCanvas(tab->cnv, 0); // Show moved tab in left tabbar - resized(); - } - else if(tabbars[0].size() > 1 && splits[0] && !splits[1] && tabbars[0].indexOf(tab) >= 0 && (dragSourceDetails.localPosition.y > 30)) // If we try to create a left splits when there are no splits open - { - showCanvas(tab->cnv, 0); // Show dragged tab on left split - - // Move all other tabs to right split - for(int i = tabbars[0].size() - 1; i >= 0; i--) - { - if(tab != tabbars[0][i]) { - tabbars[0][i]->cnv->patch.splitViewIndex = 1; // Save split index - tabbars[1].insert(0, tabbars[0].removeAndReturn(i)); // Move to other split - } - } - - showCanvas(tabbars[1][0]->cnv, 1); // Show first tab of right split - resized(); - } + else if(dragSourceDetails.localPosition.y > 30) { + moveToLeftSplit(tab); } - + closeEmptySplits(); saveTabPositions(); @@ -589,7 +630,7 @@ void TabComponent::itemDragExit(SourceDetails const& dragSourceDetails) void TabComponent::saveTabPositions() { - Array> sortedPatches; + Array> sortedPatches; for(int i = 0; i < tabbars.size(); i++) { for(int j = 0; j < tabbars[i].size(); j++) { @@ -601,13 +642,9 @@ void TabComponent::saveTabPositions() } std::sort(sortedPatches.begin(), sortedPatches.end(), [](auto const& a, auto const& b) { - auto& [patchA, idxA] = a; - auto& [patchB, idxB] = b; - - if (patchA->splitViewIndex == patchB->splitViewIndex) - return idxA < idxB; - - return patchA->splitViewIndex < patchB->splitViewIndex; + if (a.first->splitViewIndex == b.first->splitViewIndex) + return a.second < b.second; + return a.first->splitViewIndex < b.first->splitViewIndex; }); pd->patches.getLock().enter(); @@ -638,7 +675,7 @@ void TabComponent::itemDragMove(SourceDetails const& dragSourceDetails) if(tab->parent != this) { - // TODO: split refactor, handle drag to other window! + editor->getTabComponent().createNewWindow(tab->cnv); } auto centreX = tab->getBounds().getCentreX(); @@ -675,15 +712,13 @@ void TabComponent::itemDragMove(SourceDetails const& dragSourceDetails) if(splitDropBounds != oldSplitDropBounds) { - // Repaint for updated split drop bounds - editor->nvgSurface.invalidateAll(); + editor->nvgSurface.invalidateAll(); // Repaint for updated split drop bounds } } void TabComponent::showHiddenTabsMenu(int splitIndex) { class HiddenTabMenuItem : public PopupMenu::CustomComponent { - String tabTitle; SafePointer cnv; @@ -696,18 +731,16 @@ void TabComponent::showHiddenTabsMenu(int splitIndex) { , tabbar(tabs) { closeTabButton.setButtonText(Icons::Clear); - closeTabButton.setSize(26, 26); closeTabButton.addMouseListener(this, false); closeTabButton.onClick = [this]() mutable { tabbar.closeTab(cnv.getComponent()); }; - addChildComponent(closeTabButton); } void resized() override { - closeTabButton.setTopLeftPosition(getWidth() - 26, -2); + closeTabButton.setBounds(getWidth() - 26, -2, 26, 26); } void getIdealSize(int& idealWidth, int& idealHeight) override @@ -762,8 +795,6 @@ void TabComponent::showHiddenTabsMenu(int splitIndex) { }; PopupMenu m; - - auto& tabbar = tabbars[splitIndex]; for (int i = 0; i < tabbar.size(); ++i) { auto* tab = tabbar[i]; diff --git a/Source/TabComponent.h b/Source/TabComponent.h index bf12826461..6d42c53652 100644 --- a/Source/TabComponent.h +++ b/Source/TabComponent.h @@ -1,10 +1,12 @@ #pragma once #include "Utility/ZoomableDragAndDropContainer.h" +#include "PluginProcessor.h" -class TabBarButtonComponent; class TabComponent : public Component, public DragAndDropTarget, public AsyncUpdater { + class TabBarButtonComponent; + public: TabComponent(PluginEditor* editor); @@ -24,7 +26,8 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd void showTab(Canvas* cnv, int splitIndex = 0); void setActiveSplit(Canvas* cnv); - void closeAllTabs(); + void closeAllTabs(bool quitAfterComplete = false, Canvas* patchToExclude = nullptr, std::function afterComplete = [](){}); + void createNewWindow(Component* draggedTab); Canvas* getCurrentCanvas(); Canvas* getCanvasAtScreenPosition(Point screenPosition); @@ -39,6 +42,9 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd void resized() override; void parentSizeChanged() override; + void moveToLeftSplit(TabComponent::TabBarButtonComponent* tab); + void moveToRightSplit(TabComponent::TabBarButtonComponent* tab); + void saveTabPositions(); void closeEmptySplits(); @@ -137,10 +143,12 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd auto textColour = findColour(PlugDataColour::toolbarTextColourId); g.setGradientFill(ColourGradient(textColour, fadeX - 18, area.getY(), Colours::transparentBlack, fadeX, area.getY(), false)); - auto text = cnv->patch.getTitle() + (cnv->patch.isDirty() ? String("*") : String()); - - g.setFont(Fonts::getCurrentFont().withHeight(14.0f)); - g.drawText(text, area.reduced(4, 0), Justification::centred, false); + if(cnv) { + auto text = cnv->patch.getTitle() + (cnv->patch.isDirty() ? String("*") : String()); + + g.setFont(Fonts::getCurrentFont().withHeight(14.0f)); + g.drawText(text, area.reduced(4, 0), Justification::centred, false); + } } void resized() override @@ -150,6 +158,8 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd ScaledImage generateTabBarButtonImage() { + if(!cnv) return {}; + auto scale = 2.0f; // we calculate the best size for the tab DnD image auto text = cnv->patch.getTitle(); @@ -167,11 +177,11 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd g.addTransform(AffineTransform::scale(scale)); Path path; path.addRoundedRectangle(bounds.reduced(10), 5.0f); - StackShadow::renderDropShadow(g, path, Colour(0, 0, 0).withAlpha(0.3f), 7, { 0, 1 }, scale); + StackShadow::renderDropShadow(g, path, Colour(0, 0, 0).withAlpha(0.2f), 6, { 0, 1 }, scale); g.setOpacity(1.0f); - g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, textBounds.withPosition(10, 10).toFloat(), Corners::defaultCornerRadius); + g.setColour(findColour(PlugDataColour::activeTabBackgroundColourId)); + PlugDataLook::fillSmoothedRectangle(g, textBounds.withPosition(10, 10).reduced(2).toFloat(), Corners::defaultCornerRadius); g.setColour(findColour(PlugDataColour::toolbarTextColourId)); @@ -183,9 +193,79 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd void mouseDown(const MouseEvent& e) override { - toFront(false); - parent->showTab(cnv, parent->tabbars[1].contains(this)); - dragger.startDraggingComponent(this, e); + if (e.mods.isPopupMenu() && cnv) { + PopupMenu tabMenu; + + #if JUCE_MAC + String revealTip = "Reveal in Finder"; + #elif JUCE_WINDOWS + String revealTip = "Reveal in Explorer"; + #else + String revealTip = "Reveal in file browser"; + #endif + + bool canReveal = cnv->patch.getCurrentFile().existsAsFile(); + + tabMenu.addItem(revealTip, canReveal, false, [this]() { + cnv->patch.getCurrentFile().revealToUser(); + }); + + tabMenu.addSeparator(); + + PopupMenu parentPatchMenu; + + if(auto patch = cnv->patch.getPointer()) + { + auto* parentPatch = patch.get(); + while((parentPatch = parentPatch->gl_owner)) + { + parentPatchMenu.addItem(String::fromUTF8(parentPatch->gl_name->s_name), [this, parentPatch](){ + auto* pdInstance = dynamic_cast(parent->pd); + parent->openPatch(new pd::Patch(pd::WeakReference(parentPatch, pdInstance), pdInstance, false)); + }); + } + } + + tabMenu.addSubMenu("Parent patches", parentPatchMenu, parentPatchMenu.getNumItems()); + + tabMenu.addSeparator(); + + auto splitIndex = parent->splits[1] && parent->tabbars[1].contains(this); + auto canSplitTab = parent->splits[1] || parent->tabbars[splitIndex].size() > 1; + tabMenu.addItem("Split left", canSplitTab, false, [this]() { + parent->moveToLeftSplit(this); + parent->closeEmptySplits(); + parent->saveTabPositions(); + }); + tabMenu.addItem("Split right", canSplitTab, false, [this]() { + parent->moveToRightSplit(this); + parent->closeEmptySplits(); + parent->saveTabPositions(); + }); + + tabMenu.addSeparator(); + + + tabMenu.addItem("Close patch", true, false, [this]() { + parent->closeTab(cnv); + }); + + tabMenu.addItem("Close all other patches", true, false, [this]() { + parent->closeAllTabs(false, cnv); + }); + + tabMenu.addItem("Close all patches", true, false, [this]() { + parent->closeAllTabs(false); + }); + + // Show the popup menu at the mouse position + tabMenu.showMenuAsync(PopupMenu::Options().withMinimumWidth(150).withMaximumNumColumns(1)); + } + else if(cnv) { + toFront(false); + parent->showTab(cnv, parent->tabbars[1].contains(this)); + dragger.startDraggingComponent(this, e); + } } void mouseDrag(const MouseEvent& e) override @@ -235,6 +315,7 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd bool draggingSplitResizer = false; Rectangle splitDropBounds; + float splitProportion = 2; int splitSize = 0; int activeSplitIndex = 0; uint32 lastMouseTime = 0; diff --git a/Source/Utility/ZoomableDragAndDropContainer.cpp b/Source/Utility/ZoomableDragAndDropContainer.cpp index 323646f7dc..88fecb6299 100644 --- a/Source/Utility/ZoomableDragAndDropContainer.cpp +++ b/Source/Utility/ZoomableDragAndDropContainer.cpp @@ -120,12 +120,10 @@ class ZoomableDragAndDropContainer::DragImageComponent : public Component if (finalTarget != nullptr) { currentlyOverComp = nullptr; finalTarget->itemDropped(details); - } - /* TODO: split refactor - else if (auto* tab = dynamic_cast(details.sourceComponent.get())) { - if (ProjectInfo::isStandalone) - owner.createNewWindow(tab); - } */ + } + else { + owner.getTabComponent().createNewWindow(details.sourceComponent.get()); + } // careful - this object could now be deleted.. } } diff --git a/Source/Utility/ZoomableDragAndDropContainer.h b/Source/Utility/ZoomableDragAndDropContainer.h index 6f916789a0..fe75b7db28 100644 --- a/Source/Utility/ZoomableDragAndDropContainer.h +++ b/Source/Utility/ZoomableDragAndDropContainer.h @@ -175,8 +175,6 @@ class ZoomableDragAndDropContainer { */ static ZoomableDragAndDropContainer* findParentDragContainerFor(Component* childComponent); - virtual void createNewWindow(Canvas* tabButton) { } - virtual TabComponent& getTabComponent() = 0; /** This performs an asynchronous drag-and-drop of a set of files to some external application. From 464c148755f97aa486c21f2e64fb050b5ca6ab5c Mon Sep 17 00:00:00 2001 From: alcomposer Date: Thu, 6 Jun 2024 17:21:16 +0930 Subject: [PATCH 0875/1030] fix surface updates --- Source/NVGSurface.h | 4 ++++ Source/Objects/PictureObject.h | 5 +++++ Source/Standalone/PlugDataWindow.h | 8 ++++++++ 3 files changed, 17 insertions(+) diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 2f852bfdbe..ac1a03970c 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -237,6 +237,8 @@ class NVGImage { if(image->isValid() && image->nvg == nvg) nvgDeleteImage(image->nvg, image->imageId); image->imageId = 0; + if (image->onImageInvalidate) + image->onImageInvalidate(); } } @@ -319,6 +321,8 @@ class NVGImage NVGcontext* nvg = nullptr; int imageId = 0; int imageWidth = 0, imageHeight = 0; + + std::function onImageInvalidate = nullptr; static inline std::set allImages; }; diff --git a/Source/Objects/PictureObject.h b/Source/Objects/PictureObject.h index ecdeb2803c..4d4c50c20a 100644 --- a/Source/Objects/PictureObject.h +++ b/Source/Objects/PictureObject.h @@ -146,6 +146,11 @@ class PictureObject final : public ObjectBase { auto partialImage = std::make_unique(nvg, width, height, [&clip](Graphics& g){ g.drawImageAt(clip, 0, 0); }); + + partialImage->onImageInvalidate = [this](){ + imageNeedsReload = true; + repaint(); + }; imageBuffers.emplace_back(std::move(partialImage), bounds); y += 8192; diff --git a/Source/Standalone/PlugDataWindow.h b/Source/Standalone/PlugDataWindow.h index ee464f0393..441baa2fb8 100644 --- a/Source/Standalone/PlugDataWindow.h +++ b/Source/Standalone/PlugDataWindow.h @@ -613,6 +613,14 @@ class PlugDataWindow : public DocumentWindow void activeWindowStatusChanged() override { +#if JUCE_WINDOWS + // Windows looses the opengl buffers when minimised, + // regenerate here when restored from minimised + if (isActiveWindow()) { + if (auto *pluginEditor = dynamic_cast(editor)) + pluginEditor->nvgSurface.invalidateAll(); + } +#endif repaint(); } From ac1b6bbd6d426873f0802bec2c41d16e22655e8d Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 13:33:18 +0200 Subject: [PATCH 0876/1030] Small nanovg optimisations, more tabbar fixes --- Libraries/nanovg | 2 +- Source/Canvas.cpp | 13 ++++-------- Source/Dialogs/AboutPanel.h | 2 +- Source/Iolet.cpp | 4 +--- Source/NVGSurface.cpp | 16 +++++--------- Source/NVGSurface.h | 12 +++-------- Source/Object.cpp | 8 ++----- Source/Objects/LuaObject.h | 10 +++------ Source/Objects/ObjectBase.h | 4 +--- Source/Objects/PictureObject.h | 4 +--- Source/PluginEditor.cpp | 25 ++++++++-------------- Source/PluginMode.h | 5 ++--- Source/TabComponent.cpp | 35 +++++++++++++++++++++++-------- Source/Tabbar/WelcomePanel.h | 8 ++----- Source/Utility/CachedTextRender.h | 4 +--- 15 files changed, 62 insertions(+), 90 deletions(-) diff --git a/Libraries/nanovg b/Libraries/nanovg index b45a86d112..07038a962b 160000 --- a/Libraries/nanovg +++ b/Libraries/nanovg @@ -1 +1 @@ -Subproject commit b45a86d1124468128ec21071d873a9ecb8f2a4e5 +Subproject commit 07038a962b38b5c3fd70c83830eecd3293adedbb diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 9299b0af14..5aaa39a436 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -203,8 +203,9 @@ bool Canvas::updateFramebuffers(NVGcontext* nvg, Rectangle invalidRegion, i auto renderIolet = [](NVGcontext* nvg, Rectangle bounds, NVGcolor background, NVGcolor outline){ if (PlugDataLook::getUseSquareIolets()) { nvgBeginPath(nvg); - nvgFillColor(nvg, background); nvgRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); + + nvgFillColor(nvg, background); nvgFill(nvg); nvgStrokeColor(nvg, outline); @@ -311,10 +312,8 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) invalidRegion = invalidRegion.translated(viewport->getViewPositionX(), viewport->getViewPositionY()); invalidRegion /= zoom; - nvgBeginPath(nvg); - nvgRect(nvg, 0, 0, infiniteCanvasSize, infiniteCanvasSize); nvgFillColor(nvg, backgroundColour); - nvgFill(nvg); + nvgFillRect(nvg, invalidRegion.getX(), invalidRegion.getY(), invalidRegion.getWidth(), invalidRegion.getHeight()); } if(hasViewport && !getValue(locked)) { @@ -324,7 +323,7 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) nvgTranslate(nvg, canvasOrigin.x % objectGrid.gridSize, canvasOrigin.y % objectGrid.gridSize); // Make sure grid aligns with origin NVGpaint dots = nvgDotPattern(nvg, dotsColour, nvgRGBA(0, 0, 0, 0), objectGrid.gridSize, 1.0f, feather); nvgFillPaint(nvg, dots); - nvgFill(nvg); + nvgFillRect(nvg, invalidRegion.getX(), invalidRegion.getY(), invalidRegion.getWidth(), invalidRegion.getHeight()); nvgRestore(nvg); } else { @@ -445,9 +444,7 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) // background colour to crop outside of border area nvgBeginPath(nvg); - nvgFillColor(nvg, bgColour); nvgRect(nvg, 0, 0, infiniteCanvasSize, infiniteCanvasSize); - nvgPathWinding(nvg, NVG_HOLE); nvgRoundedRect(nvg, pos.getX(), pos.getY(), borderWidth, borderHeight, windowCorner); nvgFillColor(nvg, bgColour); @@ -455,9 +452,7 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) // background drop shadow to simulate a virtual plugin nvgBeginPath(nvg); - nvgFillColor(nvg, bgColour); nvgRect(nvg, 0, 0, infiniteCanvasSize, infiniteCanvasSize); - nvgPathWinding(nvg, NVG_HOLE); nvgRoundedRect(nvg, pos.getX(), pos.getY(), borderWidth, borderHeight, windowCorner); diff --git a/Source/Dialogs/AboutPanel.h b/Source/Dialogs/AboutPanel.h index 0c933dfcf4..1c05beb908 100644 --- a/Source/Dialogs/AboutPanel.h +++ b/Source/Dialogs/AboutPanel.h @@ -38,7 +38,7 @@ class AboutPanel : public Component { class CreditsPanel : public Component { const std::vector> contributors = { - {"Timothy Schoen", "Development, UI/UX design"}, + {"Timothy Schoen", "Lead development, UI/UX design"}, {"Alex Mitchell", "Development, UI/UX design"}, {"Joshua A.C. Newman", "Community management, logo and identity design"}, {"Bas de Bruin", "Logo design"}, diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index 8e15718306..6305fa88ec 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -80,10 +80,8 @@ void Iolet::render(NVGcontext* nvg) auto scale = getWidth() / 13.0f; nvgScale(nvg, scale, scale); // If the iolet is shrunk because there is little space, we scale it down - nvgBeginPath(nvg); - nvgRect(nvg, 0, 0, 13, 13); nvgFillPaint(nvg, nvgImagePattern(nvg, isHovering * -16 - 1.5f, type * -16 - 0.5f, 16 * 4, 16 * 4, 0, fb.getImage(), 1)); - nvgFill(nvg); + nvgFillRect(nvg, 0, 0, 13, 13); nvgRestore(nvg); } diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 1029fa916e..83eb26c6fe 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -33,10 +33,8 @@ class FrameTimer void render(NVGcontext* nvg) { - nvgBeginPath(nvg); - nvgRect(nvg, 0, 0, 40, 22); nvgFillColor(nvg, nvgRGBA(40, 40, 40, 255)); - nvgFill(nvg); + nvgFillRect(nvg, 0, 0, 40, 22); nvgFontSize(nvg, 20.0f); nvgTextAlign(nvg,NVG_ALIGN_LEFT|NVG_ALIGN_TOP); @@ -315,17 +313,15 @@ void NVGSurface::render() nvgViewport(0, 0, getWidth() * pixelScale, getHeight() * pixelScale); nvgBeginFrame(nvg, getWidth(), getHeight(), pixelScale); nvgBeginPath(nvg); - nvgRect(nvg, invalidated.getX(), invalidated.getY(), invalidated.getWidth(), invalidated.getHeight()); nvgScissor(nvg, invalidated.getX(), invalidated.getY(), invalidated.getWidth(), invalidated.getHeight()); + nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, getWidth(), getHeight(), 0, invalidFBO->image, 1)); - nvgFill(nvg); + nvgFillRect(nvg, invalidated.getX(), invalidated.getY(), invalidated.getWidth(), invalidated.getHeight()); #if ENABLE_FB_DEBUGGING static Random rng; - nvgBeginPath(nvg); nvgFillColor(nvg, nvgRGBA(rng.nextInt(255), rng.nextInt(255), rng.nextInt(255), 0x50)); - nvgRect(nvg, 0, 0, getWidth(), getHeight()); - nvgFill(nvg); + nvgFillRect(nvg, 0, 0, getWidth(), getHeight()); #endif nvgEndFrame(nvg); @@ -341,12 +337,10 @@ void NVGSurface::render() nvgBeginFrame(nvg, getWidth(), getHeight(), pixelScale); - nvgBeginPath(nvg); nvgSave(nvg); - nvgRect(nvg, 0, 0, getWidth(), getHeight()); nvgScissor(nvg, 0, 0, getWidth(), getHeight()); nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, getWidth(), getHeight(), 0, mainFBO->image, 1)); - nvgFill(nvg); + nvgFillRect(nvg, 0, 0, getWidth(), getHeight()); nvgRestore(nvg); #if ENABLE_FPS_COUNT diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 2f852bfdbe..b9b77abe00 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -252,10 +252,8 @@ class NVGImage loadJUCEImage(nvg, componentImage); - nvgBeginPath(nvg); - nvgRect(nvg, 0, 0, component.getWidth(), component.getHeight()); nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, component.getWidth(), component.getHeight(), 0, imageId, 1.0f)); - nvgFill(nvg); + nvgFillRect(nvg, 0, 0, component.getWidth(), component.getHeight()); } void loadJUCEImage(NVGcontext* context, Image& image) @@ -299,10 +297,8 @@ class NVGImage void render(NVGcontext* nvg, Rectangle b) { if(imageId) { - nvgBeginPath(nvg); - nvgRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight()); nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, b.getWidth(), b.getHeight(), 0, imageId, 1)); - nvgFill(nvg); + nvgFillRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight()); } } @@ -390,10 +386,8 @@ class NVGFramebuffer void render(NVGcontext* nvg, Rectangle b) { if(fb) { - nvgBeginPath(nvg); - nvgRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight()); nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, b.getWidth(), b.getHeight(), 0, fb->image, 1)); - nvgFill(nvg); + nvgFillRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight()); } } diff --git a/Source/Object.cpp b/Source/Object.cpp index 8bf42e3a65..35e9b50599 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -1197,10 +1197,8 @@ void Object::updateFramebuffer(NVGcontext* nvg) #if ENABLE_OBJECT_FB_DEBUGGING static Random rng; - nvgBeginPath(nvg); nvgFillColor(nvg, nvgRGBA(rng.nextInt(255), rng.nextInt(255), rng.nextInt(255), 0x50)); - nvgRect(nvg, 0, 0, b.getWidth(), b.getHeight()); - nvgFill(nvg); + nvgFillRect(nvg, 0, 0, b.getWidth(), b.getHeight()); #endif nvgEndFrame(nvg); }); @@ -1258,10 +1256,8 @@ void Object::performRender(NVGcontext* nvg) if(cnv->shouldShowObjectActivity() && !approximatelyEqual(activeStateAlpha, 0.0f) && activityOverlayImage.isValid()) { - nvgBeginPath(nvg); nvgFillPaint(nvg, nvgImagePattern(nvg, lb.getX(), lb.getY(), lb.getWidth(), lb.getHeight(), 0, activityOverlayImage.getImageId(), activeStateAlpha)); - nvgRect(nvg, lb.getX(), lb.getY(), lb.getWidth(), lb.getHeight()); - nvgFill(nvg); + nvgFillRect(nvg, lb.getX(), lb.getY(), lb.getWidth(), lb.getHeight()); } if (gui && gui->isTransparent() && !getValue(locked) && !cnv->isGraph) { diff --git a/Source/Objects/LuaObject.h b/Source/Objects/LuaObject.h index ff0d2b7f67..a607422d4b 100644 --- a/Source/Objects/LuaObject.h +++ b/Source/Objects/LuaObject.h @@ -324,10 +324,8 @@ class LuaObject : public ObjectBase, public Timer { float y = atom_getfloat(argv + 1); float w = atom_getfloat(argv + 2); float h = atom_getfloat(argv + 3); - - nvgBeginPath(nvg); - nvgRect(nvg, x, y, w, h); - nvgFill(nvg); + + nvgFillRect(nvg, x, y, w, h); } break; } @@ -340,9 +338,7 @@ class LuaObject : public ObjectBase, public Timer { float lineThickness = atom_getfloat(argv + 4); nvgStrokeWidth(nvg, lineThickness); - nvgBeginPath(nvg); - nvgRect(nvg, x, y, w, h); - nvgStroke(nvg); + nvgStrokeRect(nvg, x, y, w, h); } break; } diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index 4c88674351..cf2d5e59d4 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -54,10 +54,8 @@ class ObjectLabel : public Label, public NVGComponent { updateColour = false; } - nvgBeginPath(nvg); - nvgRect(nvg, 0, 0, getWidth() + 1, getHeight()); nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, getWidth() + 1, getHeight(), 0, image.getImageId(), 1.0f)); - nvgFill(nvg); + nvgFillRect(nvg, 0, 0, getWidth() + 1, getHeight()); } void setColour(const Colour& colour) diff --git a/Source/Objects/PictureObject.h b/Source/Objects/PictureObject.h index ecdeb2803c..e8cac50849 100644 --- a/Source/Objects/PictureObject.h +++ b/Source/Objects/PictureObject.h @@ -176,10 +176,8 @@ class PictureObject final : public ObjectBase { else { for(auto& [image, bounds] : imageBuffers) { - nvgBeginPath(nvg); - nvgRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); nvgFillPaint(nvg, nvgImagePattern(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), 0, image->getImageId(), 1.0f)); - nvgFill(nvg); + nvgFillRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); } } diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 1be18a7c27..bf7ffd5994 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -345,13 +345,14 @@ void PluginEditor::paintOverChildren(Graphics& g) g.drawRoundedRectangle(getLocalBounds().reduced(1).toFloat(), Corners::windowCornerRadius, 2.0f); } - auto tabbarDepth = welcomePanel->isVisible() ? toolbarHeight + 5.5f : toolbarHeight + 30.0f; + auto welcomePanelVisible = !getCurrentCanvas(); + auto tabbarDepth = welcomePanelVisible ? toolbarHeight + 5.5f : toolbarHeight + 30.0f; g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.drawLine(palettes->isExpanded() ? palettes->getRight() : 29.0f, tabbarDepth, sidebar->getX() + 1.0f, tabbarDepth); // Draw extra lines in case tabbar is not visible. Otherwise some outlines will stop too soon if(!getCurrentCanvas()) { - auto toolbarDepth = welcomePanel->isVisible() ? toolbarHeight + 6 : toolbarHeight; + auto toolbarDepth = welcomePanelVisible ? toolbarHeight + 6 : toolbarHeight; g.drawLine(palettes->isExpanded() ? palettes->getRight() : 29.5f, toolbarDepth, palettes->isExpanded() ? palettes->getRight() : 29.5f, toolbarDepth + 30); g.drawLine(sidebar->getX() + 0.5f, toolbarDepth, sidebar->getX() + 0.5f, toolbarHeight + 30); } @@ -363,9 +364,13 @@ void PluginEditor::renderArea(NVGcontext* nvg, Rectangle area) pluginMode->render(nvg); } else { - bool hasCanvas = getCurrentCanvas() != nullptr; - if(hasCanvas) + if(!getCurrentCanvas()) { + nvgSave(nvg); + welcomePanel->render(nvg); + nvgRestore(nvg); + } + else { tabComponent.renderArea(nvg, area); if(touchSelectionHelper && touchSelectionHelper->isVisible() && area.intersects(touchSelectionHelper->getBounds() - nvgSurface.getPosition())) { @@ -375,18 +380,6 @@ void PluginEditor::renderArea(NVGcontext* nvg, Rectangle area) nvgRestore(nvg); } } - else if(!tabComponent.isUpdatePending()) - { - nvgSave(nvg); - welcomePanel->render(nvg); - nvgRestore(nvg); - } - else { - nvgBeginPath(nvg); - nvgRect(nvg, 0, 0, nvgSurface.getWidth(), nvgSurface.getHeight()); - nvgFillColor(nvg, NVGComponent::convertColour(findColour(PlugDataColour::canvasBackgroundColourId))); - nvgFill(nvg); - } } } diff --git a/Source/PluginMode.h b/Source/PluginMode.h index 81792a2930..3ef0e89a1e 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -145,10 +145,8 @@ class PluginMode : public Component, public NVGComponent { void render(NVGcontext* nvg) override { - nvgBeginPath(nvg); - nvgRect(nvg, 0, 0, getWidth(), getHeight()); nvgFillColor(nvg, findNVGColour(PlugDataColour::canvasBackgroundColourId)); - nvgFill(nvg); + nvgFillRect(nvg, 0, 0, getWidth(), getHeight()); nvgSave(nvg); nvgScale(nvg, pluginModeScale, pluginModeScale); @@ -191,6 +189,7 @@ class PluginMode : public Component, public NVGComponent { originalCanvas->patch.openInPluginMode = false; } + editor->getTabComponent().triggerAsyncUpdate(); editor->parentSizeChanged(); editor->resized(); diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index fbfc94e988..3fba331022 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -26,6 +26,10 @@ TabComponent::TabComponent(PluginEditor* editor) : editor(editor), pd(editor->pd } addMouseListener(this, true); + + if(!pd->isInPluginMode()) { + triggerAsyncUpdate(); + } } Canvas* TabComponent::newPatch() @@ -202,9 +206,12 @@ void TabComponent::handleAsyncUpdate() tabbars[0].clear(); tabbars[1].clear(); + if(pd->isInPluginMode()) return; + auto editorIndex = pd->getEditors().indexOf(editor); // Load all patches from pd patch array + pd->patches.getLock().enter(); for(auto& patch : pd->patches) { if(patch->windowIndex != editorIndex) continue; @@ -228,6 +235,7 @@ void TabComponent::handleAsyncUpdate() tabbars[patch->splitViewIndex == 1].add(newTabButton); addAndMakeVisible(newTabButton); } + pd->patches.getLock().exit(); closeEmptySplits(); resized(); // Update tab and canvas layout @@ -273,6 +281,7 @@ void TabComponent::closeEmptySplits() } } } + editor->resized(); } void TabComponent::showTab(Canvas* cnv, int splitIndex) @@ -293,6 +302,13 @@ void TabComponent::showTab(Canvas* cnv, int splitIndex) repaint(); editor->nvgSurface.invalidateAll(); + + for(auto* tab : getVisibleCanvases()) + { + tab->tabChanged(); + } + + editor->updateCommandStatus(); } Canvas* TabComponent::getCurrentCanvas() @@ -309,6 +325,9 @@ Array TabComponent::getCanvases() void TabComponent::renderArea(NVGcontext* nvg, Rectangle area) { + nvgFillColor(nvg, NVGComponent::convertColour(findColour(PlugDataColour::canvasBackgroundColourId))); + nvgFillRect(nvg, 0, 0, area.getWidth(), area.getHeight()); + if(splits[0]) { nvgSave(nvg); @@ -328,26 +347,20 @@ void TabComponent::renderArea(NVGcontext* nvg, Rectangle area) if(!splitDropBounds.isEmpty()) { - nvgBeginPath(nvg); - nvgRect(nvg, splitDropBounds.getX(), splitDropBounds.getY(), splitDropBounds.getWidth(), splitDropBounds.getHeight()); nvgFillColor(nvg, NVGComponent::convertColour(findColour(PlugDataColour::dataColourId).withAlpha(0.1f))); - nvgFill(nvg); + nvgFillRect(nvg, splitDropBounds.getX(), splitDropBounds.getY(), splitDropBounds.getWidth(), splitDropBounds.getHeight()); } if(splits[1]) { - nvgBeginPath(nvg); - nvgRect(nvg, splitSize - 3, 0, 6, getHeight()); nvgFillColor(nvg, NVGComponent::convertColour(findColour(PlugDataColour::canvasBackgroundColourId))); - nvgFill(nvg); + nvgFillRect(nvg, splitSize - 3, 0, 6, getHeight()); auto activeSplitBounds = activeSplitIndex ? Rectangle(splitSize, 0, getWidth() - splitSize, getHeight() - 31) : Rectangle(0, 0, splitSize, getHeight() - 31); - nvgBeginPath(nvg); - nvgRect(nvg, activeSplitBounds.getX(), activeSplitBounds.getY(), activeSplitBounds.getWidth(), activeSplitBounds.getHeight()); nvgStrokeWidth(nvg, 3.0f); nvgStrokeColor(nvg, NVGComponent::convertColour(findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.25f))); - nvgStroke(nvg); + nvgStrokeRect(nvg, activeSplitBounds.getX(), activeSplitBounds.getY(), activeSplitBounds.getWidth(), activeSplitBounds.getHeight()); } } @@ -521,6 +534,10 @@ void TabComponent::closeAllTabs(bool quitAfterComplete, Canvas* patchToExclude, if (canvas && !(patchToExclude && canvas == patchToExclude)) { closeTab(canvas); } + else if(canvas == patchToExclude) + { + canvases.move(canvases.indexOf(patchToExclude), 0); + } closeAllTabs(quitAfterComplete, patchToExclude, afterComplete); }; diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Tabbar/WelcomePanel.h index 050bc8c47d..905932048e 100644 --- a/Source/Tabbar/WelcomePanel.h +++ b/Source/Tabbar/WelcomePanel.h @@ -292,10 +292,8 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater { if(!nvgContext || nvgContext->getContext() != nvg) nvgContext = std::make_unique(nvg); - nvgBeginPath(nvg); - nvgRect(nvg, 0, 0, getWidth(), getHeight()); nvgFillColor(nvg, convertColour(findColour(PlugDataColour::panelBackgroundColourId))); - nvgFill(nvg); + nvgFillRect(nvg, 0, 0, getWidth(), getHeight()); Graphics g(*nvgContext); g.reduceClipRegion(editor->nvgSurface.getInvalidArea()); @@ -304,9 +302,7 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater auto gradient = nvgLinearGradient(nvg, 0, recentlyOpenedViewport.getY(), 0, recentlyOpenedViewport.getY() + 20, convertColour(findColour(PlugDataColour::panelBackgroundColourId)), nvgRGBAf(1, 1, 1, 0)); nvgFillPaint(nvg, gradient); - nvgBeginPath(nvg); - nvgRect(nvg, recentlyOpenedViewport.getX() + 8, recentlyOpenedViewport.getY(), recentlyOpenedViewport.getWidth() - 16, 20); - nvgFill(nvg); + nvgFillRect(nvg, recentlyOpenedViewport.getX() + 8, recentlyOpenedViewport.getY(), recentlyOpenedViewport.getWidth() - 16, 20); nvgBeginPath(nvg); nvgFillColor(nvg, findNVGColour(PlugDataColour::panelTextColourId)); diff --git a/Source/Utility/CachedTextRender.h b/Source/Utility/CachedTextRender.h index ca694d8db5..70f5540ec8 100644 --- a/Source/Utility/CachedTextRender.h +++ b/Source/Utility/CachedTextRender.h @@ -16,12 +16,10 @@ class CachedTextRender updateImage = false; } - nvgBeginPath(nvg); nvgSave(nvg); nvgIntersectScissor(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); - nvgRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth() + 3, bounds.getHeight()); nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, bounds.getWidth() + 3, bounds.getHeight(), 0, image.getImageId(), 1.0f)); - nvgFill(nvg); + nvgFillRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth() + 3, bounds.getHeight()); nvgRestore(nvg); } From 702376007165c8ca36724e73177627561c82c7a1 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 14:37:31 +0200 Subject: [PATCH 0877/1030] More reliable system for loading pluginmode --- Source/PluginEditor.cpp | 31 +--------------------------- Source/PluginEditor.h | 2 -- Source/PluginMode.h | 24 +++++----------------- Source/PluginProcessor.cpp | 17 +++++----------- Source/TabComponent.cpp | 41 +++++++++++++++++++++++++------------- Source/TabComponent.h | 4 +++- 6 files changed, 41 insertions(+), 78 deletions(-) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index bf7ffd5994..c5b9c4c68d 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -210,7 +210,7 @@ PluginEditor::PluginEditor(PluginProcessor& p) pluginModeButton.setColour(ComboBox::outlineColourId, findColour(TextButton::buttonColourId)); pluginModeButton.onClick = [this]() { if (auto* cnv = getCurrentCanvas()) { - enablePluginMode(cnv); + tabComponent.openInPluginMode(cnv->refCountedPatch); } }; @@ -232,10 +232,6 @@ PluginEditor::PluginEditor(PluginProcessor& p) addModifierKeyListener(this); - // Restore Plugin Mode View - if (pd->isInPluginMode()) - enablePluginMode(nullptr); - connectionMessageDisplay = std::make_unique(this); connectionMessageDisplay->addToDesktop(ComponentPeer::windowIsTemporary | ComponentPeer::windowIgnoresKeyPresses | ComponentPeer::windowIgnoresMouseClicks); if(!ProjectInfo::isStandalone) { @@ -1486,31 +1482,6 @@ bool PluginEditor::wantsRoundedCorners() } } -void PluginEditor::enablePluginMode(Canvas* cnv) -{ - if (!cnv) { - if (pd->isInPluginMode()) { - MessageManager::callAsync([_this = SafePointer(this), this]() { - if (!_this) - return; - - // Restore Plugin Mode View - for (auto* canvas : getCanvases()) { - if (canvas && canvas->patch.openInPluginMode) { - enablePluginMode(canvas); - } - } - }); - } else { - return; - } - } else { - cnv->patch.openInPluginMode = true; - pluginMode = std::make_unique(cnv); - resized(); - } -} - // At the top-level, always catch all keypresses // This makes sure you can't accidentally do a DAW keyboard shortcut with plugdata open // Since objects like "keyname" need to be able to respond to any key as well, diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 646ca3c619..deab32e8d6 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -129,8 +129,6 @@ class PluginEditor : public AudioProcessorEditor CallOutBox& showCalloutBox(std::unique_ptr content, Rectangle screenBounds); - void enablePluginMode(Canvas* cnv); - void commandKeyChanged(bool isHeld) override; void setUseBorderResizer(bool shouldUse); void showTouchSelectionHelper(bool shouldBeShown); diff --git a/Source/PluginMode.h b/Source/PluginMode.h index 3ef0e89a1e..9b3a12baaa 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -13,11 +13,10 @@ class PluginMode : public Component, public NVGComponent { public: - explicit PluginMode(Canvas* canvas) + explicit PluginMode(PluginEditor* editor, pd::Patch::Ptr patch) : NVGComponent(this) - , cnv(std::make_unique(canvas->editor, canvas->patch, this)) - , originalCanvas(canvas) - , editor(cnv->editor) + , cnv(std::make_unique(editor, patch, this)) + , editor(editor) , desktopWindow(editor->getPeer()) , windowBounds(editor->getBounds().withPosition(editor->getTopLevelComponent()->getPosition())) { @@ -48,15 +47,9 @@ class PluginMode : public Component, public NVGComponent { desktopWindow = editor->getPeer(); - // Save original canvas properties - originalCanvasScale = getValue(cnv->zoomScale); - originalCanvasPos = cnv->getPosition(); - originalLockedMode = getValue(cnv->locked); - originalPresentationMode = getValue(cnv->presentationMode); - editor->nvgSurface.invalidateAll(); cnv->setCachedComponentImage(new NVGSurface::InvalidationListener(editor->nvgSurface, cnv.get())); - originalCanvas->patch.openInPluginMode = true; + patch->openInPluginMode = true; // Titlebar titleBar.setBounds(0, 0, width, titlebarHeight); @@ -184,10 +177,7 @@ class PluginMode : public Component, public NVGComponent { editor->setBounds(windowBounds); } - if (originalCanvas) { - // Reset the canvas properties to before plugin mode was entered - originalCanvas->patch.openInPluginMode = false; - } + cnv->patch.openInPluginMode = false; editor->getTabComponent().triggerAsyncUpdate(); editor->parentSizeChanged(); @@ -410,10 +400,6 @@ class PluginMode : public Component, public NVGComponent { WindowDragger windowDragger; bool isDraggingWindow = false; - Point originalCanvasPos; - float originalCanvasScale; - bool originalLockedMode; - bool originalPresentationMode; bool isFullScreenKioskMode = false; Rectangle originalPluginWindowBounds; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 6876538f8e..81358fe933 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1099,16 +1099,6 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) if (sizeInBytes == 0) return; - // Don't clear tabs if there is no editor open before loading state, if we don't check this it will not load properly in some DAWs - if(getEditors().size()) { - // Close any opened patches - MessageManager::callAsync([this]() { - for (auto* editor : getEditors()) { - editor->getTabComponent().closeAllTabs(); - } - }); - } - MemoryInputStream istream(data, sizeInBytes, false); lockAudioThread(); @@ -1523,7 +1513,7 @@ void PluginProcessor::receiveSysMessage(String const& selector, std::vectorpatch) { - editors[0]->enablePluginMode(canvas); + editors[0]->getTabComponent().openInPluginMode(canvas->refCountedPatch); } } } @@ -1533,7 +1523,10 @@ void PluginProcessor::receiveSysMessage(String const& selector, std::vectorenablePluginMode(editor->getCurrentCanvas()); + if(auto* cnv = editor->getCurrentCanvas()) + { + editor->getTabComponent().openInPluginMode(cnv->refCountedPatch); + } } }); } diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 3fba331022..6203c8f87c 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -7,6 +7,7 @@ #include "Utility/Autosave.h" #include "Components/ObjectDragAndDrop.h" #include "NVGSurface.h" +#include "PluginMode.h" #include "Standalone/PlugDataWindow.h" TabComponent::TabComponent(PluginEditor* editor) : editor(editor), pd(editor->pd) @@ -26,10 +27,7 @@ TabComponent::TabComponent(PluginEditor* editor) : editor(editor), pd(editor->pd } addMouseListener(this, true); - - if(!pd->isInPluginMode()) { - triggerAsyncUpdate(); - } + triggerAsyncUpdate(); } Canvas* TabComponent::newPatch() @@ -201,12 +199,36 @@ void TabComponent::createNewWindow(Component* draggedTab) newWindow->toFront(true); } +void TabComponent::openInPluginMode(pd::Patch::Ptr patch) +{ + patch->openInPluginMode = true; + triggerAsyncUpdate(); +} + void TabComponent::handleAsyncUpdate() { tabbars[0].clear(); tabbars[1].clear(); - if(pd->isInPluginMode()) return; + if(pd->isInPluginMode() && !editor->pluginMode) + { + // Initialise plugin mode + for(auto& patch : pd->patches) + { + if(patch->openInPluginMode) // Found pluginmode patch + { + canvases.clear(); + editor->pluginMode = std::make_unique(editor, patch); + editor->resized(); + return; + } + } + } + else if(pd->isInPluginMode()) + { + canvases.clear(); + return; + } auto editorIndex = pd->getEditors().indexOf(editor); @@ -366,9 +388,6 @@ void TabComponent::renderArea(NVGcontext* nvg, Rectangle area) void TabComponent::mouseDown(const MouseEvent& e) { - if(e.eventTime.getMillisecondCounter() == lastMouseTime) return; - lastMouseTime = e.eventTime.getMillisecondCounter(); - auto localPos = e.getEventRelativeTo(this).getPosition(); if(localPos.x > splitSize - 3 && localPos.x < splitSize + 3) { @@ -392,9 +411,6 @@ void TabComponent::mouseUp(const MouseEvent& e) void TabComponent::mouseDrag(const MouseEvent& e) { - if(e.eventTime.getMillisecondCounter() == lastMouseTime) return; - lastMouseTime = e.eventTime.getMillisecondCounter(); - auto localPos = e.getEventRelativeTo(this).getPosition(); if(draggingSplitResizer) { splitSize = localPos.x; @@ -405,9 +421,6 @@ void TabComponent::mouseDrag(const MouseEvent& e) void TabComponent::mouseMove(const MouseEvent& e) { - if(e.eventTime.getMillisecondCounter() == lastMouseTime) return; - lastMouseTime = e.eventTime.getMillisecondCounter(); - auto localPos = e.getEventRelativeTo(this).getPosition(); if(localPos.x > splitSize - 3 && localPos.x < splitSize + 3) { diff --git a/Source/TabComponent.h b/Source/TabComponent.h index 6d42c53652..21363ba268 100644 --- a/Source/TabComponent.h +++ b/Source/TabComponent.h @@ -17,6 +17,8 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd Canvas* openPatch(pd::Patch::Ptr existingPatch); void openPatch(); + void openInPluginMode(pd::Patch::Ptr patch); + void renderArea(NVGcontext* nvg, Rectangle bounds); void nextTab(); @@ -318,7 +320,7 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd float splitProportion = 2; int splitSize = 0; int activeSplitIndex = 0; - uint32 lastMouseTime = 0; + Time lastMouseTime; OwnedArray canvases; From c48af95d60164846a641752162b740d58b478c76 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 14:42:09 +0200 Subject: [PATCH 0878/1030] Show pluginmode tab after closing pluginmode --- Source/TabComponent.cpp | 15 +++++++++++++++ Source/TabComponent.h | 2 ++ 2 files changed, 17 insertions(+) diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 6203c8f87c..89d8c156a1 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -220,6 +220,7 @@ void TabComponent::handleAsyncUpdate() canvases.clear(); editor->pluginMode = std::make_unique(editor, patch); editor->resized(); + lastPluginModePatchPtr = patch->getPointer().get(); return; } } @@ -258,6 +259,20 @@ void TabComponent::handleAsyncUpdate() addAndMakeVisible(newTabButton); } pd->patches.getLock().exit(); + + // Show plugin mode tab after closing pluginmode + if(lastPluginModePatchPtr != nullptr) + { + for(auto* canvas : canvases) + { + if(canvas->patch.getPointer().get() == lastPluginModePatchPtr) + { + showTab(canvas); + break; + } + } + lastPluginModePatchPtr = nullptr; + } closeEmptySplits(); resized(); // Update tab and canvas layout diff --git a/Source/TabComponent.h b/Source/TabComponent.h index 21363ba268..cbcf884fbc 100644 --- a/Source/TabComponent.h +++ b/Source/TabComponent.h @@ -323,6 +323,8 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd Time lastMouseTime; OwnedArray canvases; + + t_glist* lastPluginModePatchPtr = nullptr; PluginEditor* editor; PluginProcessor* pd; From 6353b34f53cbe2a91db690c3f342d7e4599f6635 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 14:45:17 +0200 Subject: [PATCH 0879/1030] Small nanovg fix --- Libraries/nanovg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/nanovg b/Libraries/nanovg index 07038a962b..0bd65b5aa2 160000 --- a/Libraries/nanovg +++ b/Libraries/nanovg @@ -1 +1 @@ -Subproject commit 07038a962b38b5c3fd70c83830eecd3293adedbb +Subproject commit 0bd65b5aa2611049f3af81f7721ccaada69414bc From 1f61f6072a66c05204ca1748464f09866f2fb945 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 14:47:02 +0200 Subject: [PATCH 0880/1030] File structure cleanup --- Source/{Tabbar => Components}/WelcomePanel.h | 0 Source/NVGSurface.cpp | 2 +- Source/PluginEditor.cpp | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename Source/{Tabbar => Components}/WelcomePanel.h (100%) diff --git a/Source/Tabbar/WelcomePanel.h b/Source/Components/WelcomePanel.h similarity index 100% rename from Source/Tabbar/WelcomePanel.h rename to Source/Components/WelcomePanel.h diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 83eb26c6fe..234cc0615a 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -18,7 +18,7 @@ using namespace juce::gl; #include "PluginEditor.h" #include "PluginProcessor.h" -#include "Tabbar/WelcomePanel.h" +#include "Components/WelcomePanel.h" #define ENABLE_FPS_COUNT 0 diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index c5b9c4c68d..de9a7eb6ee 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -28,7 +28,7 @@ #include "Dialogs/ConnectionMessageDisplay.h" #include "Dialogs/Dialogs.h" #include "Statusbar.h" -#include "Tabbar/WelcomePanel.h" +#include "Components/WelcomePanel.h" #include "Sidebar/Sidebar.h" #include "Object.h" #include "PluginMode.h" From aef8153d84f2247d16c87137c96ea0328ceb703a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 15:00:58 +0200 Subject: [PATCH 0881/1030] Further simplification of loading patches from DAW state --- Source/PluginProcessor.cpp | 59 +++++++++----------------------- Source/Sidebar/AutomationPanel.h | 4 +-- Source/Sidebar/Sidebar.cpp | 5 +-- Source/TabComponent.cpp | 18 ++++++++++ 4 files changed, 38 insertions(+), 48 deletions(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 81358fe933..e7c8190c63 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1141,42 +1141,22 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) // This makes sure the patch can find abstractions/resources, even though it's loading patch from state glob_forcefilename(generateSymbol(location.getFileName().toRawUTF8()), generateSymbol(location.getParentDirectory().getFullPathName().toRawUTF8())); - if(auto* editor = dynamic_cast(getActiveEditor())) + auto patchPtr = loadPatch(content, nullptr); + patchPtr->splitViewIndex = splitIndex; + patchPtr->openInPluginMode = pluginMode; + if(!location.exists() || (location.exists() && location.getParentDirectory() == File::getSpecialLocation(File::tempDirectory))) { - auto* cnv = editor->getTabComponent().openPatch(content); - cnv->patch.splitViewIndex = splitIndex; - cnv->patch.openInPluginMode = pluginMode; - if(!location.exists() || (location.exists() && location.getParentDirectory() == File::getSpecialLocation(File::tempDirectory))) - { - cnv->patch.setUntitled(); - } - else - { - cnv->patch.setTitle(location.getFileName()); - } + patchPtr->setUntitled(); } - else { - loadPatch(content, nullptr); + else + { + patchPtr->setTitle(location.getFileName()); } } else { - if(auto* editor = dynamic_cast(getActiveEditor())) - { - auto* cnv = editor->getTabComponent().openPatch(URL(location)); - cnv->patch.splitViewIndex = splitIndex; - cnv->patch.openInPluginMode = pluginMode; - if(!location.exists() || (location.exists() && location.getParentDirectory() == File::getSpecialLocation(File::tempDirectory))) - { - cnv->patch.setUntitled(); - } - else - { - cnv->patch.setTitle(location.getFileName()); - } - } - else { - loadPatch(URL(location), nullptr); - } + auto patchPtr = loadPatch(URL(location), nullptr); + patchPtr->splitViewIndex = splitIndex; + patchPtr->openInPluginMode = pluginMode; } }; @@ -1250,17 +1230,12 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) unlockAudioThread(); delete[] xmlData; - - // TODO: ugly, clean this up - MessageManager::callAsync([this]() { - for (auto* editor : getEditors()) { - editor->sidebar->updateAutomationParameters(); - - if (editor->pluginMode && !editor->pd->isInPluginMode()) { - editor->pluginMode->closePluginMode(); - } - } - }); + + if(auto* editor = dynamic_cast(getActiveEditor())) + { + editor->getTabComponent().triggerAsyncUpdate(); + editor->sidebar->updateAutomationParameters(); + } // After loading a state, we need to update all the parameters if(PluginHostType::getPluginLoadedAs() == AudioProcessor::wrapperType_AudioUnit || PluginHostType::getPluginLoadedAs() == AudioProcessor::wrapperType_AudioUnitv3) diff --git a/Source/Sidebar/AutomationPanel.h b/Source/Sidebar/AutomationPanel.h index 4e8c7448e2..5ef28e652a 100644 --- a/Source/Sidebar/AutomationPanel.h +++ b/Source/Sidebar/AutomationPanel.h @@ -783,7 +783,7 @@ class AutomationComponent : public Component { }; class AutomationPanel : public Component - , public ScrollBar::Listener { + , public ScrollBar::Listener, public AsyncUpdater { public: explicit AutomationPanel(PluginProcessor* processor) @@ -819,7 +819,7 @@ class AutomationPanel : public Component sliders.setSize(getWidth(), std::max(sliders.getTotalHeight(), viewport.getMaximumVisibleHeight())); } - void updateParameters() + void handleAsyncUpdate() override { if (ProjectInfo::isStandalone) { diff --git a/Source/Sidebar/Sidebar.cpp b/Source/Sidebar/Sidebar.cpp index 4de8250a3f..5a58cb4d96 100644 --- a/Source/Sidebar/Sidebar.cpp +++ b/Source/Sidebar/Sidebar.cpp @@ -268,10 +268,7 @@ bool Sidebar::isShowingBrowser() void Sidebar::updateAutomationParameters() { if (automationPanel) { - // Might be called from audio thread - MessageManager::callAsync([this]() { - automationPanel->updateParameters(); - }); + automationPanel->triggerAsyncUpdate(); } } diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 89d8c156a1..7c6803e2a2 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -233,6 +233,24 @@ void TabComponent::handleAsyncUpdate() auto editorIndex = pd->getEditors().indexOf(editor); + // First, remove canvases that no longer exist + for(int i = canvases.size() - 1; i >= 0; i--) + { + bool exists = false; + for(auto& patch : pd->patches) + { + if(canvases[i]->patch == *patch) + { + exists = true; + } + } + + if(!exists) + { + canvases.remove(i); + } + } + // Load all patches from pd patch array pd->patches.getLock().enter(); for(auto& patch : pd->patches) From 33027e9687e26e04618cadb036a1d24a27b91a26 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 15:14:33 +0200 Subject: [PATCH 0882/1030] Windows and Linux compilation fix --- Source/Standalone/PlugDataApp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Standalone/PlugDataApp.cpp b/Source/Standalone/PlugDataApp.cpp index 7dbc6dbbd3..1b7de40232 100644 --- a/Source/Standalone/PlugDataApp.cpp +++ b/Source/Standalone/PlugDataApp.cpp @@ -205,7 +205,7 @@ class PlugDataApp : public JUCEApplication { if (toOpen.existsAsFile() && toOpen.hasFileExtension("pd") && !openedPatches.contains(toOpen.getFullPathName())) { auto* pd = dynamic_cast(pluginHolder->processor.get()); auto* editor = dynamic_cast(mainWindow->mainComponent->getEditor()); - editor->tabComponent.openPatch(URL(toOpen)); + editor->getTabComponent().openPatch(URL(toOpen)); SettingsFile::getInstance()->addToRecentlyOpened(toOpen); } } From 98fac2474f3227950d71087c9598e6166b888b67 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 15:19:29 +0200 Subject: [PATCH 0883/1030] Fixed unused variables warnings --- Libraries/nanovg | 2 +- Source/Standalone/PlugDataApp.cpp | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Libraries/nanovg b/Libraries/nanovg index 0bd65b5aa2..54fa8e55fb 160000 --- a/Libraries/nanovg +++ b/Libraries/nanovg @@ -1 +1 @@ -Subproject commit 0bd65b5aa2611049f3af81f7721ccaada69414bc +Subproject commit 54fa8e55fbd730c7e5a33014bbec14c88e8e0dc9 diff --git a/Source/Standalone/PlugDataApp.cpp b/Source/Standalone/PlugDataApp.cpp index 1b7de40232..186a8e4e0b 100644 --- a/Source/Standalone/PlugDataApp.cpp +++ b/Source/Standalone/PlugDataApp.cpp @@ -184,11 +184,9 @@ class PlugDataApp : public JUCEApplication { if (toOpen.existsAsFile() && toOpen.hasFileExtension("pd")) { auto* editor = dynamic_cast(mainWindow->mainComponent->getEditor()); - if (auto* pd = dynamic_cast(pluginHolder->processor.get())) { - editor->getTabComponent().openPatch(URL(toOpen)); - SettingsFile::getInstance()->addToRecentlyOpened(toOpen); - openedPatches.add(toOpen.getFullPathName()); - } + editor->getTabComponent().openPatch(URL(toOpen)); + SettingsFile::getInstance()->addToRecentlyOpened(toOpen); + openedPatches.add(toOpen.getFullPathName()); } } @@ -203,7 +201,6 @@ class PlugDataApp : public JUCEApplication { # endif auto toOpen = File(arg); if (toOpen.existsAsFile() && toOpen.hasFileExtension("pd") && !openedPatches.contains(toOpen.getFullPathName())) { - auto* pd = dynamic_cast(pluginHolder->processor.get()); auto* editor = dynamic_cast(mainWindow->mainComponent->getEditor()); editor->getTabComponent().openPatch(URL(toOpen)); SettingsFile::getInstance()->addToRecentlyOpened(toOpen); From 6cc06ae38b441c54ccef3f2ec820b17128e9e9fc Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 15:22:26 +0200 Subject: [PATCH 0884/1030] Canvas dots fix --- Source/Canvas.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 5aaa39a436..623e76b160 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -340,7 +340,7 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) NVGpaint dots = nvgDotPattern(nvg, i == 3 ? darkDotColour : dotsColour, nvgRGBA(0, 0, 0, 0), objectGrid.gridSize * 4, scaledDotSize, feather + 0.2f); nvgFillPaint(nvg, dots); - nvgFill(nvg); + nvgFillRect(nvg, invalidRegion.getX(), invalidRegion.getY(), invalidRegion.getWidth(), invalidRegion.getHeight()); } nvgRestore(nvg); nvgSave(nvg); @@ -350,7 +350,7 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) nvgTranslate(nvg, 0, objectGrid.gridSize); NVGpaint dots = nvgDotPattern(nvg, i == 3 ? darkDotColour : dotsColour, nvgRGBA(0, 0, 0, 0), objectGrid.gridSize * 4, scaledDotSize, feather + 0.2f); nvgFillPaint(nvg, dots); - nvgFill(nvg); + nvgFillRect(nvg, invalidRegion.getX(), invalidRegion.getY(), invalidRegion.getWidth(), invalidRegion.getHeight()); } nvgRestore(nvg); From c6d341f81616775f87f2901610ad348fbb4813a1 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 15:30:13 +0200 Subject: [PATCH 0885/1030] Revert some dot pattern changes --- Source/Canvas.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 623e76b160..d3aa32290f 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -317,13 +317,16 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) } if(hasViewport && !getValue(locked)) { + nvgBeginPath(nvg); + nvgRect(nvg, 0, 0, infiniteCanvasSize, infiniteCanvasSize); + auto feather = getRenderScale() > 1.0f ? 0.25f : 0.75f; if(getValue(zoomScale) >= 1.0f) { nvgSave(nvg); nvgTranslate(nvg, canvasOrigin.x % objectGrid.gridSize, canvasOrigin.y % objectGrid.gridSize); // Make sure grid aligns with origin NVGpaint dots = nvgDotPattern(nvg, dotsColour, nvgRGBA(0, 0, 0, 0), objectGrid.gridSize, 1.0f, feather); nvgFillPaint(nvg, dots); - nvgFillRect(nvg, invalidRegion.getX(), invalidRegion.getY(), invalidRegion.getWidth(), invalidRegion.getHeight()); + nvgFill(nvg); nvgRestore(nvg); } else { @@ -340,7 +343,7 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) NVGpaint dots = nvgDotPattern(nvg, i == 3 ? darkDotColour : dotsColour, nvgRGBA(0, 0, 0, 0), objectGrid.gridSize * 4, scaledDotSize, feather + 0.2f); nvgFillPaint(nvg, dots); - nvgFillRect(nvg, invalidRegion.getX(), invalidRegion.getY(), invalidRegion.getWidth(), invalidRegion.getHeight()); + nvgFill(nvg); } nvgRestore(nvg); nvgSave(nvg); @@ -350,7 +353,7 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) nvgTranslate(nvg, 0, objectGrid.gridSize); NVGpaint dots = nvgDotPattern(nvg, i == 3 ? darkDotColour : dotsColour, nvgRGBA(0, 0, 0, 0), objectGrid.gridSize * 4, scaledDotSize, feather + 0.2f); nvgFillPaint(nvg, dots); - nvgFillRect(nvg, invalidRegion.getX(), invalidRegion.getY(), invalidRegion.getWidth(), invalidRegion.getHeight()); + nvgFill(nvg); } nvgRestore(nvg); From cb3e43b00d5c96fba3d01fba91556a2b809e6aca Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 15:34:16 +0200 Subject: [PATCH 0886/1030] Close dialog fix --- Source/TabComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 7c6803e2a2..370a52be56 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -592,7 +592,7 @@ void TabComponent::closeAllTabs(bool quitAfterComplete, Canvas* patchToExclude, MessageManager::callAsync([this, canvas, patch, deleteFunc]() mutable { if (patch->isDirty()) { Dialogs::showAskToSaveDialog( - &editor->openedDialog, this, patch->getTitle(), + &editor->openedDialog, editor, patch->getTitle(), [canvas, deleteFunc](int result) mutable { if (!canvas) return; From 07be4894e666422bd028ce4f30f84de4c66c0285 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 16:06:50 +0200 Subject: [PATCH 0887/1030] Ask to save before closing tabs --- Source/TabComponent.cpp | 24 +++++++++++++++++++++++- Source/TabComponent.h | 3 ++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 370a52be56..e856a33ee0 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -542,7 +542,29 @@ void TabComponent::resized() newTabButtons[1].setVisible(!tabbars[1].isEmpty()); } +void TabComponent::askToCloseTab(Canvas* cnv) +{ + MessageManager::callAsync([_cnv = SafePointer(cnv), _editor = SafePointer(editor), _this = SafePointer(this)]() mutable { + // Don't show save dialog, if patch is still open in another view + if (_editor && _cnv && _cnv->patch.isDirty()) { + Dialogs::showAskToSaveDialog( + &_editor->openedDialog, _editor, _cnv->patch.getTitle(), + [_cnv, _this](int result) mutable { + if (!_cnv || !_this) return; + if (result == 2) + _cnv->save([_cnv, _this]() mutable { _this->closeTab(_cnv); }); + else if (result == 1) + _this->closeTab(_cnv); + }, + 0, true); + } else if(_this && _cnv) { + _this->closeTab(_cnv); + } + }); +} + void TabComponent::closeTab(Canvas* cnv) { + auto patch = cnv->refCountedPatch; editor->sidebar->hideParameters(); @@ -796,7 +818,7 @@ void TabComponent::showHiddenTabsMenu(int splitIndex) { closeTabButton.setButtonText(Icons::Clear); closeTabButton.addMouseListener(this, false); closeTabButton.onClick = [this]() mutable { - tabbar.closeTab(cnv.getComponent()); + tabbar.askToCloseTab(cnv.getComponent()); }; addChildComponent(closeTabButton); } diff --git a/Source/TabComponent.h b/Source/TabComponent.h index cbcf884fbc..cdfcd650aa 100644 --- a/Source/TabComponent.h +++ b/Source/TabComponent.h @@ -24,6 +24,7 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd void nextTab(); void previousTab(); + void askToCloseTab(Canvas* cnv); void closeTab(Canvas* cnv); void showTab(Canvas* cnv, int splitIndex = 0); void setActiveSplit(Canvas* cnv); @@ -116,7 +117,7 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd TabBarButtonComponent(Canvas* cnv, TabComponent* parent) : cnv(cnv), parent(parent), tabDragConstrainer(parent) { closeButton.onClick = [cnv = SafePointer(cnv), parent](){ - if(cnv) parent->closeTab(cnv); + if(cnv) parent->askToCloseTab(cnv); }; closeButton.addMouseListener(this, false); closeButton.setSize(28, 28); From 13fcf3b5a70e8f20dc0fd447618923fc7e04f451 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 16:09:09 +0200 Subject: [PATCH 0888/1030] Linux fonts fix --- Source/Sidebar/Palettes.h | 2 +- Source/Sidebar/Sidebar.cpp | 4 ++-- Source/Utility/Fonts.h | 4 ---- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Source/Sidebar/Palettes.h b/Source/Sidebar/Palettes.h index 4de046f76a..b60f39c0da 100644 --- a/Source/Sidebar/Palettes.h +++ b/Source/Sidebar/Palettes.h @@ -317,7 +317,7 @@ class PaletteComponent : public Component { void lookAndFeelChanged() override { - nameLabel.setFont(Fonts::getCurrentFont().boldened()); + nameLabel.setFont(Fonts::getBoldFont()); } void showAndGrabEditorFocus() diff --git a/Source/Sidebar/Sidebar.cpp b/Source/Sidebar/Sidebar.cpp index 5a58cb4d96..a665c7f33e 100644 --- a/Source/Sidebar/Sidebar.cpp +++ b/Source/Sidebar/Sidebar.cpp @@ -112,9 +112,9 @@ void Sidebar::paint(Graphics& g) g.fillRect(0, 30, getWidth(), getHeight()); if (inspector->isVisible()) { - Fonts::drawStyledText(g, "Inspector: " + inspector->getTitle(), Rectangle(0, 0, getWidth() - 30, 30), findColour(PlugDataColour::toolbarTextColourId), RegularBoldened, 15, Justification::centred); + Fonts::drawStyledText(g, "Inspector: " + inspector->getTitle(), Rectangle(0, 0, getWidth() - 30, 30), findColour(PlugDataColour::toolbarTextColourId), Bold, 15, Justification::centred); } else { - Fonts::drawStyledText(g, panelNames[currentPanel], Rectangle(0, 0, getWidth() - 30, 30), findColour(PlugDataColour::toolbarTextColourId), RegularBoldened, 15, Justification::centred); + Fonts::drawStyledText(g, panelNames[currentPanel], Rectangle(0, 0, getWidth() - 30, 30), findColour(PlugDataColour::toolbarTextColourId), Bold, 15, Justification::centred); } } } diff --git a/Source/Utility/Fonts.h b/Source/Utility/Fonts.h index 14962c4727..c8d9fa0990 100644 --- a/Source/Utility/Fonts.h +++ b/Source/Utility/Fonts.h @@ -6,7 +6,6 @@ enum FontStyle { Regular, - RegularBoldened, Bold, Semibold, Thin, @@ -89,9 +88,6 @@ struct Fonts { case Regular: font = Fonts::getCurrentFont(); break; - case RegularBoldened: - font = Fonts::getCurrentFont().boldened(); - break; case Bold: font = Fonts::getBoldFont(); break; From a2870ea3b8e658e79912c603f0cbad39bb5391ae Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 16:12:28 +0200 Subject: [PATCH 0889/1030] Remove unused variable --- Source/TabComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index e856a33ee0..3792632c55 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -662,7 +662,7 @@ bool TabComponent::isInterestedInDragSource(SourceDetails const& dragSourceDetai void TabComponent::itemDropped(SourceDetails const& dragSourceDetails) { - if(auto* objectDnD = dynamic_cast(dragSourceDetails.sourceComponent.get())) + if(dynamic_cast(dragSourceDetails.sourceComponent.get())) { auto screenPosition = localPointToGlobal(dragSourceDetails.localPosition); From 27b4136fef77a614d272317f469a539b982769dd Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 17:31:29 +0200 Subject: [PATCH 0890/1030] Fix visual glitch when closing tabs --- Source/NVGSurface.cpp | 12 ------------ Source/PluginEditor.cpp | 2 +- Source/TabComponent.cpp | 30 ++++++++++++++++++++++-------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 234cc0615a..3df97af206 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -279,18 +279,6 @@ void NVGSurface::render() return; // Render on next frame } - bool hasCanvas = editor->getCurrentCanvas() != nullptr; - - // Manage showing/hiding welcome panel - if(hasCanvas && editor->welcomePanel->isVisible()) { - editor->welcomePanel->hide(); - editor->resized(); - } - else if(!hasCanvas && !editor->welcomePanel->isVisible()) { - editor->welcomePanel->show(); - editor->resized(); - } - updateBufferSize(); auto pixelScale = getRenderScale(); diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index de9a7eb6ee..c9281ffa57 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -360,7 +360,7 @@ void PluginEditor::renderArea(NVGcontext* nvg, Rectangle area) pluginMode->render(nvg); } else { - if(!getCurrentCanvas()) + if(welcomePanel->isVisible()) { nvgSave(nvg); welcomePanel->render(nvg); diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 3792632c55..b9b32a04fe 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -6,6 +6,7 @@ #include "Dialogs/Dialogs.h" #include "Utility/Autosave.h" #include "Components/ObjectDragAndDrop.h" +#include "Components/WelcomePanel.h" #include "NVGSurface.h" #include "PluginMode.h" #include "Standalone/PlugDataWindow.h" @@ -293,6 +294,18 @@ void TabComponent::handleAsyncUpdate() } closeEmptySplits(); + + // Show welcome panel if there are no tabs + if(tabbars[0].size() == 0 && tabbars[1].size() == 0) + { + editor->welcomePanel->show(); + editor->resized(); + } + else { + editor->welcomePanel->hide(); + editor->resized(); + } + resized(); // Update tab and canvas layout } @@ -682,14 +695,14 @@ void TabComponent::itemDropped(SourceDetails const& dragSourceDetails) return; } - auto* tab = dynamic_cast(dragSourceDetails.sourceComponent.get()); - - if(getLocalBounds().removeFromRight(getWidth() - splitSize).contains(dragSourceDetails.localPosition) && (dragSourceDetails.localPosition.y > 30 || splits[1])) // Dragging to right split - { - moveToRightSplit(tab); - } - else if(dragSourceDetails.localPosition.y > 30) { - moveToLeftSplit(tab); + if(auto* tab = dynamic_cast(dragSourceDetails.sourceComponent.get())) { + if(getLocalBounds().removeFromRight(getWidth() - splitSize).contains(dragSourceDetails.localPosition) && (dragSourceDetails.localPosition.y > 30 || splits[1])) // Dragging to right split + { + moveToRightSplit(tab); + } + else if(dragSourceDetails.localPosition.y > 30) { + moveToLeftSplit(tab); + } } closeEmptySplits(); @@ -698,6 +711,7 @@ void TabComponent::itemDropped(SourceDetails const& dragSourceDetails) draggingOverTabbar = false; splitDropBounds = Rectangle(); editor->nvgSurface.invalidateAll(); + } void TabComponent::itemDragEnter(SourceDetails const& dragSourceDetails) From 91573ec028c4cf133bf03f0516da99c0034177ec Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 19:32:45 +0200 Subject: [PATCH 0891/1030] Small fix when reloading patches from state --- Source/PluginProcessor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index e7c8190c63..055c3433fa 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1144,6 +1144,7 @@ void PluginProcessor::setStateInformation(void const* data, int sizeInBytes) auto patchPtr = loadPatch(content, nullptr); patchPtr->splitViewIndex = splitIndex; patchPtr->openInPluginMode = pluginMode; + patchPtr->setCurrentFile(URL(location)); if(!location.exists() || (location.exists() && location.getParentDirectory() == File::getSpecialLocation(File::tempDirectory))) { patchPtr->setUntitled(); From d68be797d825f31e88d1dbf14ef6a2f9f4336646 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 19:42:31 +0200 Subject: [PATCH 0892/1030] Small pd update --- Libraries/pure-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/pure-data b/Libraries/pure-data index 9aef545588..6cb0f51703 160000 --- a/Libraries/pure-data +++ b/Libraries/pure-data @@ -1 +1 @@ -Subproject commit 9aef545588467093a9d315851c1c76d0e7484a18 +Subproject commit 6cb0f5170374f8d4852327db95cff0fc39c0737d From cad59366772c035753c04805ee998b298b9d1023 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 21:07:39 +0200 Subject: [PATCH 0893/1030] Fixed theme change issue --- Source/NVGSurface.cpp | 6 ++++++ Source/NVGSurface.h | 2 ++ Source/Standalone/PlugDataWindow.h | 1 - 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 3df97af206..2326f60b8d 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -201,6 +201,12 @@ void NVGSurface::timerCallback() } #endif +void NVGSurface::lookAndFeelChanged() +{ + NVGFramebuffer::clearAll(nvg); + NVGImage::clearAll(nvg); + invalidateAll(); +} void NVGSurface::triggerRepaint() { diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index f49aba0a01..8c0555d6f4 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -50,6 +50,8 @@ public Component, public Timer #ifdef NANOVG_GL_IMPLEMENTATION void timerCallback() override; #endif + + void lookAndFeelChanged() override; Rectangle getInvalidArea() { return invalidArea; } diff --git a/Source/Standalone/PlugDataWindow.h b/Source/Standalone/PlugDataWindow.h index 441baa2fb8..eef6a1f3ae 100644 --- a/Source/Standalone/PlugDataWindow.h +++ b/Source/Standalone/PlugDataWindow.h @@ -464,7 +464,6 @@ class PlugDataWindow : public DocumentWindow #if JUCE_IOS nativeWindow = true; #endif - auto* editor = mainComponent->getEditor(); auto* pdEditor = dynamic_cast(editor); From 5f71c717d0bf8ab5fe23464ee53c715cf423d219 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 21:21:41 +0200 Subject: [PATCH 0894/1030] Tabbar fix --- Source/TabComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index b9b32a04fe..3d8bf6351a 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -853,7 +853,7 @@ void TabComponent::showHiddenTabsMenu(int splitIndex) { if (e.originalComponent == &closeTabButton) return; - tabbar.showTab(cnv); + tabbar.showTab(cnv, cnv->patch.splitViewIndex); triggerMenuItem(); } From 577417058efd278add2666d56454a9d6665c9995 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 21:27:51 +0200 Subject: [PATCH 0895/1030] Fixed performance regression --- Source/Canvas.cpp | 6 ------ Source/Canvas.h | 4 +--- Source/Object.cpp | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index d3aa32290f..1c472e0578 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -542,12 +542,6 @@ float Canvas::getRenderScale() const return editor->nvgSurface.getRenderScale(); } - -void Canvas::lookAndFeelChanged() -{ - //presentationShadowImage = -1; -} - void Canvas::updatePatchSnapshot() { auto patchFile = patch.getCurrentFile(); diff --git a/Source/Canvas.h b/Source/Canvas.h index a061e5060d..07a6c9263c 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -144,9 +144,7 @@ class Canvas : public Component void updatePatchSnapshot(); float getRenderScale() const; - - void lookAndFeelChanged() override; - + bool autoscroll(MouseEvent const& e); // Multi-dragger functions diff --git a/Source/Object.cpp b/Source/Object.cpp index 35e9b50599..eca2190b75 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -1215,7 +1215,7 @@ void Object::render(NVGcontext* nvg) activityOverlayDirty = false; } - if(cnv->isScrolling && scrollBuffer.needsUpdate(getWidth(), getHeight())) + if(cnv->isScrolling && scrollBuffer.needsUpdate(getWidth() * 3.0f * cnv->getRenderScale(), getHeight() * 3.0f * cnv->getRenderScale())) { performRender(nvg); } From 3856be72882570dd5e907a284053d6a20b5e3eb3 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 21:28:55 +0200 Subject: [PATCH 0896/1030] Fixed label performance issue --- Source/Objects/ObjectBase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index cf2d5e59d4..b1404b8d04 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -46,7 +46,7 @@ class ObjectLabel : public Label, public NVGComponent { void renderLabel(NVGcontext* nvg, float scale) { auto textHash = hash(getText()); - if(image.needsUpdate(getWidth(), getHeight()) || updateColour || lastTextHash != textHash || lastScale != scale) + if(image.needsUpdate(getWidth() * scale, getHeight() * scale) || updateColour || lastTextHash != textHash || lastScale != scale) { updateImage(nvg, scale); lastTextHash = textHash; From b99d3b8730686ae0aaac4910093c288935132cd9 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Thu, 6 Jun 2024 21:30:24 +0200 Subject: [PATCH 0897/1030] Another performance fix --- Source/Components/WelcomePanel.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Components/WelcomePanel.h b/Source/Components/WelcomePanel.h index 905932048e..f9fe99c82c 100644 --- a/Source/Components/WelcomePanel.h +++ b/Source/Components/WelcomePanel.h @@ -64,17 +64,17 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater auto* nvg = dynamic_cast(g.getInternalContext()).getContext(); auto textWidth = bounds.getWidth() - 8; - if(titleImage.needsUpdate(textWidth, 24) || subtitleImage.needsUpdate(textWidth, 16)) + if(titleImage.needsUpdate(textWidth * 2, 24 * 2) || subtitleImage.needsUpdate(textWidth * 2, 16 * 2)) { auto textColour = findColour(PlugDataColour::panelTextColourId); - titleImage = NVGImage(nvg, textWidth * 2.0f, 24 * 2.0f, [this, textColour, textWidth](Graphics& g){ + titleImage = NVGImage(nvg, textWidth * 2, 24 * 2, [this, textColour, textWidth](Graphics& g){ g.addTransform(AffineTransform::scale(2.0f, 2.0f)); g.setColour(textColour); g.setFont(Fonts::getBoldFont().withHeight(14)); g.drawText(tileName, Rectangle(0, 0, textWidth, 24), Justification::centredLeft, true); }); - subtitleImage = NVGImage(nvg, textWidth * 2.0f, 16 * 2.0f, [this, textColour, textWidth](Graphics& g){ + subtitleImage = NVGImage(nvg, textWidth * 2, 16 * 2, [this, textColour, textWidth](Graphics& g){ g.addTransform(AffineTransform::scale(2.0f, 2.0f)); g.setColour(textColour.withAlpha(0.75f)); g.setFont(Fonts::getDefaultFont().withHeight(13.5f)); From 13a2af225ccc2db7e2c0529b6d4a755b8dee57a9 Mon Sep 17 00:00:00 2001 From: dreamer Date: Fri, 7 Jun 2024 02:37:19 +0200 Subject: [PATCH 0898/1030] reorder export options; add Source + GUI --- Source/Heavy/DPFExporter.h | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Source/Heavy/DPFExporter.h b/Source/Heavy/DPFExporter.h index c343a28e14..e4a7f89ad3 100644 --- a/Source/Heavy/DPFExporter.h +++ b/Source/Heavy/DPFExporter.h @@ -15,7 +15,7 @@ class DPFExporter : public ExporterBase { Value clapEnableValue = Value(var(1)); Value jackEnableValue = Value(var(0)); - Value exportTypeValue = Value(var(2)); + Value exportTypeValue = Value(var(1)); Value pluginTypeValue = Value(var(1)); PropertiesPanelProperty* midiinProperty; @@ -25,7 +25,7 @@ class DPFExporter : public ExporterBase { : ExporterBase(editor, exportingView) { Array properties; - properties.add(new PropertiesPanel::ComboComponent("Export type", exportTypeValue, { "Source code", "Binary" })); + properties.add(new PropertiesPanel::ComboComponent("Export type", exportTypeValue, { "Binary", "Source code", "Source + GUI code" })); properties.add(new PropertiesPanel::ComboComponent("Plugin type", pluginTypeValue, { "Effect", "Instrument", "Custom" })); midiinProperty = new PropertiesPanel::BoolComponent("Midi Input", midiinEnableValue, { "No", "yes" }); @@ -128,6 +128,7 @@ class DPFExporter : public ExporterBase { args.add("\"" + copyright + "\""); } + int exportType = getValue(exportTypeValue); int midiin = getValue(midiinEnableValue); int midiout = getValue(midioutEnableValue); @@ -158,7 +159,7 @@ class DPFExporter : public ExporterBase { DynamicObject::Ptr metaJson(new DynamicObject()); var metaDPF(new DynamicObject()); - metaDPF.getDynamicObject()->setProperty("project", "true"); + metaDPF.getDynamicObject()->setProperty("project", true); metaDPF.getDynamicObject()->setProperty("description", "Rename Me"); metaDPF.getDynamicObject()->setProperty("maker", "Wasted Audio"); metaDPF.getDynamicObject()->setProperty("license", "ISC"); @@ -166,6 +167,10 @@ class DPFExporter : public ExporterBase { metaDPF.getDynamicObject()->setProperty("midi_output", midiout); metaDPF.getDynamicObject()->setProperty("plugin_formats", formats); + if (exportType == 3) { + metaDPF.getDynamicObject()->setProperty("enable_ui", true); + } + metaJson->setProperty("dpf", metaDPF); args.add("-m" + createMetaJson(metaJson)); @@ -204,7 +209,7 @@ class DPFExporter : public ExporterBase { bool generationExitCode = getExitCode(); // Check if we need to compile - if (!generationExitCode && getValue(exportTypeValue) == 2) { + if (!generationExitCode && exportType == 1) { auto workingDir = File::getCurrentWorkingDirectory(); outputFile.setAsCurrentWorkingDirectory(); From b086a7d85e645d407ad85fb98127edb3e9731041 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 7 Jun 2024 14:14:02 +0930 Subject: [PATCH 0899/1030] Reset editor canvases state when leaving plugin mode. Scale / Position / Mode is not saved in a PD-Patch, so we need to keep the canvases around, or have a way to save the old state and re-init --- Source/PluginMode.h | 1 - Source/TabComponent.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/Source/PluginMode.h b/Source/PluginMode.h index 9b3a12baaa..0cca3763e2 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -385,7 +385,6 @@ class PluginMode : public Component, public NVGComponent { private: std::unique_ptr cnv; - SafePointer originalCanvas; PluginEditor* editor; ComponentPeer* desktopWindow; diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 3d8bf6351a..9312e42289 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -218,7 +218,6 @@ void TabComponent::handleAsyncUpdate() { if(patch->openInPluginMode) // Found pluginmode patch { - canvases.clear(); editor->pluginMode = std::make_unique(editor, patch); editor->resized(); lastPluginModePatchPtr = patch->getPointer().get(); From 34776f0d1535f198fdbae1d12d488a5956fc1085 Mon Sep 17 00:00:00 2001 From: alcomposer Date: Fri, 7 Jun 2024 16:14:18 +0930 Subject: [PATCH 0900/1030] remember last set plugin mode scale when re-entering plugin mode from editor NOTE: we use a member vector for scale, as this isn't changing, and it's safer to keep scale as an int as opposed to a float due to possible rounding errors --- Source/PluginEditor.cpp | 1 + Source/PluginEditor.h | 4 ++++ Source/PluginMode.h | 36 +++++++++++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index c9281ffa57..f4ba15778d 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -39,6 +39,7 @@ using namespace juce::gl; #include +std::map PluginEditor::pluginModeScaleMap; PluginEditor::PluginEditor(PluginProcessor& p) : AudioProcessorEditor(&p) diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index deab32e8d6..460fcddb7c 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -9,6 +9,8 @@ #include #include +#include + #include "Utility/Fonts.h" #include "Utility/ModifierKeyListener.h" #include "Components/CheckedTooltip.h" @@ -174,6 +176,8 @@ class PluginEditor : public AudioProcessorEditor std::unique_ptr welcomePanel; CheckedTooltip tooltipWindow; + + static std::map pluginModeScaleMap; private: diff --git a/Source/PluginMode.h b/Source/PluginMode.h index 0cca3763e2..4f2556e2df 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -10,7 +10,6 @@ #include "Canvas.h" #include "Standalone/PlugDataWindow.h" - class PluginMode : public Component, public NVGComponent { public: explicit PluginMode(PluginEditor* editor, pd::Patch::Ptr patch) @@ -19,6 +18,7 @@ class PluginMode : public Component, public NVGComponent { , editor(editor) , desktopWindow(editor->getPeer()) , windowBounds(editor->getBounds().withPosition(editor->getTopLevelComponent()->getPosition())) + , patchPtr(patch) { if (ProjectInfo::isStandalone) { // If the window is already maximised, unmaximise it to prevent problems @@ -71,7 +71,12 @@ class PluginMode : public Component, public NVGComponent { // Add this view to the editor editor->addAndMakeVisible(this); - scaleComboBox.addItemList({ "50%", "75%", "100%", "125%", "150%", "175%", "200%" }, 1); + StringArray itemList; + for (auto scale : pluginScales ){ + itemList.add(String(scale.intScale) + "%"); + } + + scaleComboBox.addItemList(itemList, 1); if (ProjectInfo::isStandalone) { scaleComboBox.addSeparator(); scaleComboBox.addItem("Fullscreen", 8); @@ -87,10 +92,10 @@ class PluginMode : public Component, public NVGComponent { setKioskMode(true); return; } - auto scale = std::vector { 0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f }[itemId - 1]; if (selectedItemId != itemId) { selectedItemId = itemId; - setWidthAndHeight(scale); + setWidthAndHeight(pluginScales[itemId - 1].floatScale); + pluginPreviousScale = pluginScales[itemId - 1].intScale; } }; @@ -98,7 +103,16 @@ class PluginMode : public Component, public NVGComponent { addAndMakeVisible(titleBar); - setWidthAndHeight(1.0f); + // set scale to the last scale that was set for this patches plugin mode + // if none was set, use 100% scale + if (PluginEditor::pluginModeScaleMap.contains(patchPtr->getPointer().get())) + { + int previousScale = PluginEditor::pluginModeScaleMap[patchPtr->getPointer().get()]; + scaleComboBox.setText(String(previousScale) + String("%"), dontSendNotification); + setWidthAndHeight(previousScale * 0.01f); + } else { + setWidthAndHeight(1.0f); + } cnv->connectionLayer.setVisible(false); } @@ -157,6 +171,9 @@ class PluginMode : public Component, public NVGComponent { void closePluginMode() { + // save the current scale in map for retrieval, so plugin mode remembers the last set scale + PluginEditor::pluginModeScaleMap[patchPtr->getPointer().get()] = pluginPreviousScale; + if (auto* mainWindow = dynamic_cast(editor->getTopLevelComponent())) { bool isUsingNativeTitlebar = SettingsFile::getInstance()->getProperty("native_window"); if (isUsingNativeTitlebar) { @@ -384,6 +401,7 @@ class PluginMode : public Component, public NVGComponent { } private: + pd::Patch::Ptr patchPtr; std::unique_ptr cnv; PluginEditor* editor; ComponentPeer* desktopWindow; @@ -407,4 +425,12 @@ class PluginMode : public Component, public NVGComponent { float const width = float(cnv->patchWidth.getValue()) + 1.0f; float const height = float(cnv->patchHeight.getValue()) + 1.0f; float pluginModeScale = 1.0f; + int pluginPreviousScale = 100; + + struct Scale { + float floatScale; + int intScale; + }; + std::vector pluginScales { Scale{0.5f, 50}, Scale{0.75f, 75}, Scale{1.0f, 100}, Scale{1.25f, 125}, Scale{1.5f, 150}, Scale{1.75f, 175}, Scale{2.0f, 200} }; + }; From c80e3c37cf09af5d6f87a6657055069be278c69e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 12:49:07 +0200 Subject: [PATCH 0901/1030] Fix multi-window tab drag-and-drop --- Source/PluginMode.h | 2 +- Source/TabComponent.cpp | 118 +++++++++++++++++++++++++++++++++++----- 2 files changed, 105 insertions(+), 15 deletions(-) diff --git a/Source/PluginMode.h b/Source/PluginMode.h index 4f2556e2df..687f5f5244 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -14,11 +14,11 @@ class PluginMode : public Component, public NVGComponent { public: explicit PluginMode(PluginEditor* editor, pd::Patch::Ptr patch) : NVGComponent(this) + , patchPtr(patch) , cnv(std::make_unique(editor, patch, this)) , editor(editor) , desktopWindow(editor->getPeer()) , windowBounds(editor->getBounds().withPosition(editor->getTopLevelComponent()->getPosition())) - , patchPtr(patch) { if (ProjectInfo::isStandalone) { // If the window is already maximised, unmaximise it to prevent problems diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 9312e42289..0a7c222857 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -93,12 +93,51 @@ Canvas* TabComponent::openPatch(pd::Patch::Ptr existingPatch) void TabComponent::moveToLeftSplit(TabBarButtonComponent* tab) { + if(tab->parent != this) // Move to another window + { + if(tab->parent->tabbars[0].contains(tab)) + { + auto patch = tab->cnv->refCountedPatch; + patch->windowIndex = pd->getEditors().indexOf(editor); + + auto* oldTabbar = tab->parent; + oldTabbar->canvases.removeObject(tab->cnv); + oldTabbar->tabbars[0].removeObject(tab); + + auto* cnv = canvases.add(new Canvas(editor, patch)); + cnv->jumpToOrigin(); + tabbars[0].add(new TabBarButtonComponent(cnv, this)); + showTab(cnv, 1); + + triggerAsyncUpdate(); + oldTabbar->triggerAsyncUpdate(); + } + else if(tab->parent->tabbars[1].contains(tab)) + { + auto patch = tab->cnv->refCountedPatch; + patch->windowIndex = pd->getEditors().indexOf(editor); + + auto* oldTabbar = tab->parent; + oldTabbar->canvases.removeObject(tab->cnv); + oldTabbar->tabbars[1].removeObject(tab); + + auto* cnv = canvases.add(new Canvas(editor, patch)); + cnv->jumpToOrigin(); + tabbars[0].add(new TabBarButtonComponent(cnv, this)); + showTab(cnv, 1); + + triggerAsyncUpdate(); + oldTabbar->triggerAsyncUpdate(); + } + return; + } + if(tabbars[1].size() && splits[1] && tabbars[1].indexOf(tab) >= 0) { tabbars[0].add(tabbars[1].removeAndReturn(tabbars[1].indexOf(tab))); // Move tab to left tabbar if(tabbars[1].size()) showTab(tabbars[1][0]->cnv, 1); // Show first tab of right tabbar, if there are any tabs left else showTab(nullptr, 1); // If no tabs are left on right tabbar - showTab(tab->cnv, 0); // Show moved tab in left tabbar + showTab(tab->cnv, 0); // Show moved tab on left split } else if(tabbars[0].size() > 1 && splits[0] && !splits[1] && tabbars[0].indexOf(tab) >= 0) // If we try to create a left splits when there are no splits open { @@ -119,6 +158,45 @@ void TabComponent::moveToLeftSplit(TabBarButtonComponent* tab) void TabComponent::moveToRightSplit(TabBarButtonComponent* tab) { + if(tab->parent != this) // Move to another window + { + if(tab->parent->tabbars[0].contains(tab)) + { + auto patch = tab->cnv->refCountedPatch; + patch->windowIndex = pd->getEditors().indexOf(editor); + + auto* oldTabbar = tab->parent; + oldTabbar->canvases.removeObject(tab->cnv); + oldTabbar->tabbars[0].removeObject(tab); + + auto* cnv = canvases.add(new Canvas(editor, patch)); + cnv->jumpToOrigin(); + tabbars[1].add(new TabBarButtonComponent(cnv, this)); + showTab(cnv, 1); + + triggerAsyncUpdate(); + oldTabbar->triggerAsyncUpdate(); + } + else if(tab->parent->tabbars[1].contains(tab)) + { + auto patch = tab->cnv->refCountedPatch; + patch->windowIndex = pd->getEditors().indexOf(editor); + + auto* oldTabbar = tab->parent; + oldTabbar->canvases.removeObject(tab->cnv); + oldTabbar->tabbars[1].removeObject(tab); + + auto* cnv = canvases.add(new Canvas(editor, patch)); + cnv->jumpToOrigin(); + tabbars[1].add(new TabBarButtonComponent(cnv, this)); + showTab(cnv, 1); + + triggerAsyncUpdate(); + oldTabbar->triggerAsyncUpdate(); + } + return; + } + if(tabbars[0].size() && splits[0] && tabbars[0].indexOf(tab) >= 0) { tabbars[1].add(tabbars[0].removeAndReturn(tabbars[0].indexOf(tab))); // Move tab to right tabbar @@ -218,6 +296,7 @@ void TabComponent::handleAsyncUpdate() { if(patch->openInPluginMode) // Found pluginmode patch { + canvases.clear(); editor->pluginMode = std::make_unique(editor, patch); editor->resized(); lastPluginModePatchPtr = patch->getPointer().get(); @@ -239,7 +318,7 @@ void TabComponent::handleAsyncUpdate() bool exists = false; for(auto& patch : pd->patches) { - if(canvases[i]->patch == *patch) + if(canvases[i]->patch == *patch && canvases[i]->patch.windowIndex == editorIndex) { exists = true; } @@ -458,8 +537,8 @@ void TabComponent::mouseDrag(const MouseEvent& e) { auto localPos = e.getEventRelativeTo(this).getPosition(); if(draggingSplitResizer) { - splitSize = localPos.x; - splitProportion = getWidth() / static_cast(localPos.x); + splitProportion = std::clamp(getWidth() / static_cast(localPos.x), 1.25f, 5.0f); + splitSize = getWidth() / splitProportion; resized(); } } @@ -724,25 +803,36 @@ void TabComponent::itemDragExit(SourceDetails const& dragSourceDetails) tab->setVisible(false); splitDropBounds = Rectangle(); draggingOverTabbar = false; + editor->nvgSurface.invalidateAll(); } void TabComponent::saveTabPositions() { + auto editors = pd->getEditors(); + Array> sortedPatches; - for(int i = 0; i < tabbars.size(); i++) { - for(int j = 0; j < tabbars[i].size(); j++) - { - if(auto* cnv = tabbars[i][j]->cnv.getComponent()) { - cnv->patch.splitViewIndex = i; - sortedPatches.add({ cnv->refCountedPatch, j }); + for(int e = 0; e < editors.size(); e++) { + auto& tabbar = editors[e]->getTabComponent(); + for(int i = 0; i < tabbar.tabbars.size(); i++) { + for(int j = 0; j < tabbar.tabbars[i].size(); j++) + { + if(auto* cnv = tabbar.tabbars[i][j]->cnv.getComponent()) { + cnv->patch.splitViewIndex = i; + cnv->patch.windowIndex = e; + sortedPatches.add({ cnv->refCountedPatch, j }); + } } } } std::sort(sortedPatches.begin(), sortedPatches.end(), [](auto const& a, auto const& b) { - if (a.first->splitViewIndex == b.first->splitViewIndex) - return a.second < b.second; - return a.first->splitViewIndex < b.first->splitViewIndex; + if (a.first->windowIndex != b.first->windowIndex) + return a.first->windowIndex < b.first->windowIndex; + + if (a.first->splitViewIndex != b.first->splitViewIndex) + return a.first->splitViewIndex < b.first->splitViewIndex; + + return a.second < b.second; }); pd->patches.getLock().enter(); @@ -799,7 +889,7 @@ void TabComponent::itemDragMove(SourceDetails const& dragSourceDetails) else if(getLocalBounds().removeFromRight(getWidth() - splitSize).contains(dragSourceDetails.localPosition)) // Dragging over right split { draggingOverTabbar = false; - splitDropBounds = getLocalBounds().removeFromRight(splitSize); + splitDropBounds = getLocalBounds().removeFromRight(getWidth() - splitSize); tab->setVisible(false); } else { // Dragging over left split From db029509c0d30f5b6648a9500dfcd6ab8ab0a17e Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 12:58:10 +0200 Subject: [PATCH 0902/1030] Allow drag and drop on welcome panel --- Source/Utility/ZoomableDragAndDropContainer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Utility/ZoomableDragAndDropContainer.cpp b/Source/Utility/ZoomableDragAndDropContainer.cpp index 88fecb6299..ffbe155293 100644 --- a/Source/Utility/ZoomableDragAndDropContainer.cpp +++ b/Source/Utility/ZoomableDragAndDropContainer.cpp @@ -403,6 +403,10 @@ class ZoomableDragAndDropContainer::DragImageComponent : public Component auto details = sourceDetails; while (hit != nullptr) { + if(auto* ddt = dynamic_cast(hit)) + { + hit = &ddt->getTabComponent(); + } if (auto* ddt = dynamic_cast(hit)) { if (ddt->isInterestedInDragSource(details)) { relativePos = hit->getLocalPoint(nullptr, screenPos); From 2b4615baa982f3a044aece730d4bbee4038e812c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 13:21:52 +0200 Subject: [PATCH 0903/1030] Consider window index for plugin mode --- Source/TabComponent.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 0a7c222857..67e3b2416e 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -289,29 +289,26 @@ void TabComponent::handleAsyncUpdate() tabbars[0].clear(); tabbars[1].clear(); - if(pd->isInPluginMode() && !editor->pluginMode) + auto editorIndex = pd->getEditors().indexOf(editor); + + if(pd->isInPluginMode()) { // Initialise plugin mode for(auto& patch : pd->patches) { - if(patch->openInPluginMode) // Found pluginmode patch + if(patch->openInPluginMode && patch->windowIndex == editorIndex) // Found pluginmode patch for current window { canvases.clear(); - editor->pluginMode = std::make_unique(editor, patch); - editor->resized(); - lastPluginModePatchPtr = patch->getPointer().get(); + if(!editor->pluginMode) { + editor->pluginMode = std::make_unique(editor, patch); + editor->resized(); + lastPluginModePatchPtr = patch->getPointer().get(); + } return; } } } - else if(pd->isInPluginMode()) - { - canvases.clear(); - return; - } - - auto editorIndex = pd->getEditors().indexOf(editor); - + // First, remove canvases that no longer exist for(int i = canvases.size() - 1; i >= 0; i--) { From 5f38e5c13140c2dec4818e379940601ec4125348 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 13:28:13 +0200 Subject: [PATCH 0904/1030] Multi-window drag-and-drop fix --- Source/TabComponent.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 67e3b2416e..ee40dd2481 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -107,7 +107,7 @@ void TabComponent::moveToLeftSplit(TabBarButtonComponent* tab) auto* cnv = canvases.add(new Canvas(editor, patch)); cnv->jumpToOrigin(); tabbars[0].add(new TabBarButtonComponent(cnv, this)); - showTab(cnv, 1); + showTab(cnv, 0); triggerAsyncUpdate(); oldTabbar->triggerAsyncUpdate(); @@ -124,7 +124,7 @@ void TabComponent::moveToLeftSplit(TabBarButtonComponent* tab) auto* cnv = canvases.add(new Canvas(editor, patch)); cnv->jumpToOrigin(); tabbars[0].add(new TabBarButtonComponent(cnv, this)); - showTab(cnv, 1); + showTab(cnv, 0); triggerAsyncUpdate(); oldTabbar->triggerAsyncUpdate(); @@ -852,6 +852,11 @@ void TabComponent::itemDragMove(SourceDetails const& dragSourceDetails) Rectangle oldSplitDropBounds = splitDropBounds; + if(!splits[1]) { + splitProportion = 2; + splitSize = getWidth() / 2; + } + if(getLocalBounds().removeFromTop(30).contains(dragSourceDetails.localPosition)) // Dragging over tabbar { draggingOverTabbar = true; From e9196bdac9b00c285c631aa94fe0904c715d23a8 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 13:46:02 +0200 Subject: [PATCH 0905/1030] More reliable way to keep track of PluginEditors --- Source/PluginEditor.cpp | 2 ++ Source/PluginEditor.h | 3 +++ Source/TabComponent.cpp | 26 +++++++++++++++----------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index f4ba15778d..f5646c4164 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -263,6 +263,8 @@ PluginEditor::PluginEditor(PluginProcessor& p) addChildComponent(nvgSurface); nvgSurface.toBehind(&tabComponent); + editorIndex = numEditors++; + #if JUCE_IOS addAndMakeVisible(touchSelectionHelper.get()); touchSelectionHelper->setAlwaysOnTop(true); diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 460fcddb7c..bd1112b0a7 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -178,6 +178,7 @@ class PluginEditor : public AudioProcessorEditor CheckedTooltip tooltipWindow; static std::map pluginModeScaleMap; + int editorIndex; private: @@ -200,6 +201,8 @@ class PluginEditor : public AudioProcessorEditor bool isMaximised = false; bool isDraggingFile = false; + + static inline int numEditors = 0; // Used in plugin std::unique_ptr> cornerResizer; diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index ee40dd2481..68eeb27ac9 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -78,7 +78,7 @@ Canvas* TabComponent::openPatch(pd::Patch::Ptr existingPatch) cnv->locked.setValue(true); existingPatch->splitViewIndex = activeSplitIndex; - existingPatch->windowIndex = pd->getEditors().indexOf(editor); + existingPatch->windowIndex = editor->editorIndex; triggerAsyncUpdate(); pd->titleChanged(); @@ -98,7 +98,7 @@ void TabComponent::moveToLeftSplit(TabBarButtonComponent* tab) if(tab->parent->tabbars[0].contains(tab)) { auto patch = tab->cnv->refCountedPatch; - patch->windowIndex = pd->getEditors().indexOf(editor); + patch->windowIndex = editor->editorIndex; auto* oldTabbar = tab->parent; oldTabbar->canvases.removeObject(tab->cnv); @@ -115,7 +115,7 @@ void TabComponent::moveToLeftSplit(TabBarButtonComponent* tab) else if(tab->parent->tabbars[1].contains(tab)) { auto patch = tab->cnv->refCountedPatch; - patch->windowIndex = pd->getEditors().indexOf(editor); + patch->windowIndex = editor->editorIndex; auto* oldTabbar = tab->parent; oldTabbar->canvases.removeObject(tab->cnv); @@ -163,7 +163,7 @@ void TabComponent::moveToRightSplit(TabBarButtonComponent* tab) if(tab->parent->tabbars[0].contains(tab)) { auto patch = tab->cnv->refCountedPatch; - patch->windowIndex = pd->getEditors().indexOf(editor); + patch->windowIndex = editor->editorIndex; auto* oldTabbar = tab->parent; oldTabbar->canvases.removeObject(tab->cnv); @@ -180,7 +180,7 @@ void TabComponent::moveToRightSplit(TabBarButtonComponent* tab) else if(tab->parent->tabbars[1].contains(tab)) { auto patch = tab->cnv->refCountedPatch; - patch->windowIndex = pd->getEditors().indexOf(editor); + patch->windowIndex = editor->editorIndex; auto* oldTabbar = tab->parent; oldTabbar->canvases.removeObject(tab->cnv); @@ -269,7 +269,7 @@ void TabComponent::createNewWindow(Component* draggedTab) auto patch = tab->cnv->refCountedPatch; closeTab(tab->cnv); - patch->windowIndex = pd->getEditors().size() - 1; + patch->windowIndex = newEditor->editorIndex; auto* newCanvas = newEditor->getTabComponent().openPatch(patch); newCanvas->jumpToOrigin(); @@ -289,7 +289,7 @@ void TabComponent::handleAsyncUpdate() tabbars[0].clear(); tabbars[1].clear(); - auto editorIndex = pd->getEditors().indexOf(editor); + auto editorIndex = editor->editorIndex; if(pd->isInPluginMode()) { @@ -361,7 +361,7 @@ void TabComponent::handleAsyncUpdate() { if(canvas->patch.getPointer().get() == lastPluginModePatchPtr) { - showTab(canvas); + showTab(canvas, canvas->patch.splitViewIndex); break; } } @@ -429,6 +429,8 @@ void TabComponent::closeEmptySplits() void TabComponent::showTab(Canvas* cnv, int splitIndex) { + if(cnv == splits[splitIndex]) return; + if(splits[splitIndex]) removeChildComponent(splits[splitIndex]->viewport.get()); splits[splitIndex] = cnv; @@ -451,6 +453,8 @@ void TabComponent::showTab(Canvas* cnv, int splitIndex) tab->tabChanged(); } + editor->sidebar->hideParameters(); + editor->sidebar->clearSearchOutliner(); editor->updateCommandStatus(); } @@ -808,14 +812,14 @@ void TabComponent::saveTabPositions() auto editors = pd->getEditors(); Array> sortedPatches; - for(int e = 0; e < editors.size(); e++) { - auto& tabbar = editors[e]->getTabComponent(); + for(auto* editor : pd->getEditors()) { + auto& tabbar = editor->getTabComponent(); for(int i = 0; i < tabbar.tabbars.size(); i++) { for(int j = 0; j < tabbar.tabbars[i].size(); j++) { if(auto* cnv = tabbar.tabbars[i][j]->cnv.getComponent()) { cnv->patch.splitViewIndex = i; - cnv->patch.windowIndex = e; + cnv->patch.windowIndex = editor->editorIndex; sortedPatches.add({ cnv->refCountedPatch, j }); } } From 7848131e20f97e7b068782069cf5eab987a2a63a Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 14:04:40 +0200 Subject: [PATCH 0906/1030] Multi-window fix --- Source/TabComponent.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 68eeb27ac9..a6ac766b6f 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -80,14 +80,12 @@ Canvas* TabComponent::openPatch(pd::Patch::Ptr existingPatch) existingPatch->splitViewIndex = activeSplitIndex; existingPatch->windowIndex = editor->editorIndex; - triggerAsyncUpdate(); - pd->titleChanged(); - showTab(cnv, activeSplitIndex); - closeEmptySplits(); - cnv->jumpToOrigin(); + triggerAsyncUpdate(); + pd->titleChanged(); + return cnv; } From 2b377a1c1a09141d4221eeca114dde436d1c8275 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 15:12:03 +0200 Subject: [PATCH 0907/1030] Fixed openGL bugs --- Source/NVGSurface.cpp | 50 +++++++++++++++++++++-------------------- Source/NVGSurface.h | 16 ++++++++----- Source/TabComponent.cpp | 1 + 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 2326f60b8d..1c5414498b 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -147,31 +147,33 @@ void NVGSurface::initialise() void NVGSurface::detachContext() { - NVGFramebuffer::clearAll(nvg); - NVGImage::clearAll(nvg); - - if(invalidFBO) { - nvgDeleteFramebuffer(invalidFBO); - invalidFBO = nullptr; - } - if(mainFBO) { - nvgDeleteFramebuffer(mainFBO); - mainFBO = nullptr; - } - if(nvg) - { - nvgDeleteContext(nvg); - nvg = nullptr; - } - + if(makeContextActive()) { + NVGFramebuffer::clearAll(nvg); + NVGImage::clearAll(nvg); + + if(invalidFBO) { + nvgDeleteFramebuffer(invalidFBO); + invalidFBO = nullptr; + } + if(mainFBO) { + nvgDeleteFramebuffer(mainFBO); + mainFBO = nullptr; + } + if(nvg) + { + nvgDeleteContext(nvg); + nvg = nullptr; + } + #ifdef NANOVG_METAL_IMPLEMENTATION - if(auto* view = getView()) { - OSUtils::MTLDeleteView(view); - setView(nullptr); - } + if(auto* view = getView()) { + OSUtils::MTLDeleteView(view); + setView(nullptr); + } #else - if(glContext) glContext->detach(); + glContext->detach(); #endif + } } void NVGSurface::updateBufferSize() @@ -180,7 +182,7 @@ void NVGSurface::updateBufferSize() int scaledWidth = getWidth() * pixelScale; int scaledHeight = getHeight() * pixelScale; - if(fbWidth != scaledWidth || fbHeight != scaledHeight || !mainFBO) { + if((fbWidth != scaledWidth || fbHeight != scaledHeight || !mainFBO) && makeContextActive()) { if(invalidFBO) nvgDeleteFramebuffer(invalidFBO); if(mainFBO) nvgDeleteFramebuffer(mainFBO); mainFBO = nvgCreateFramebuffer(nvg, scaledWidth, scaledHeight, NVG_IMAGE_PREMULTIPLIED); @@ -359,7 +361,7 @@ void NVGSurface::render() auto elapsed = Time::getMillisecondCounter() - startTime; // We update frambuffers after we call swapBuffers to make sure the frame is on time - if(elapsed < 14) { + if(elapsed < 14 && makeContextActive()) { for(auto* cnv : editor->getTabComponent().getVisibleCanvases()) { cnv->updateFramebuffers(nvg, cnv->getLocalBounds(), 14 - elapsed); diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 8c0555d6f4..238f1bd870 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -237,10 +237,12 @@ class NVGImage { for(auto* image : allImages) { - if(image->isValid() && image->nvg == nvg) nvgDeleteImage(image->nvg, image->imageId); - image->imageId = 0; - if (image->onImageInvalidate) - image->onImageInvalidate(); + if(image->isValid() && image->nvg == nvg) { + nvgDeleteImage(image->nvg, image->imageId); + image->imageId = 0; + if (image->onImageInvalidate) + image->onImageInvalidate(); + } } } @@ -343,8 +345,10 @@ class NVGFramebuffer { for(auto* buffer : allFramebuffers) { - if(buffer->nvg == nvg && buffer->fb) nvgDeleteFramebuffer(buffer->fb); - buffer->fb = nullptr; + if(buffer->nvg == nvg && buffer->fb) { + nvgDeleteFramebuffer(buffer->fb); + buffer->fb = nullptr; + } } } diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index a6ac766b6f..2fa12d3953 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -274,6 +274,7 @@ void TabComponent::createNewWindow(Component* draggedTab) newWindow->setTopLeftPosition(Desktop::getInstance().getMousePosition() - Point(500, 60)); newWindow->toFront(true); + newEditor->nvgSurface.detachContext(); } void TabComponent::openInPluginMode(pd::Patch::Ptr patch) From f123161364cdaf5627cc672423344025dedc9fd0 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 15:36:28 +0200 Subject: [PATCH 0908/1030] Fix for deleting framebuffers --- Source/NVGSurface.cpp | 19 ++++++++++++++++--- Source/NVGSurface.h | 14 ++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 1c5414498b..423e41fca0 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -134,6 +134,8 @@ void NVGSurface::initialise() nvg = nvgCreateContext(NVG_ANTIALIAS); #endif + surfaces[nvg] = this; + invalidateAll(); if (!nvg) std::cerr << "could not initialise nvg" << std::endl; @@ -163,6 +165,7 @@ void NVGSurface::detachContext() { nvgDeleteContext(nvg); nvg = nullptr; + surfaces.erase(nvg); } #ifdef NANOVG_METAL_IMPLEMENTATION @@ -205,9 +208,11 @@ void NVGSurface::timerCallback() void NVGSurface::lookAndFeelChanged() { - NVGFramebuffer::clearAll(nvg); - NVGImage::clearAll(nvg); - invalidateAll(); + if(makeContextActive()) { + NVGFramebuffer::clearAll(nvg); + NVGImage::clearAll(nvg); + invalidateAll(); + } } void NVGSurface::triggerRepaint() @@ -368,3 +373,11 @@ void NVGSurface::render() } } } + + +NVGSurface* NVGSurface::getSurfaceForContext(NVGcontext* nvg) +{ + if(!surfaces.count(nvg)) return nullptr; + + return surfaces[nvg]; +} diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 238f1bd870..a5fbb738ba 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -99,6 +99,8 @@ public Component, public Timer NVGcontext* getRawContext() { return nvg; } + static NVGSurface* getSurfaceForContext(NVGcontext*); + private: void resized() override; @@ -113,6 +115,8 @@ public Component, public Timer NVGframebuffer* invalidFBO = nullptr; int fbWidth = 0, fbHeight = 0; + static inline std::map surfaces; + bool hresize = false; bool resizing = false; Rectangle newBounds; @@ -229,6 +233,11 @@ class NVGImage ~NVGImage() { + if(auto* surface = NVGSurface::getSurfaceForContext(nvg)) + { + surface->makeContextActive(); + } + if(imageId && nvg) nvgDeleteImage(nvg, imageId); allImages.erase(this); } @@ -337,7 +346,12 @@ class NVGFramebuffer ~NVGFramebuffer() { + if(auto* surface = NVGSurface::getSurfaceForContext(nvg)) + { + surface->makeContextActive(); + } + nvgDeleteFramebuffer(fb); allFramebuffers.erase(this); } From 9cfb8483bb4a2bde348b117d7a05a3ec110f6b56 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 15:55:46 +0200 Subject: [PATCH 0909/1030] Fixes for buffer management, fix for pluginmode --- Source/Iolet.cpp | 2 +- Source/NVGSurface.h | 5 ++++- Source/TabComponent.cpp | 15 ++++++++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index 6305fa88ec..5bbf7c8ef3 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -60,7 +60,7 @@ void Iolet::render(NVGcontext* nvg) if (!isVisible()) return; - auto fb = cnv->ioletBuffer; + auto& fb = cnv->ioletBuffer; if(!fb.isValid()) return; bool isLocked = getValue(locked) || getValue(commandLocked); diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index a5fbb738ba..51ad72f3f3 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -351,7 +351,10 @@ class NVGFramebuffer surface->makeContextActive(); } - nvgDeleteFramebuffer(fb); + if(fb) { + nvgDeleteFramebuffer(fb); + fb = nullptr; + } allFramebuffers.erase(this); } diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 2fa12d3953..ec9fcfcf40 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -39,11 +39,16 @@ Canvas* TabComponent::newPatch() Canvas* TabComponent::openPatch(const URL& path) { auto patchFile = path.getLocalFile(); - for (auto* cnv : canvases) { - if (cnv->patch.getCurrentFile() == patchFile) { + + for (auto& patch : pd->patches) { + if (patch->getCurrentFile() == patchFile) { pd->logError("Patch is already open"); - showTab(cnv); - return cnv; + for (auto* cnv : canvases) { + if(cnv->patch == *patch) { + showTab(cnv); + return cnv; + } + } } } @@ -295,7 +300,7 @@ void TabComponent::handleAsyncUpdate() // Initialise plugin mode for(auto& patch : pd->patches) { - if(patch->openInPluginMode && patch->windowIndex == editorIndex) // Found pluginmode patch for current window + if(patch->openInPluginMode && (patch->windowIndex == editorIndex || ProjectInfo::isStandalone)) // Found pluginmode patch for current window { canvases.clear(); if(!editor->pluginMode) { From 36cf6a9d5bc0d05ae400236f82102988f494d7a2 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 15:56:36 +0200 Subject: [PATCH 0910/1030] Small fix --- Source/TabComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index ec9fcfcf40..f877a8ba83 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -300,7 +300,7 @@ void TabComponent::handleAsyncUpdate() // Initialise plugin mode for(auto& patch : pd->patches) { - if(patch->openInPluginMode && (patch->windowIndex == editorIndex || ProjectInfo::isStandalone)) // Found pluginmode patch for current window + if(patch->openInPluginMode && (patch->windowIndex == editorIndex || !ProjectInfo::isStandalone)) // Found pluginmode patch for current window { canvases.clear(); if(!editor->pluginMode) { From 7ac56e07a27dba0531843795fe02f0429ea0f1d2 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 16:06:46 +0200 Subject: [PATCH 0911/1030] Fix for editor index tracking --- Source/PluginEditor.cpp | 2 +- Source/TabComponent.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index f5646c4164..ee47c7088a 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -263,7 +263,7 @@ PluginEditor::PluginEditor(PluginProcessor& p) addChildComponent(nvgSurface); nvgSurface.toBehind(&tabComponent); - editorIndex = numEditors++; + editorIndex = ProjectInfo::isStandalone ? numEditors++ : 0; #if JUCE_IOS addAndMakeVisible(touchSelectionHelper.get()); diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index f877a8ba83..4ad31e0e1c 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -300,7 +300,7 @@ void TabComponent::handleAsyncUpdate() // Initialise plugin mode for(auto& patch : pd->patches) { - if(patch->openInPluginMode && (patch->windowIndex == editorIndex || !ProjectInfo::isStandalone)) // Found pluginmode patch for current window + if(patch->openInPluginMode && patch->windowIndex == editorIndex) // Found pluginmode patch for current window { canvases.clear(); if(!editor->pluginMode) { From e540a5c6ed929e453abfabc7a317acefa375ec19 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 16:09:02 +0200 Subject: [PATCH 0912/1030] Plugin crash fix --- Source/NVGSurface.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 423e41fca0..0130f59cec 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -181,11 +181,13 @@ void NVGSurface::detachContext() void NVGSurface::updateBufferSize() { + if(!makeContextActive()) return; + float pixelScale = getRenderScale(); int scaledWidth = getWidth() * pixelScale; int scaledHeight = getHeight() * pixelScale; - if((fbWidth != scaledWidth || fbHeight != scaledHeight || !mainFBO) && makeContextActive()) { + if(fbWidth != scaledWidth || fbHeight != scaledHeight || !mainFBO) { if(invalidFBO) nvgDeleteFramebuffer(invalidFBO); if(mainFBO) nvgDeleteFramebuffer(mainFBO); mainFBO = nvgCreateFramebuffer(nvg, scaledWidth, scaledHeight, NVG_IMAGE_PREMULTIPLIED); From df2a804fdb039638be4cb01d2679103865138a97 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 16:17:36 +0200 Subject: [PATCH 0913/1030] nanovg and pluginmode fixes --- Source/NVGSurface.cpp | 10 +++++----- Source/PluginMode.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index 0130f59cec..ce839eb126 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -181,8 +181,6 @@ void NVGSurface::detachContext() void NVGSurface::updateBufferSize() { - if(!makeContextActive()) return; - float pixelScale = getRenderScale(); int scaledWidth = getWidth() * pixelScale; int scaledHeight = getHeight() * pixelScale; @@ -294,10 +292,12 @@ void NVGSurface::render() return; // Render on next frame } + if(!makeContextActive()) return; + updateBufferSize(); auto pixelScale = getRenderScale(); - if(!invalidArea.isEmpty() && makeContextActive()) { + if(!invalidArea.isEmpty()) { auto invalidated = invalidArea.expanded(1); // First, draw only the invalidated region to a separate framebuffer @@ -334,7 +334,7 @@ void NVGSurface::render() invalidArea = Rectangle(0, 0, 0, 0); } - if(needsBufferSwap && makeContextActive()) { + if(needsBufferSwap) { float pixelScale = getRenderScale(); nvgViewport(0, 0, getWidth() * pixelScale, getHeight() * pixelScale); @@ -368,7 +368,7 @@ void NVGSurface::render() auto elapsed = Time::getMillisecondCounter() - startTime; // We update frambuffers after we call swapBuffers to make sure the frame is on time - if(elapsed < 14 && makeContextActive()) { + if(elapsed < 14) { for(auto* cnv : editor->getTabComponent().getVisibleCanvases()) { cnv->updateFramebuffers(nvg, cnv->getLocalBounds(), 14 - elapsed); diff --git a/Source/PluginMode.h b/Source/PluginMode.h index 687f5f5244..79e18feff1 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -42,9 +42,9 @@ class PluginMode : public Component, public NVGComponent { #else mainWindow->setOpaque(false); #endif - editor->nvgSurface.detachContext(); } - + editor->nvgSurface.detachContext(); + desktopWindow = editor->getPeer(); editor->nvgSurface.invalidateAll(); From d7372b5af1120c4b4580c8d8f1b3253e6af1a2dc Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 17:15:35 +0200 Subject: [PATCH 0914/1030] Apply minimum bounds for window when closing plugin mode --- Source/PluginMode.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Source/PluginMode.h b/Source/PluginMode.h index 79e18feff1..f60f8907d8 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -173,26 +173,27 @@ class PluginMode : public Component, public NVGComponent { { // save the current scale in map for retrieval, so plugin mode remembers the last set scale PluginEditor::pluginModeScaleMap[patchPtr->getPointer().get()] = pluginPreviousScale; - + + auto constrainedNewBounds = windowBounds.withWidth(std::max(windowBounds.getWidth(), 850)).withHeight(std::max(windowBounds.getHeight(), 650)); if (auto* mainWindow = dynamic_cast(editor->getTopLevelComponent())) { bool isUsingNativeTitlebar = SettingsFile::getInstance()->getProperty("native_window"); if (isUsingNativeTitlebar) { mainWindow->setResizeLimits(850, 650, 99000, 99000); mainWindow->setOpaque(true); mainWindow->setUsingNativeTitleBar(true); - editor->nvgSurface.detachContext(); } editor->constrainer.setSizeLimits(850, 650, 99000, 99000); #if JUCE_LINUX || JUCE_BSD OSUtils::updateX11Constraints(getPeer()->getNativeHandle()); #endif - - auto correctedPosition = windowBounds.getTopLeft() - Point(0, nativeTitleBarHeight); - mainWindow->setBoundsConstrained(windowBounds.withPosition(correctedPosition)); + auto correctedPosition = constrainedNewBounds.getTopLeft() - Point(0, nativeTitleBarHeight); + mainWindow->setBoundsConstrained(constrainedNewBounds.withPosition(correctedPosition)); } else { editor->pluginConstrainer.setSizeLimits(850, 650, 99000, 99000); - editor->setBounds(windowBounds); + editor->setBounds(constrainedNewBounds); } + + editor->nvgSurface.detachContext(); cnv->patch.openInPluginMode = false; From fbe752e29f1b8711598a62cdb697bc504d424fc8 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 17:16:29 +0200 Subject: [PATCH 0915/1030] Fix image/framebuffer bugs --- Source/NVGSurface.h | 22 ++++++++++++---------- Source/Pd/Patch.h | 2 ++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 51ad72f3f3..6c8d844d37 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -233,12 +233,14 @@ class NVGImage ~NVGImage() { - if(auto* surface = NVGSurface::getSurfaceForContext(nvg)) - { - surface->makeContextActive(); + if(imageId && nvg) { + if(auto* surface = NVGSurface::getSurfaceForContext(nvg)) + { + surface->makeContextActive(); + } + + nvgDeleteImage(nvg, imageId); } - - if(imageId && nvg) nvgDeleteImage(nvg, imageId); allImages.erase(this); } @@ -346,12 +348,12 @@ class NVGFramebuffer ~NVGFramebuffer() { - if(auto* surface = NVGSurface::getSurfaceForContext(nvg)) - { - surface->makeContextActive(); - } - if(fb) { + if(auto* surface = NVGSurface::getSurfaceForContext(nvg)) + { + surface->makeContextActive(); + } + nvgDeleteFramebuffer(fb); fb = nullptr; } diff --git a/Source/Pd/Patch.h b/Source/Pd/Patch.h index 7da8fea688..af2678f568 100644 --- a/Source/Pd/Patch.h +++ b/Source/Pd/Patch.h @@ -118,9 +118,11 @@ class Patch : public ReferenceCountedObject { Instance* instance = nullptr; bool closePatchOnDelete; + bool openInPluginMode = false; int splitViewIndex = 0; int windowIndex = 0; + String lastUndoSequence; String lastRedoSequence; From 2085ce89d08b7ca7c78042b434fa2342d02c9730 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 17:23:24 +0200 Subject: [PATCH 0916/1030] Fixed daw databuffer bug --- Source/Pd/Instance.cpp | 9 ++++----- Source/Pd/Instance.h | 4 ---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 690c24ed99..3cf73d79e1 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -217,7 +217,6 @@ void Instance::initialisePd(String& pdlua_version) parameterModeReceiver = pd::Setup::createReceiver(this, "param_mode", reinterpret_cast(internal::instance_multi_bang), reinterpret_cast(internal::instance_multi_float), reinterpret_cast(internal::instance_multi_symbol), reinterpret_cast(internal::instance_multi_list), reinterpret_cast(internal::instance_multi_message)); - atoms = malloc(sizeof(t_atom) * 512); // Register callback when pd's gui changes // Needs to be done on pd's thread @@ -442,15 +441,15 @@ void Instance::sendSymbol(char const* receiver, char const* symbol) const void Instance::sendList(char const* receiver, std::vector const& list) const { - auto* argv = static_cast(atoms); + auto argv = std::vector(list.size()); libpd_set_instance(static_cast(instance)); for (size_t i = 0; i < list.size(); ++i) { if (list[i].isFloat()) - libpd_set_float(argv + i, list[i].getFloat()); + libpd_set_float(argv.data() + i, list[i].getFloat()); else - libpd_set_symbol(argv + i, list[i].getSymbol()->s_name); + libpd_set_symbol(argv.data() + i, list[i].getSymbol()->s_name); } - libpd_list(receiver, static_cast(list.size()), argv); + libpd_list(receiver, static_cast(list.size()), argv.data()); } void Instance::sendTypedMessage(void* object, char const* msg, std::vector const& list) const diff --git a/Source/Pd/Instance.h b/Source/Pd/Instance.h index 8a5aa049b7..7d3c936541 100644 --- a/Source/Pd/Instance.h +++ b/Source/Pd/Instance.h @@ -299,8 +299,6 @@ class Instance { bool loadLibrary(String const& library); void* instance = nullptr; - void* patch = nullptr; - void* atoms = nullptr; void* messageReceiver = nullptr; void* parameterReceiver = nullptr; void* pluginLatencyReceiver = nullptr; @@ -310,8 +308,6 @@ class Instance { void* parameterModeReceiver = nullptr; void* midiReceiver = nullptr; void* printReceiver = nullptr; - - // JYG added this void* dataBufferReceiver = nullptr; inline static String const defaultPatch = "#N canvas 827 239 527 327 12;"; From 2799c895d79fb5c858251f169f357963a042a17c Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 17:24:51 +0200 Subject: [PATCH 0917/1030] Compilation fix --- Source/Pd/Instance.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 3cf73d79e1..5cda1b33ba 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -459,16 +459,16 @@ void Instance::sendTypedMessage(void* object, char const* msg, std::vector libpd_set_instance(static_cast(instance)); - auto* argv = static_cast(atoms); + auto argv = std::vector(list.size()); for (size_t i = 0; i < list.size(); ++i) { if (list[i].isFloat()) - libpd_set_float(argv + i, list[i].getFloat()); + libpd_set_float(argv.data() + i, list[i].getFloat()); else - libpd_set_symbol(argv + i, list[i].getSymbol()->s_name); + libpd_set_symbol(argv.data() + i, list[i].getSymbol()->s_name); } - pd_typedmess(static_cast(object), generateSymbol(msg), static_cast(list.size()), argv); + pd_typedmess(static_cast(object), generateSymbol(msg), static_cast(list.size()), argv.data()); } void Instance::sendMessage(char const* receiver, char const* msg, std::vector const& list) const @@ -545,16 +545,16 @@ void Instance::processSend(dmessage mess) { if (auto obj = mess.object.get()) { if (mess.selector == "list") { - auto* argv = static_cast(atoms); + auto argv = std::vector(mess.list.size()); for (size_t i = 0; i < mess.list.size(); ++i) { if (mess.list[i].isFloat()) - SETFLOAT(argv + i, mess.list[i].getFloat()); + SETFLOAT(argv.data() + i, mess.list[i].getFloat()); else if (mess.list[i].isSymbol()) { - SETSYMBOL(argv + i, mess.list[i].getSymbol()); + SETSYMBOL(argv.data() + i, mess.list[i].getSymbol()); } else - SETFLOAT(argv + i, 0.0); + SETFLOAT(argv.data() + i, 0.0); } - pd_list(obj.get(), generateSymbol("list"), static_cast(mess.list.size()), argv); + pd_list(obj.get(), generateSymbol("list"), static_cast(mess.list.size()), argv.data()); } else if (mess.selector == "float" && !mess.list.empty() && mess.list[0].isFloat()) { pd_float(obj.get(), mess.list[0].getFloat()); } else if (mess.selector == "symbol" && !mess.list.empty() && mess.list[0].isSymbol()) { From f40f933f431929855de7b1bec4cd46829ae91284 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Fri, 7 Jun 2024 17:34:53 +0200 Subject: [PATCH 0918/1030] Plugin mode crash fix --- Source/Canvas.cpp | 15 +++++++-------- Source/PluginEditor.cpp | 11 +++++------ Source/PluginEditor.h | 2 -- Source/PluginMode.h | 2 -- Source/TabComponent.cpp | 15 ++++++++++++--- Source/TabComponent.h | 4 ++++ 6 files changed, 28 insertions(+), 21 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 1c472e0578..180881d760 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -300,12 +300,11 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) const auto halfSize = infiniteCanvasSize / 2; const auto zoom = getValue(zoomScale); - const auto hasViewport = viewport && !editor->pluginMode; - + // apply translation to the canvas nvg objects nvgSave(nvg); - if(hasViewport) { + if(viewport) { nvgTranslate(nvg, -viewport->getViewPositionX(), -viewport->getViewPositionY()); nvgScale(nvg, zoom, zoom); @@ -316,7 +315,7 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) nvgFillRect(nvg, invalidRegion.getX(), invalidRegion.getY(), invalidRegion.getWidth(), invalidRegion.getHeight()); } - if(hasViewport && !getValue(locked)) { + if(viewport && !getValue(locked)) { nvgBeginPath(nvg); nvgRect(nvg, 0, 0, infiniteCanvasSize, infiniteCanvasSize); @@ -359,8 +358,8 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) nvgRestore(nvg); } } - auto drawBorder = [this, nvg, hasViewport, backgroundColour, zoom, dotsColour](bool bg, bool fg) { - if (hasViewport && (showOrigin || showBorder) && !::getValue(presentationMode)) { + auto drawBorder = [this, nvg, backgroundColour, zoom, dotsColour](bool bg, bool fg) { + if (viewport && (showOrigin || showBorder) && !::getValue(presentationMode)) { nvgBeginPath(nvg); const auto borderWidth = getValue(patchWidth); @@ -433,7 +432,7 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) if (::getValue(presentationMode) || isGraph) { renderAllObjects(nvg, invalidRegion); // render presentation mode as clipped 'virtual' plugin view - if (::getValue(presentationMode) && !editor->pluginMode) { + if (::getValue(presentationMode)) { const auto borderWidth = getValue(patchWidth); const auto borderHeight = getValue(patchHeight); const auto pos = Point(halfSize, halfSize); @@ -1052,7 +1051,7 @@ void Canvas::mouseDown(MouseEvent const& e) editor->updateCommandStatus(); } // Right click - else if (!editor->pluginMode) { + else { Dialogs::showCanvasRightClickMenu(this, source, e.getScreenPosition()); } } diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index ee47c7088a..9cf146ea47 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -47,7 +47,6 @@ PluginEditor::PluginEditor(PluginProcessor& p) , sidebar(std::make_unique(&p, this)) , statusbar(std::make_unique(&p)) , openedDialog(nullptr) - , pluginMode(nullptr) , offlineRenderer(&p) , nvgSurface(this) , pluginConstrainer(*getConstrainer()) @@ -293,8 +292,8 @@ void PluginEditor::setUseBorderResizer(bool shouldUse) borderResizer->setVisible(true); resized(); // Makes sure resizer gets resized - if (pluginMode) { - borderResizer->toBehind(pluginMode.get()); + if (auto* pluginMode = tabComponent.getPluginModeComponent()) { + borderResizer->toBehind(pluginMode); } } else { if (!cornerResizer) { @@ -359,7 +358,7 @@ void PluginEditor::paintOverChildren(Graphics& g) void PluginEditor::renderArea(NVGcontext* nvg, Rectangle area) { - if(pluginMode) { + if(auto* pluginMode = tabComponent.getPluginModeComponent()) { pluginMode->render(nvg); } else { @@ -419,7 +418,7 @@ DragAndDropTarget* PluginEditor::findNextDragAndDropTarget(Point screenPos) void PluginEditor::resized() { - if (pluginMode && pd->isInPluginMode()) { + if (auto* pluginMode = tabComponent.getPluginModeComponent()) { nvgSurface.updateBounds(getLocalBounds().withTrimmedTop(pluginMode->isWindowFullscreen() ? 0 : 40)); return; } @@ -888,7 +887,7 @@ void PluginEditor::getCommandInfo(CommandID const commandID, ApplicationCommandI case CommandIDs::PanDragKey: { result.setInfo("Pan drag key", "Pan drag key", "View", ApplicationCommandInfo::dontTriggerAlertSound); result.addDefaultKeypress(KeyPress::spaceKey, ModifierKeys::noModifiers); - result.setActive(hasCanvas && !isDragging && !pluginMode); + result.setActive(hasCanvas && !isDragging && !tabComponent.getPluginModeComponent()); break; } case CommandIDs::ZoomIn: { diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index bd1112b0a7..e964952135 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -148,8 +148,6 @@ class PluginEditor : public AudioProcessorEditor std::unique_ptr openedDialog; - std::unique_ptr pluginMode; - Value theme; Value hvccMode; diff --git a/Source/PluginMode.h b/Source/PluginMode.h index f60f8907d8..fe0ed7d3b5 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -204,8 +204,6 @@ class PluginMode : public Component, public NVGComponent { cnv->connectionLayer.setVisible(true); editor->nvgSurface.invalidateAll(); - // Destroy this view - editor->pluginMode.reset(nullptr); } bool isWindowFullscreen() const diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 4ad31e0e1c..f5468502cd 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -11,7 +11,7 @@ #include "PluginMode.h" #include "Standalone/PlugDataWindow.h" -TabComponent::TabComponent(PluginEditor* editor) : editor(editor), pd(editor->pd) +TabComponent::TabComponent(PluginEditor* editor) : pluginMode(nullptr), editor(editor), pd(editor->pd) { for(int i = 0; i < tabbars.size(); i++) { @@ -282,6 +282,11 @@ void TabComponent::createNewWindow(Component* draggedTab) newEditor->nvgSurface.detachContext(); } +PluginMode* TabComponent::getPluginModeComponent() +{ + return pluginMode.get(); +} + void TabComponent::openInPluginMode(pd::Patch::Ptr patch) { patch->openInPluginMode = true; @@ -303,8 +308,8 @@ void TabComponent::handleAsyncUpdate() if(patch->openInPluginMode && patch->windowIndex == editorIndex) // Found pluginmode patch for current window { canvases.clear(); - if(!editor->pluginMode) { - editor->pluginMode = std::make_unique(editor, patch); + if(!pluginMode) { + pluginMode = std::make_unique(editor, patch); editor->resized(); lastPluginModePatchPtr = patch->getPointer().get(); } @@ -312,6 +317,10 @@ void TabComponent::handleAsyncUpdate() } } } + else if(pluginMode) + { + pluginMode.reset(nullptr); + } // First, remove canvases that no longer exist for(int i = canvases.size() - 1; i >= 0; i--) diff --git a/Source/TabComponent.h b/Source/TabComponent.h index cdfcd650aa..230e628806 100644 --- a/Source/TabComponent.h +++ b/Source/TabComponent.h @@ -3,6 +3,7 @@ #include "Utility/ZoomableDragAndDropContainer.h" #include "PluginProcessor.h" +class PluginMode; class TabComponent : public Component, public DragAndDropTarget, public AsyncUpdater { class TabBarButtonComponent; @@ -38,6 +39,8 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd Array getCanvases(); Array getVisibleCanvases(); + PluginMode* getPluginModeComponent(); + private: void handleAsyncUpdate() override; @@ -325,6 +328,7 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd OwnedArray canvases; + std::unique_ptr pluginMode; t_glist* lastPluginModePatchPtr = nullptr; PluginEditor* editor; From ff85f2de8f49b255ba7f110ac51d08b983d7d718 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 8 Jun 2024 01:12:59 +0200 Subject: [PATCH 0919/1030] Fixed unable to open patches from file in new window --- Source/Canvas.cpp | 2 -- Source/Components/WelcomePanel.h | 2 +- Source/Dialogs/MainMenu.h | 2 +- Source/PluginEditor.cpp | 2 +- Source/TabComponent.cpp | 2 +- Source/Utility/Autosave.h | 6 +----- 6 files changed, 5 insertions(+), 11 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 180881d760..9b05d4013a 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -760,8 +760,6 @@ void Canvas::tabChanged() synchronise(); updateDrawables(); - // update GraphOnParent when changing tabs - // TODO: shouldn't we do this always on sync? for (auto* obj : objects) { if (!obj->gui) continue; diff --git a/Source/Components/WelcomePanel.h b/Source/Components/WelcomePanel.h index f9fe99c82c..7f1e390da0 100644 --- a/Source/Components/WelcomePanel.h +++ b/Source/Components/WelcomePanel.h @@ -253,7 +253,7 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater auto* tile = tiles.add(new WelcomePanelTile(patchFile.getFileName(), timeDescription, silhoutteSvg, snapshotColour, 1.0f, favourited)); tile->onClick = [this, patchFile]() mutable { - editor->autosave->checkForMoreRecentAutosave(patchFile, [this, patchFile]() { + editor->autosave->checkForMoreRecentAutosave(patchFile, editor, [this, patchFile]() { editor->getTabComponent().openPatch(URL(patchFile)); SettingsFile::getInstance()->addToRecentlyOpened(patchFile); editor->pd->titleChanged(); diff --git a/Source/Dialogs/MainMenu.h b/Source/Dialogs/MainMenu.h index 20169800e7..39fe1beaec 100644 --- a/Source/Dialogs/MainMenu.h +++ b/Source/Dialogs/MainMenu.h @@ -30,7 +30,7 @@ class MainMenu : public PopupMenu { for (int i = 0; i < recentlyOpenedTree.getNumChildren(); i++) { auto path = File(recentlyOpenedTree.getChild(i).getProperty("Path").toString()); recentlyOpened->addItem(path.getFileName(), [path, editor]() mutable { - editor->autosave->checkForMoreRecentAutosave(path, [editor, path]() { + editor->autosave->checkForMoreRecentAutosave(path, editor, [editor, path]() { editor->getTabComponent().openPatch(URL(path)); SettingsFile::getInstance()->addToRecentlyOpened(path); }); diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 9cf146ea47..a82b0879ef 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -629,7 +629,7 @@ void PluginEditor::filesDropped(StringArray const& files, int x, int y) auto file = File(path); if (file.exists() && file.hasFileExtension("pd")) { openedPdFiles = true; - autosave->checkForMoreRecentAutosave(file, [this, file]() { + autosave->checkForMoreRecentAutosave(file, editor, [this, file]() { tabComponent.openPatch(URL(file)); SettingsFile::getInstance()->addToRecentlyOpened(file); pd->titleChanged(); diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index f5468502cd..0c0f1e0564 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -213,7 +213,7 @@ void TabComponent::openPatch() Dialogs::showOpenDialog([this](URL resultURL) { auto result = resultURL.getLocalFile(); if (result.exists() && result.getFileExtension().equalsIgnoreCase(".pd")) { - editor->autosave->checkForMoreRecentAutosave(result, [this, result, resultURL]() { + editor->autosave->checkForMoreRecentAutosave(result, editor, [this, result, resultURL]() { openPatch(resultURL); SettingsFile::getInstance()->addToRecentlyOpened(result); }); diff --git a/Source/Utility/Autosave.h b/Source/Utility/Autosave.h index 2390532b39..5ab9cca34c 100644 --- a/Source/Utility/Autosave.h +++ b/Source/Utility/Autosave.h @@ -38,12 +38,8 @@ class Autosave : public Timer } // Call this whenever we load a file - void checkForMoreRecentAutosave(File& patchPath, std::function callback) + void checkForMoreRecentAutosave(File& patchPath, PluginEditor* editor, std::function callback) { - auto* editor = dynamic_cast(pd->getActiveEditor()); - if (!editor) - return; - auto lastAutoSavedPatch = autoSaveTree.getChildWithProperty("Path", patchPath.getFullPathName()); auto autoSavedTime = static_cast(lastAutoSavedPatch.getProperty("LastModified")); auto fileChangedTime = patchPath.getLastModificationTime().toMilliseconds(); From a959e673bc1f1af3b565a62fa010c6ec62b1057f Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 8 Jun 2024 01:13:24 +0200 Subject: [PATCH 0920/1030] Compilation fix --- Source/PluginEditor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index a82b0879ef..71473a607d 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -629,7 +629,7 @@ void PluginEditor::filesDropped(StringArray const& files, int x, int y) auto file = File(path); if (file.exists() && file.hasFileExtension("pd")) { openedPdFiles = true; - autosave->checkForMoreRecentAutosave(file, editor, [this, file]() { + autosave->checkForMoreRecentAutosave(file, this, [this, file]() { tabComponent.openPatch(URL(file)); SettingsFile::getInstance()->addToRecentlyOpened(file); pd->titleChanged(); From fdea808b6f80c0cc645738f474b6b506819ebdc3 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 8 Jun 2024 12:35:16 +0200 Subject: [PATCH 0921/1030] Don't derive ObjectThemeManager from Component --- Source/Utility/ObjectThemeManager.h | 37 +++++++++++++++++++---------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/Source/Utility/ObjectThemeManager.h b/Source/Utility/ObjectThemeManager.h index e32b4ebf4e..aa1f6cc2ed 100644 --- a/Source/Utility/ObjectThemeManager.h +++ b/Source/Utility/ObjectThemeManager.h @@ -12,19 +12,30 @@ * This is a static class that handles all theming & formatting for UI objects placed onto the canvas */ -class ObjectThemeManager : public Component { +class ObjectThemeManager { public: - ObjectThemeManager() = default; + static inline ObjectThemeManager* instance = nullptr; + + static ObjectThemeManager* get() + { + if(!instance) + { + instance = new ObjectThemeManager(); + } + + return instance; + } - void lookAndFeelChanged() override + void updateTheme() { - bg = findColour(PlugDataColour::guiObjectBackgroundColourId); - fg = findColour(PlugDataColour::canvasTextColourId); - lbl = findColour(PlugDataColour::toolbarTextColourId); - ln = findColour(PlugDataColour::guiObjectInternalOutlineColour); + auto& lnf = LookAndFeel::getDefaultLookAndFeel(); + bg = lnf.findColour(PlugDataColour::guiObjectBackgroundColourId); + fg = lnf.findColour(PlugDataColour::canvasTextColourId); + lbl = lnf.findColour(PlugDataColour::toolbarTextColourId); + ln = lnf.findColour(PlugDataColour::guiObjectInternalOutlineColour); } - static String getCompleteFormat(String& name) + String getCompleteFormat(String& name) { StringArray token; token.add(name); @@ -32,7 +43,7 @@ class ObjectThemeManager : public Component { return String("#X obj 0 0 " + token.joinIntoString(" ")); } - static void formatObject(StringArray& tokens) + void formatObject(StringArray& tokens) { // See if we have preset parameters for this object // These parameters are designed to make the experience in plugdata better @@ -64,10 +75,10 @@ class ObjectThemeManager : public Component { } private: - inline static Colour bg = Colour(); - inline static Colour fg = Colour(); - inline static Colour lbl = Colour(); - inline static Colour ln = Colour(); + Colour bg = Colour(); + Colour fg = Colour(); + Colour lbl = Colour(); + Colour ln = Colour(); // Initialisation parameters for GUI objects // Taken from pd save files, this will make sure that it directly initialises objects with the right parameters From 9c15c29d1f3cf564439a3c8ecec6f4f684922750 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 8 Jun 2024 12:45:00 +0200 Subject: [PATCH 0922/1030] Cleaned up, make viewport remember its last position --- Source/Canvas.cpp | 17 ++- Source/Canvas.h | 3 + Source/Dialogs/AddObjectMenu.h | 2 +- Source/Dialogs/ObjectBrowserDialog.h | 4 +- Source/Object.cpp | 6 +- Source/ObjectGrid.cpp | 2 + Source/Pd/Patch.cpp | 4 +- Source/Pd/Patch.h | 3 +- Source/PluginEditor.cpp | 26 ++-- Source/PluginEditor.h | 8 +- Source/PluginMode.h | 21 ++-- Source/PluginProcessor.cpp | 2 +- Source/Statusbar.cpp | 16 ++- Source/Statusbar.h | 9 +- Source/TabComponent.cpp | 181 ++++++++++++++------------- Source/TabComponent.h | 64 +++++----- 16 files changed, 212 insertions(+), 156 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 9b05d4013a..b4e00de574 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -107,7 +107,7 @@ Canvas::Canvas(PluginEditor* parent, pd::Patch::Ptr p, Component* parentGraph) canvasViewport->setScrollBarsShown(true, true, true, true); viewport.reset(canvasViewport); // Owned by the tabbar, but doesn't exist for graph! - jumpToOrigin(); + jumpToLastKnownPosition(); } commandLocked.referTo(pd->commandLocked); @@ -176,6 +176,7 @@ Canvas::Canvas(PluginEditor* parent, pd::Patch::Ptr p, Component* parentGraph) Canvas::~Canvas() { + saveViewportPosition(); zoomScale.removeListener(this); editor->removeModifierKeyListener(this); pd->unregisterMessageListener(patch.getPointer().get(), this); @@ -710,6 +711,19 @@ void Canvas::jumpToOrigin() viewport->setViewPosition(canvasOrigin.transformedBy(getTransform()) + Point(1, 1)); } +void Canvas::jumpToLastKnownPosition() +{ + viewport->setViewPosition((patch.lastViewportPosition + canvasOrigin).transformedBy(getTransform())); +} + +void Canvas::saveViewportPosition() +{ + if(viewport) + { + patch.lastViewportPosition = viewport->getViewPosition() - canvasOrigin; + } +} + void Canvas::zoomToFitAll() { if (objects.isEmpty() || !viewport) @@ -2072,6 +2086,7 @@ void Canvas::valueChanged(Value& v) // Update zoom if (v.refersToSameSourceAs(zoomScale)) { editor->statusbar->updateZoomLevel(); + patch.lastViewportScale = getValue(zoomScale); hideSuggestions(); } else if (v.refersToSameSourceAs(patchWidth)) { // limit canvas width to smallest object (11px) diff --git a/Source/Canvas.h b/Source/Canvas.h index 07a6c9263c..e6e7e46160 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -139,6 +139,9 @@ class Canvas : public Component void redo(); void jumpToOrigin(); + void jumpToLastKnownPosition(); + void saveViewportPosition(); + void zoomToFitAll(); void updatePatchSnapshot(); diff --git a/Source/Dialogs/AddObjectMenu.h b/Source/Dialogs/AddObjectMenu.h index 86e42e527b..cc5ce0e574 100644 --- a/Source/Dialogs/AddObjectMenu.h +++ b/Source/Dialogs/AddObjectMenu.h @@ -160,7 +160,7 @@ class ObjectList : public Component { if (objectPatch.isEmpty()) objectPatch = "#X obj 0 0 " + name; else if (!objectPatch.startsWith("#")) { - objectPatch = editor->getObjectManager()->getCompleteFormat(objectPatch); + objectPatch = ObjectThemeManager::get()->getCompleteFormat(objectPatch); } auto* button = objectButtons.add(new ObjectItem(editor, name, icon, tooltip, objectPatch, objectID, dismissMenu)); addAndMakeVisible(button); diff --git a/Source/Dialogs/ObjectBrowserDialog.h b/Source/Dialogs/ObjectBrowserDialog.h index 1f4732e309..a6b4acf066 100644 --- a/Source/Dialogs/ObjectBrowserDialog.h +++ b/Source/Dialogs/ObjectBrowserDialog.h @@ -148,7 +148,7 @@ class ObjectsListBox : public ListBox String getObjectString() override { - return PluginEditor::getObjectManager()->getCompleteFormat(objectName); + return ObjectThemeManager::get()->getCompleteFormat(objectName); } String getPatchStringName() override @@ -282,7 +282,7 @@ class ObjectViewerDragArea : public ObjectDragAndDrop { String getObjectString() override { - return PluginEditor::getObjectManager()->getCompleteFormat(objectName); + return ObjectThemeManager::get()->getCompleteFormat(objectName); } String getPatchStringName() override diff --git a/Source/Object.cpp b/Source/Object.cpp index eca2190b75..0a3ad86db4 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -1046,8 +1046,10 @@ void Object::mouseDrag(MouseEvent const& e) object->setTopLeftPosition(newPosition); } - - cnv->autoscroll(e.getEventRelativeTo(cnv->viewport.get())); + + if(cnv->viewport) { + cnv->autoscroll(e.getEventRelativeTo(cnv->viewport.get())); + } } // This handles the "unsnap" action when you shift-drag a connected object diff --git a/Source/ObjectGrid.cpp b/Source/ObjectGrid.cpp index 059757540c..88f36c6614 100644 --- a/Source/ObjectGrid.cpp +++ b/Source/ObjectGrid.cpp @@ -25,6 +25,8 @@ ObjectGrid::ObjectGrid(Canvas* cnv) : cnv(cnv) Array ObjectGrid::getSnappableObjects(Object* draggedObject) { auto& cnv = draggedObject->cnv; + if(!cnv->viewport) return {}; + Array snappable; auto scaleFactor = std::sqrt(std::abs(cnv->getTransform().getDeterminant())); diff --git a/Source/Pd/Patch.cpp b/Source/Pd/Patch.cpp index ff554d59bd..f1ab9a634c 100644 --- a/Source/Pd/Patch.cpp +++ b/Source/Pd/Patch.cpp @@ -235,7 +235,7 @@ t_gobj* Patch::createObject(int x, int y, String const& name) StringArray tokens; tokens.addTokens(name.replace("\\ ", "__%SPACE%__"), true); // Prevent "/ " from being tokenised - PluginEditor::getObjectManager()->formatObject(tokens); + ObjectThemeManager::get()->formatObject(tokens); if (tokens[0] == "garray") { if (auto patch = ptr.get()) { @@ -319,7 +319,7 @@ t_gobj* Patch::renameObject(t_object* obj, String const& name) StringArray tokens; tokens.addTokens(name, false); - PluginEditor::getObjectManager()->formatObject(tokens); + ObjectThemeManager::get()->formatObject(tokens); String newName = tokens.joinIntoString(" "); if (auto patch = ptr.get()) { diff --git a/Source/Pd/Patch.h b/Source/Pd/Patch.h index af2678f568..408022e893 100644 --- a/Source/Pd/Patch.h +++ b/Source/Pd/Patch.h @@ -122,8 +122,9 @@ class Patch : public ReferenceCountedObject { bool openInPluginMode = false; int splitViewIndex = 0; int windowIndex = 0; + Point lastViewportPosition = {1, 1}; + float lastViewportScale = 1.0f; - String lastUndoSequence; String lastRedoSequence; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 71473a607d..b5c2bf4638 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -39,8 +39,6 @@ using namespace juce::gl; #include -std::map PluginEditor::pluginModeScaleMap; - PluginEditor::PluginEditor(PluginProcessor& p) : AudioProcessorEditor(&p) , pd(&p) @@ -256,8 +254,7 @@ PluginEditor::PluginEditor(PluginProcessor& p) _this->grabKeyboardFocus(); }); - addChildComponent(&objectManager); - objectManager.lookAndFeelChanged(); + ObjectThemeManager::get()->updateTheme(); addChildComponent(nvgSurface); nvgSurface.toBehind(&tabComponent); @@ -733,10 +730,7 @@ void PluginEditor::handleAsyncUpdate() runButton.setEnabled(true); presentButton.setEnabled(true); - statusbar->centreButton.setEnabled(true); - statusbar->zoomComboButton.setEnabled(true); - reinterpret_cast(statusbar->zoomLabel.get())->setEnabled(true); - + statusbar->setHasActiveCanvas(true); addObjectMenuButton.setEnabled(true); } else { @@ -746,9 +740,7 @@ void PluginEditor::handleAsyncUpdate() runButton.setEnabled(false); presentButton.setEnabled(false); - statusbar->centreButton.setEnabled(false); - statusbar->zoomComboButton.setEnabled(false); - reinterpret_cast(statusbar->zoomLabel.get())->setEnabled(false); + statusbar->setHasActiveCanvas(false); undoButton.setEnabled(false); redoButton.setEnabled(false); @@ -1335,6 +1327,7 @@ bool PluginEditor::perform(InvocationInfo const& info) } case CommandIDs::ZoomIn: { auto* viewport = dynamic_cast(cnv->viewport.get()); + if(!viewport) return false; float newScale = getValue(getCurrentCanvas()->zoomScale) + 0.1f; newScale = static_cast(static_cast(round(std::clamp(newScale, 0.25f, 3.0f) * 10.))) / 10.; viewport->magnify(newScale); @@ -1342,6 +1335,7 @@ bool PluginEditor::perform(InvocationInfo const& info) } case CommandIDs::ZoomOut: { auto* viewport = dynamic_cast(cnv->viewport.get()); + if(!viewport) return false; float newScale = getValue(getCurrentCanvas()->zoomScale) - 0.1f; newScale = static_cast(static_cast(round(std::clamp(newScale, 0.25f, 3.0f) * 10.))) / 10.; viewport->magnify(newScale); @@ -1421,6 +1415,7 @@ bool PluginEditor::perform(InvocationInfo const& info) } default: { cnv = getCurrentCanvas(); + if(!cnv->viewport) return false; // This should close any opened editors before creating a new object cnv->grabKeyboardFocus(); @@ -1512,6 +1507,11 @@ void PluginEditor::broughtToFront() if(openedDialog) openedDialog->toFront(true); } +void PluginEditor::lookAndFeelChanged() +{ + ObjectThemeManager::get()->updateTheme(); +} + void PluginEditor::commandKeyChanged(bool isHeld) { if (isHeld) { @@ -1597,6 +1597,8 @@ bool PluginEditor::highlightSearchTarget(void* target, bool openNewTabIfNeeded) cnv->setSelected(found, true); auto* viewport = cnv->viewport.get(); + if(!viewport) return false; + auto scale = getValue(cnv->zoomScale); auto pos = found->getBounds().getCentre() * scale; @@ -1628,6 +1630,8 @@ bool PluginEditor::highlightSearchTarget(void* target, bool openNewTabIfNeeded) cnv->setSelected(found, true); auto* viewport = cnv->viewport.get(); + if(!viewport) return false; + auto scale = getValue(cnv->zoomScale); auto pos = found->getBounds().getCentre() * scale; diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index e964952135..940a6ec5b9 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -93,6 +93,8 @@ class PluginEditor : public AudioProcessorEditor void parentSizeChanged() override; void parentHierarchyChanged() override; void broughtToFront() override; + + void lookAndFeelChanged() override; // For dragging parent window void mouseDrag(MouseEvent const& e) override; @@ -166,16 +168,12 @@ class PluginEditor : public AudioProcessorEditor std::unique_ptr autosave; ApplicationCommandManager commandManager; - - inline static ObjectThemeManager objectManager; - static ObjectThemeManager* getObjectManager() { return &objectManager; }; - + std::unique_ptr calloutArea; std::unique_ptr welcomePanel; CheckedTooltip tooltipWindow; - static std::map pluginModeScaleMap; int editorIndex; private: diff --git a/Source/PluginMode.h b/Source/PluginMode.h index fe0ed7d3b5..d93d516e69 100644 --- a/Source/PluginMode.h +++ b/Source/PluginMode.h @@ -105,9 +105,9 @@ class PluginMode : public Component, public NVGComponent { // set scale to the last scale that was set for this patches plugin mode // if none was set, use 100% scale - if (PluginEditor::pluginModeScaleMap.contains(patchPtr->getPointer().get())) + if (pluginModeScaleMap.contains(patchPtr->getPointer().get())) { - int previousScale = PluginEditor::pluginModeScaleMap[patchPtr->getPointer().get()]; + int previousScale = pluginModeScaleMap[patchPtr->getPointer().get()]; scaleComboBox.setText(String(previousScale) + String("%"), dontSendNotification); setWidthAndHeight(previousScale * 0.01f); } else { @@ -172,7 +172,7 @@ class PluginMode : public Component, public NVGComponent { void closePluginMode() { // save the current scale in map for retrieval, so plugin mode remembers the last set scale - PluginEditor::pluginModeScaleMap[patchPtr->getPointer().get()] = pluginPreviousScale; + pluginModeScaleMap[patchPtr->getPointer().get()] = pluginPreviousScale; auto constrainedNewBounds = windowBounds.withWidth(std::max(windowBounds.getWidth(), 850)).withHeight(std::max(windowBounds.getHeight(), 650)); if (auto* mainWindow = dynamic_cast(editor->getTopLevelComponent())) { @@ -194,16 +194,10 @@ class PluginMode : public Component, public NVGComponent { } editor->nvgSurface.detachContext(); - cnv->patch.openInPluginMode = false; - editor->getTabComponent().triggerAsyncUpdate(); + editor->parentSizeChanged(); - editor->resized(); - - cnv->connectionLayer.setVisible(true); - - editor->nvgSurface.invalidateAll(); } bool isWindowFullscreen() const @@ -398,6 +392,11 @@ class PluginMode : public Component, public NVGComponent { return false; } } + + Canvas* getCanvas() + { + return cnv.get(); + } private: pd::Patch::Ptr patchPtr; @@ -425,6 +424,8 @@ class PluginMode : public Component, public NVGComponent { float const height = float(cnv->patchHeight.getValue()) + 1.0f; float pluginModeScale = 1.0f; int pluginPreviousScale = 100; + + static inline std::map pluginModeScaleMap; struct Scale { float floatScale; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 055c3433fa..a9768c59f0 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1471,7 +1471,7 @@ void PluginProcessor::receiveSysMessage(String const& selector, std::vectorstatusbar->powerButton.setToggleState(dsp, dontSendNotification); + editor->statusbar->showDSPState(dsp); } }); break; diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 2aa9835403..3a0a1f32be 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -683,7 +683,7 @@ class ZoomLabel : public Component if (auto* cnv = editor->getCurrentCanvas()) { cnv->zoomScale.setValue(1.0f); cnv->setTransform(AffineTransform().scaled(1.0f)); - cnv->viewport->resized(); + if(cnv->viewport) cnv->viewport->resized(); } } @@ -765,7 +765,7 @@ Statusbar::Statusbar(PluginProcessor* processor) if (auto* cnv = editor->getCurrentCanvas()) { cnv->zoomScale.setValue(scale); cnv->setTransform(AffineTransform().scaled(scale)); - cnv->viewport->resized(); + if(cnv->viewport) cnv->viewport->resized(); } }); } @@ -943,6 +943,18 @@ void Statusbar::setLatencyDisplay(int value) } } +void Statusbar::showDSPState(bool dspState) +{ + powerButton.setToggleState(dspState, dontSendNotification); +} + +void Statusbar::setHasActiveCanvas(bool hasActiveCanvas) +{ + centreButton.setEnabled(hasActiveCanvas); + zoomComboButton.setEnabled(hasActiveCanvas); + zoomLabel->setEnabled(hasActiveCanvas); +} + void Statusbar::audioProcessedChanged(bool audioProcessed) { auto colour = findColour(audioProcessed ? PlugDataColour::levelMeterActiveColourId : PlugDataColour::signalColourId); diff --git a/Source/Statusbar.h b/Source/Statusbar.h index 38977924cd..41334f65db 100644 --- a/Source/Statusbar.h +++ b/Source/Statusbar.h @@ -92,7 +92,13 @@ class Statusbar : public Component void setLatencyDisplay(int value); void updateZoomLevel(); + + void showDSPState(bool dspState); + void setHasActiveCanvas(bool hasActiveCanvas); + static constexpr int statusbarHeight = 30; + +private: bool wasLocked = false; // Make sure it doesn't re-lock after unlocking (because cmd is still down) std::unique_ptr levelMeter; @@ -116,13 +122,12 @@ class Statusbar : public Component Value showDirection; - static constexpr int statusbarHeight = 30; - std::unique_ptr enableAttachment; std::unique_ptr volumeAttachment; int firstSeparatorPosition; int secondSeparatorPosition; + friend class ZoomLabel; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Statusbar) }; diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 0c0f1e0564..982e14008e 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -86,7 +86,7 @@ Canvas* TabComponent::openPatch(pd::Patch::Ptr existingPatch) existingPatch->windowIndex = editor->editorIndex; showTab(cnv, activeSplitIndex); - cnv->jumpToOrigin(); + cnv->jumpToLastKnownPosition(); triggerAsyncUpdate(); pd->titleChanged(); @@ -108,10 +108,10 @@ void TabComponent::moveToLeftSplit(TabBarButtonComponent* tab) oldTabbar->tabbars[0].removeObject(tab); auto* cnv = canvases.add(new Canvas(editor, patch)); - cnv->jumpToOrigin(); tabbars[0].add(new TabBarButtonComponent(cnv, this)); showTab(cnv, 0); + cnv->jumpToLastKnownPosition(); triggerAsyncUpdate(); oldTabbar->triggerAsyncUpdate(); } @@ -125,10 +125,10 @@ void TabComponent::moveToLeftSplit(TabBarButtonComponent* tab) oldTabbar->tabbars[1].removeObject(tab); auto* cnv = canvases.add(new Canvas(editor, patch)); - cnv->jumpToOrigin(); tabbars[0].add(new TabBarButtonComponent(cnv, this)); showTab(cnv, 0); + cnv->jumpToLastKnownPosition(); triggerAsyncUpdate(); oldTabbar->triggerAsyncUpdate(); } @@ -154,7 +154,7 @@ void TabComponent::moveToLeftSplit(TabBarButtonComponent* tab) tabbars[1].insert(0, tabbars[0].removeAndReturn(i)); // Move to other split } } - + showTab(tabbars[1][0]->cnv, 1); // Show first tab of right split } } @@ -173,7 +173,7 @@ void TabComponent::moveToRightSplit(TabBarButtonComponent* tab) oldTabbar->tabbars[0].removeObject(tab); auto* cnv = canvases.add(new Canvas(editor, patch)); - cnv->jumpToOrigin(); + cnv->jumpToLastKnownPosition(); tabbars[1].add(new TabBarButtonComponent(cnv, this)); showTab(cnv, 1); @@ -190,7 +190,7 @@ void TabComponent::moveToRightSplit(TabBarButtonComponent* tab) oldTabbar->tabbars[1].removeObject(tab); auto* cnv = canvases.add(new Canvas(editor, patch)); - cnv->jumpToOrigin(); + cnv->jumpToLastKnownPosition(); tabbars[1].add(new TabBarButtonComponent(cnv, this)); showTab(cnv, 1); @@ -219,7 +219,7 @@ void TabComponent::openPatch() }); } }, - true, false, "*.pd", "Patch", this); + true, false, "*.pd", "Patch", this); } void TabComponent::nextTab() { @@ -263,9 +263,9 @@ void TabComponent::createNewWindow(Component* draggedTab) auto* newEditor = new PluginEditor(*pd); auto* newWindow = ProjectInfo::createNewWindow(newEditor); auto* window = dynamic_cast(getTopLevelComponent()); - + pd->openedEditors.add(newEditor); - + newWindow->addToDesktop(window->getDesktopWindowStyleFlags()); newWindow->setVisible(true); @@ -275,8 +275,8 @@ void TabComponent::createNewWindow(Component* draggedTab) patch->windowIndex = newEditor->editorIndex; auto* newCanvas = newEditor->getTabComponent().openPatch(patch); - newCanvas->jumpToOrigin(); - + newCanvas->jumpToLastKnownPosition(); + newWindow->setTopLeftPosition(Desktop::getInstance().getMousePosition() - Point(500, 60)); newWindow->toFront(true); newEditor->nvgSurface.detachContext(); @@ -321,7 +321,7 @@ void TabComponent::handleAsyncUpdate() { pluginMode.reset(nullptr); } - + // First, remove canvases that no longer exist for(int i = canvases.size() - 1; i >= 0; i--) { @@ -357,7 +357,7 @@ void TabComponent::handleAsyncUpdate() if(!cnv) { cnv = canvases.add(new Canvas(editor, patch)); - cnv->jumpToOrigin(); + cnv->jumpToLastKnownPosition(); } // Create tab buttons @@ -367,6 +367,21 @@ void TabComponent::handleAsyncUpdate() } pd->patches.getLock().exit(); + closeEmptySplits(); + + // Show welcome panel if there are no tabs + if(tabbars[0].size() == 0 && tabbars[1].size() == 0) + { + editor->welcomePanel->show(); + editor->resized(); + } + else { + editor->welcomePanel->hide(); + editor->resized(); + } + + resized(); // Update tab and canvas layout + // Show plugin mode tab after closing pluginmode if(lastPluginModePatchPtr != nullptr) { @@ -380,21 +395,11 @@ void TabComponent::handleAsyncUpdate() } lastPluginModePatchPtr = nullptr; } - - closeEmptySplits(); - // Show welcome panel if there are no tabs - if(tabbars[0].size() == 0 && tabbars[1].size() == 0) + for(auto* cnv : getVisibleCanvases()) { - editor->welcomePanel->show(); - editor->resized(); - } - else { - editor->welcomePanel->hide(); - editor->resized(); + cnv->jumpToLastKnownPosition(); } - - resized(); // Update tab and canvas layout } void TabComponent::closeEmptySplits() @@ -437,14 +442,16 @@ void TabComponent::closeEmptySplits() } } } - editor->resized(); } void TabComponent::showTab(Canvas* cnv, int splitIndex) { if(cnv == splits[splitIndex]) return; - if(splits[splitIndex]) removeChildComponent(splits[splitIndex]->viewport.get()); + if(splits[splitIndex]) { + splits[splitIndex]->saveViewportPosition(); + removeChildComponent(splits[splitIndex]->viewport.get()); + } splits[splitIndex] = cnv; @@ -473,6 +480,11 @@ void TabComponent::showTab(Canvas* cnv, int splitIndex) Canvas* TabComponent::getCurrentCanvas() { + if(pluginMode) + { + return pluginMode->getCanvas(); + } + return activeSplitIndex && splits[1] ? splits[1] : splits[0]; } @@ -571,7 +583,6 @@ void TabComponent::mouseMove(const MouseEvent& e) void TabComponent::parentSizeChanged() { - splitSize = getWidth() / splitProportion; } void TabComponent::resized() @@ -581,6 +592,8 @@ void TabComponent::resized() auto tabbarBounds = bounds.removeFromTop(30); auto& animator = Desktop::getInstance().getAnimator(); + splitSize = getWidth() / splitProportion; + for(int i = 0; i < tabbars.size(); i++) { auto& tabButtons = tabbars[i]; @@ -653,15 +666,15 @@ void TabComponent::askToCloseTab(Canvas* cnv) // Don't show save dialog, if patch is still open in another view if (_editor && _cnv && _cnv->patch.isDirty()) { Dialogs::showAskToSaveDialog( - &_editor->openedDialog, _editor, _cnv->patch.getTitle(), - [_cnv, _this](int result) mutable { - if (!_cnv || !_this) return; - if (result == 2) - _cnv->save([_cnv, _this]() mutable { _this->closeTab(_cnv); }); - else if (result == 1) - _this->closeTab(_cnv); - }, - 0, true); + &_editor->openedDialog, _editor, _cnv->patch.getTitle(), + [_cnv, _this](int result) mutable { + if (!_cnv || !_this) return; + if (result == 2) + _cnv->save([_cnv, _this]() mutable { _this->closeTab(_cnv); }); + else if (result == 1) + _this->closeTab(_cnv); + }, + 0, true); } else if(_this && _cnv) { _this->closeTab(_cnv); } @@ -671,10 +684,10 @@ void TabComponent::askToCloseTab(Canvas* cnv) void TabComponent::closeTab(Canvas* cnv) { auto patch = cnv->refCountedPatch; - + editor->sidebar->hideParameters(); editor->sidebar->clearSearchOutliner(); - + patch->setVisible(false); cnv->setCachedComponentImage(nullptr); // Clear nanovg invalidation listener, just to be sure @@ -699,10 +712,10 @@ void TabComponent::closeAllTabs(bool quitAfterComplete, Canvas* patchToExclude, afterComplete(); return; } - + auto canvas = SafePointer(canvases.getLast()); auto patch = canvas->refCountedPatch; - + auto deleteFunc = [this, canvas, quitAfterComplete, patchToExclude, afterComplete]() { if (canvas && !(patchToExclude && canvas == patchToExclude)) { closeTab(canvas); @@ -711,24 +724,24 @@ void TabComponent::closeAllTabs(bool quitAfterComplete, Canvas* patchToExclude, { canvases.move(canvases.indexOf(patchToExclude), 0); } - + closeAllTabs(quitAfterComplete, patchToExclude, afterComplete); }; - + if (canvas) { MessageManager::callAsync([this, canvas, patch, deleteFunc]() mutable { if (patch->isDirty()) { Dialogs::showAskToSaveDialog( - &editor->openedDialog, editor, patch->getTitle(), - [canvas, deleteFunc](int result) mutable { - if (!canvas) - return; - if (result == 2) - canvas->save([deleteFunc]() mutable { deleteFunc(); }); - else if (result == 1) - deleteFunc(); - }, - 0, true); + &editor->openedDialog, editor, patch->getTitle(), + [canvas, deleteFunc](int result) mutable { + if (!canvas) + return; + if (result == 2) + canvas->save([deleteFunc]() mutable { deleteFunc(); }); + else if (result == 1) + deleteFunc(); + }, + 0, true); } else { deleteFunc(); } @@ -773,15 +786,15 @@ void TabComponent::itemDropped(SourceDetails const& dragSourceDetails) auto* cnv = getCanvasAtScreenPosition(screenPosition); if (!cnv) return; - + auto mousePos = (cnv->getLocalPoint(this, dragSourceDetails.localPosition) - cnv->canvasOrigin); - + // Extract the array from the var auto patchWithSize = *dragSourceDetails.description.getArray(); auto patchSize = Point(patchWithSize[0], patchWithSize[1]); auto patchData = patchWithSize[2].toString(); auto patchName = patchWithSize[3].toString(); - + cnv->dragAndDropPaste(patchData, mousePos, patchSize.x, patchSize.y, patchName); setActiveSplit(cnv); return; @@ -803,7 +816,7 @@ void TabComponent::itemDropped(SourceDetails const& dragSourceDetails) draggingOverTabbar = false; splitDropBounds = Rectangle(); editor->nvgSurface.invalidateAll(); - + } void TabComponent::itemDragEnter(SourceDetails const& dragSourceDetails) @@ -838,7 +851,7 @@ void TabComponent::saveTabPositions() } } } - + std::sort(sortedPatches.begin(), sortedPatches.end(), [](auto const& a, auto const& b) { if (a.first->windowIndex != b.first->windowIndex) return a.first->windowIndex < b.first->windowIndex; @@ -848,14 +861,14 @@ void TabComponent::saveTabPositions() return a.second < b.second; }); - + pd->patches.getLock().enter(); int i = 0; for (auto& [patch, tabIdx] : sortedPatches) { - + if (i >= pd->patches.size()) break; - + pd->patches.set(i, patch); i++; } @@ -924,18 +937,18 @@ void TabComponent::itemDragMove(SourceDetails const& dragSourceDetails) } void TabComponent::showHiddenTabsMenu(int splitIndex) { - + class HiddenTabMenuItem : public PopupMenu::CustomComponent { String tabTitle; SafePointer cnv; - + public: TabComponent& tabbar; - + HiddenTabMenuItem(SafePointer canvas, String const& text, TabComponent& tabs) - : tabTitle(text) - , cnv(canvas) - , tabbar(tabs) + : tabTitle(text) + , cnv(canvas) + , tabbar(tabs) { closeTabButton.setButtonText(Icons::Clear); closeTabButton.addMouseListener(this, false); @@ -944,41 +957,41 @@ void TabComponent::showHiddenTabsMenu(int splitIndex) { }; addChildComponent(closeTabButton); } - + void resized() override { closeTabButton.setBounds(getWidth() - 26, -2, 26, 26); } - + void getIdealSize(int& idealWidth, int& idealHeight) override { idealWidth = 150; idealHeight = 24; } - + void mouseDown(MouseEvent const& e) override { if (e.originalComponent == &closeTabButton) return; - + tabbar.showTab(cnv, cnv->patch.splitViewIndex); triggerMenuItem(); } - + void mouseEnter(MouseEvent const& e) override { closeTabButton.setVisible(true); } - + void mouseExit(MouseEvent const& e) override { closeTabButton.setVisible(false); } - + void paint(Graphics& g) override { bool isActive = tabbar.getVisibleCanvases().contains(cnv); - + if (isActive) { g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId)); } else if (isItemHighlighted()) { @@ -986,21 +999,21 @@ void TabComponent::showHiddenTabsMenu(int splitIndex) { } else { g.setColour(findColour(PlugDataColour::popupMenuBackgroundColourId)); } - + PlugDataLook::fillSmoothedRectangle(g, getLocalBounds().reduced(1).toFloat(), Corners::defaultCornerRadius); - + auto area = getLocalBounds().reduced(4, 1).toFloat(); - + Font font = Font(14); - + g.setColour(findColour(TabbedButtonBar::tabTextColourId)); g.setFont(font); g.drawText(tabTitle.trim(), area.reduced(4, 0), Justification::centred, false); } - + SmallIconButton closeTabButton; }; - + PopupMenu m; auto& tabbar = tabbars[splitIndex]; for (int i = 0; i < tabbar.size(); ++i) { @@ -1010,8 +1023,8 @@ void TabComponent::showHiddenTabsMenu(int splitIndex) { m.addCustomItem(i + 1, std::make_unique(tab->cnv, title, *this), nullptr, title); } } - + m.showMenuAsync(PopupMenu::Options() - .withDeletionCheck(*this) - .withTargetComponent(&tabOverflowButtons[splitIndex])); + .withDeletionCheck(*this) + .withTargetComponent(&tabOverflowButtons[splitIndex])); } diff --git a/Source/TabComponent.h b/Source/TabComponent.h index 230e628806..af9aee2901 100644 --- a/Source/TabComponent.h +++ b/Source/TabComponent.h @@ -21,10 +21,10 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd void openInPluginMode(pd::Patch::Ptr patch); void renderArea(NVGcontext* nvg, Rectangle bounds); - + void nextTab(); void previousTab(); - + void askToCloseTab(Canvas* cnv); void closeTab(Canvas* cnv); void showTab(Canvas* cnv, int splitIndex = 0); @@ -32,7 +32,7 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd void closeAllTabs(bool quitAfterComplete = false, Canvas* patchToExclude = nullptr, std::function afterComplete = [](){}); void createNewWindow(Component* draggedTab); - + Canvas* getCurrentCanvas(); Canvas* getCanvasAtScreenPosition(Point screenPosition); @@ -53,7 +53,7 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd void saveTabPositions(); void closeEmptySplits(); - + bool isInterestedInDragSource(SourceDetails const& dragSourceDetails) override; void itemDropped(SourceDetails const& dragSourceDetails) override; void itemDragEnter(SourceDetails const& dragSourceDetails) override; @@ -83,16 +83,16 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd TabComponent* parent; }; - + class CloseTabButton : public SmallIconButton { - + using SmallIconButton::SmallIconButton; - + void paint(Graphics& g) override { auto font = Fonts::getIconFont().withHeight(12); g.setFont(font); - + if (!isEnabled()) { g.setColour(Colours::grey); } else if (getToggleState()) { @@ -102,15 +102,15 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd } else { g.setColour(findColour(PlugDataColour::toolbarTextColourId)); } - + int const yIndent = jmin(4, proportionOfHeight(0.3f)); int const cornerSize = jmin(getHeight(), getWidth()) / 2; - + int const fontHeight = roundToInt(font.getHeight() * 0.6f); int const leftIndent = jmin(fontHeight, 2 + cornerSize / (isConnectedOnLeft() ? 4 : 2)); int const rightIndent = jmin(fontHeight, 2 + cornerSize / (isConnectedOnRight() ? 4 : 2)); int const textWidth = getWidth() - leftIndent - rightIndent; - + if (textWidth > 0) g.drawFittedText(getButtonText(), leftIndent, yIndent, textWidth, getHeight() - yIndent * 2, Justification::centred, 2); } @@ -139,11 +139,11 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd } else { g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); } - + PlugDataLook::fillSmoothedRectangle(g, getLocalBounds().toFloat().reduced(4.5f), Corners::defaultCornerRadius); - + auto area = getLocalBounds().reduced(4, 1).toFloat(); - + // Use a gradient to make it fade out when it gets near to the close button auto fadeX = (mouseOver || active) ? area.getRight() - 25 : area.getRight() - 8; auto textColour = findColour(PlugDataColour::toolbarTextColourId); @@ -172,7 +172,7 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd Font font(Fonts::getCurrentFont()); auto length = font.getStringWidth(text) + 32; auto const boundsOffset = 10; - + // we need to expand the bounds, but reset the position to top left // then we offset the mouse drag by the same amount // this is to allow area for the shadow to render correctly @@ -188,12 +188,12 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd g.setColour(findColour(PlugDataColour::activeTabBackgroundColourId)); PlugDataLook::fillSmoothedRectangle(g, textBounds.withPosition(10, 10).reduced(2).toFloat(), Corners::defaultCornerRadius); - + g.setColour(findColour(PlugDataColour::toolbarTextColourId)); - + g.setFont(font); g.drawText(text, textBounds.withPosition(10, 10), Justification::centred, false); - + return ScaledImage(image, scale); } @@ -201,21 +201,21 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd { if (e.mods.isPopupMenu() && cnv) { PopupMenu tabMenu; - - #if JUCE_MAC + +#if JUCE_MAC String revealTip = "Reveal in Finder"; - #elif JUCE_WINDOWS +#elif JUCE_WINDOWS String revealTip = "Reveal in Explorer"; - #else +#else String revealTip = "Reveal in file browser"; - #endif +#endif bool canReveal = cnv->patch.getCurrentFile().existsAsFile(); - + tabMenu.addItem(revealTip, canReveal, false, [this]() { cnv->patch.getCurrentFile().revealToUser(); }); - + tabMenu.addSeparator(); PopupMenu parentPatchMenu; @@ -231,7 +231,7 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd }); } } - + tabMenu.addSubMenu("Parent patches", parentPatchMenu, parentPatchMenu.getNumItems()); tabMenu.addSeparator(); @@ -248,22 +248,22 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd parent->closeEmptySplits(); parent->saveTabPositions(); }); - + tabMenu.addSeparator(); - + tabMenu.addItem("Close patch", true, false, [this]() { parent->closeTab(cnv); }); - + tabMenu.addItem("Close all other patches", true, false, [this]() { parent->closeAllTabs(false, cnv); }); - + tabMenu.addItem("Close all patches", true, false, [this]() { parent->closeAllTabs(false); }); - + // Show the popup menu at the mouse position tabMenu.showMenuAsync(PopupMenu::Options().withMinimumWidth(150).withMaximumNumColumns(1)); } @@ -330,7 +330,7 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd std::unique_ptr pluginMode; t_glist* lastPluginModePatchPtr = nullptr; - + PluginEditor* editor; PluginProcessor* pd; }; From 76bdbea85595a70e2e7dedf5088c6fac547b7597 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 8 Jun 2024 15:20:28 +0200 Subject: [PATCH 0923/1030] Cleaned up --- Libraries/nanovg | 2 +- Source/Dialogs/Dialogs.cpp | 6 +++++- Source/Objects/ImplementationBase.cpp | 2 +- Source/Objects/ImplementationBase.h | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Libraries/nanovg b/Libraries/nanovg index 54fa8e55fb..c8f5d4845f 160000 --- a/Libraries/nanovg +++ b/Libraries/nanovg @@ -1 +1 @@ -Subproject commit 54fa8e55fbd730c7e5a33014bbec14c88e8e0dc9 +Subproject commit c8f5d4845f1a7c03aa2215c2d6a919aa3af4657f diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index 248152f870..a46fff3a29 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -532,7 +532,11 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent auto selectedBoxes = cnv->getSelectionOfType(); // If we directly right-clicked on an object, make sure it has been added to selection - if (auto* obj = dynamic_cast(originalComponent)) { + if(!originalComponent) + { + return; + } + else if (auto* obj = dynamic_cast(originalComponent)) { selectedBoxes.addIfNotAlreadyThere(obj); } else if (auto* parentOfTypeObject = originalComponent->findParentComponentOfClass()) { selectedBoxes.addIfNotAlreadyThere(parentOfTypeObject); diff --git a/Source/Objects/ImplementationBase.cpp b/Source/Objects/ImplementationBase.cpp index ce9e466627..4b60712ae1 100644 --- a/Source/Objects/ImplementationBase.cpp +++ b/Source/Objects/ImplementationBase.cpp @@ -122,7 +122,7 @@ ImplementationBase* ImplementationBase::createImplementation(String const& type, return nullptr; } -void ImplementationBase::openSubpatch(pd::Patch* subpatch) +void ImplementationBase::openSubpatch(pd::Patch::Ptr subpatch) { if (auto glist = ptr.get()) { if (!subpatch) { diff --git a/Source/Objects/ImplementationBase.h b/Source/Objects/ImplementationBase.h index a198ebe26f..968b123c5c 100644 --- a/Source/Objects/ImplementationBase.h +++ b/Source/Objects/ImplementationBase.h @@ -25,7 +25,7 @@ class ImplementationBase { virtual void update() { } - void openSubpatch(pd::Patch* subpatch); + void openSubpatch(pd::Patch::Ptr subpatch); void closeOpenedSubpatchers(); Canvas* getMainCanvas(t_canvas* patchPtr, bool alsoSearchRoot = false) const; From 410b8e1c670f83676a04439edf7ea3bd30403f45 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 8 Jun 2024 15:53:01 +0200 Subject: [PATCH 0924/1030] Colour ID cleanup --- Source/Components/Buttons.h | 1 + Source/LookAndFeel.cpp | 14 +------------- Source/TabComponent.cpp | 2 +- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/Source/Components/Buttons.h b/Source/Components/Buttons.h index afe9f9927e..a53eea2689 100644 --- a/Source/Components/Buttons.h +++ b/Source/Components/Buttons.h @@ -23,6 +23,7 @@ class MainToolbarButton : public TextButton { g.setColour(backgroundColour); PlugDataLook::fillSmoothedRectangle(g, bounds, cornerSize); + auto textColour = findColour(PlugDataColour::toolbarTextColourId).withMultipliedAlpha(isEnabled() ? 1.0f : 0.5f); AttributedString attributedIcon; diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index 1d6c4a2626..f62570d049 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -707,7 +707,7 @@ void PlugDataLook::drawComboBox(Graphics& g, int width, int height, bool, int, i path.startNewSubPath((float)arrowZone.getX() + 3.0f, (float)arrowZone.getCentreY() - 2.0f); path.lineTo((float)arrowZone.getCentreX(), (float)arrowZone.getCentreY() + 2.0f); path.lineTo((float)arrowZone.getRight() - 3.0f, (float)arrowZone.getCentreY() - 2.0f); - g.setColour(object.findColour(ComboBox::arrowColourId).withAlpha((object.isEnabled() ? 0.9f : 0.2f))); + g.setColour(object.findColour(PlugDataColour::panelTextColourId).withAlpha((object.isEnabled() ? 0.9f : 0.2f))); g.strokePath(path, PathStrokeType(2.0f)); } @@ -1068,18 +1068,6 @@ void PlugDataLook::setColours(std::map colours) colours.at(PlugDataColour::panelTextColourId)); setColour(KeyMappingEditorComponent::textColourId, colours.at(PlugDataColour::panelTextColourId)); - setColour(TabbedButtonBar::frontTextColourId, - colours.at(PlugDataColour::toolbarTextColourId)); - setColour(TabbedButtonBar::tabTextColourId, - colours.at(PlugDataColour::toolbarTextColourId)); - setColour(ToggleButton::textColourId, - colours.at(PlugDataColour::panelTextColourId)); - setColour(ToggleButton::tickColourId, - colours.at(PlugDataColour::panelTextColourId)); - setColour(ToggleButton::tickDisabledColourId, - colours.at(PlugDataColour::panelTextColourId)); - setColour(ComboBox::arrowColourId, - colours.at(PlugDataColour::panelTextColourId)); setColour(DirectoryContentsDisplayComponent::textColourId, colours.at(PlugDataColour::panelTextColourId)); setColour(Slider::textBoxTextColourId, diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index 982e14008e..c07310a6fc 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -1006,7 +1006,7 @@ void TabComponent::showHiddenTabsMenu(int splitIndex) { Font font = Font(14); - g.setColour(findColour(TabbedButtonBar::tabTextColourId)); + g.setColour(findColour(PlugDataColour::toolbarTextColourId)); g.setFont(font); g.drawText(tabTitle.trim(), area.reduced(4, 0), Justification::centred, false); } From eb4f6e078ed38b75d4f63370a043a69a9f87404b Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 8 Jun 2024 17:50:59 +0200 Subject: [PATCH 0925/1030] Fixed viewport bug --- Source/Canvas.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index b4e00de574..9970e1f93b 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -708,7 +708,7 @@ void Canvas::updateOverlays() void Canvas::jumpToOrigin() { - viewport->setViewPosition(canvasOrigin.transformedBy(getTransform()) + Point(1, 1)); + viewport->setViewPosition((canvasOrigin + Point(1, 1)).transformedBy(getTransform())); } void Canvas::jumpToLastKnownPosition() @@ -720,7 +720,7 @@ void Canvas::saveViewportPosition() { if(viewport) { - patch.lastViewportPosition = viewport->getViewPosition() - canvasOrigin; + patch.lastViewportPosition = viewport->getViewPosition().transformedBy(getTransform().inverted()) - canvasOrigin; } } From 21c13e00a0ac3bde932440d674c3b3dfaba0d9ec Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 8 Jun 2024 18:21:22 +0200 Subject: [PATCH 0926/1030] Documentation browser optimisations --- Source/Sidebar/DocumentationBrowser.h | 417 ++++++++++++++------------ Source/Utility/ValueTreeViewer.h | 6 +- 2 files changed, 232 insertions(+), 191 deletions(-) diff --git a/Source/Sidebar/DocumentationBrowser.h b/Source/Sidebar/DocumentationBrowser.h index 8c35d8529d..a4bc939342 100644 --- a/Source/Sidebar/DocumentationBrowser.h +++ b/Source/Sidebar/DocumentationBrowser.h @@ -20,219 +20,98 @@ class DocumentBrowserSettings : public Component { struct DocumentBrowserSettingsButton : public TextButton { String const icon; String const description; - + DocumentBrowserSettingsButton(String iconString, String descriptionString) - : icon(std::move(iconString)) - , description(std::move(descriptionString)) + : icon(std::move(iconString)) + , description(std::move(descriptionString)) { } - + void paint(Graphics& g) override { auto colour = findColour(PlugDataColour::toolbarTextColourId); if (isMouseOver()) { colour = colour.contrasting(0.3f); } - + Fonts::drawText(g, description, getLocalBounds().withTrimmedLeft(28), colour, 14); - + if (getToggleState()) { colour = findColour(PlugDataColour::toolbarActiveColourId); } - + Fonts::drawIcon(g, icon, getLocalBounds().withTrimmedLeft(8), colour, 14, false); } }; - + DocumentBrowserSettings(std::function const& chooseCustomLocation, std::function const& resetDefaultLocation) { addAndMakeVisible(customLocationButton); addAndMakeVisible(restoreLocationButton); - + customLocationButton.onClick = [chooseCustomLocation]() { chooseCustomLocation(); }; - + restoreLocationButton.onClick = [resetDefaultLocation]() { resetDefaultLocation(); }; - + setSize(180, 54); } - + void resized() override { auto buttonBounds = getLocalBounds(); - + int buttonHeight = buttonBounds.getHeight() / 2; - + customLocationButton.setBounds(buttonBounds.removeFromTop(buttonHeight)); restoreLocationButton.setBounds(buttonBounds.removeFromTop(buttonHeight)); } - + private: DocumentBrowserSettingsButton customLocationButton = DocumentBrowserSettingsButton(Icons::Open, "Show custom folder..."); DocumentBrowserSettingsButton restoreLocationButton = DocumentBrowserSettingsButton(Icons::Restore, "Show default folder"); - + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(DocumentBrowserSettings) }; -class DocumentationBrowser : public Component, public FileDragAndDropTarget, private FileSystemWatcher::Listener, private Thread, public KeyListener { - +class DocumentationBrowserUpdateThread : public Thread, public ChangeBroadcaster, private FileSystemWatcher::Listener, private SettingsFileListener, public DeletedAtShutdown +{ public: - explicit DocumentationBrowser(PluginProcessor* processor) - : Thread("Documentation Browser Thread"), pd(processor) + DocumentationBrowserUpdateThread() : Thread("Documentation Browser Thread") { - searchInput.setBackgroundColour(PlugDataColour::sidebarActiveBackgroundColourId); - searchInput.addKeyListener(this); - searchInput.onTextChange = [this]() { - fileList.setFilterString(searchInput.getText()); - }; - + fsWatcher.removeAllFolders(); + fsWatcher.addFolder(File(SettingsFile::getInstance()->getProperty("browser_path"))); fsWatcher.addListener(this); - - searchInput.setJustification(Justification::centredLeft); - searchInput.setBorder({ 1, 23, 5, 1 }); - searchInput.setTextToShowWhenEmpty("Type to search documentation", findColour(PlugDataColour::sidebarTextColourId).withAlpha(0.5f)); - searchInput.setInterceptsMouseClicks(true, true); - addAndMakeVisible(searchInput); - fileList.onClick = [this](ValueTree& tree){ - auto file = File(tree.getProperty("Path").toString()); - if (file.existsAsFile() && file.hasFileExtension("pd")) { - auto* editor = findParentComponentOfClass(); - editor->getTabComponent().openPatch(URL(file)); - SettingsFile::getInstance()->addToRecentlyOpened(file); - } - else if(file.isDirectory()) - { - file.revealToUser(); - } - }; - - fileList.onDragStart = [this](ValueTree& tree){ - DragAndDropContainer::performExternalDragDropOfFiles({ tree.getProperty("Path") }, false, this, nullptr); - }; - - updateContent(); - addAndMakeVisible(fileList); + update(); } - ~DocumentationBrowser() override + ~DocumentationBrowserUpdateThread() { stopThread(-1); } - bool isInterestedInFileDrag(StringArray const& files) override - { - if (!isVisible()) - return false; - - for (auto& path : files) { - auto file = File(path); - if (file.exists() && (file.isDirectory() || file.hasFileExtension("pd"))) { - return true; - } - } - - return false; - } - - void updateContent() - { - fsWatcher.removeAllFolders(); - fsWatcher.addFolder(File(pd->settingsFile->getProperty("browser_path"))); - startThread(Thread::Priority::background); - } - - bool keyPressed(KeyPress const& key, Component* originatingComponent) override - { - if (key.isKeyCode(KeyPress::upKey) || key.isKeyCode(KeyPress::downKey)) { - fileList.keyPressed(key, originatingComponent); - return true; - } - - return false; - } - - - void filesystemChanged() override + void update() { - if(isVisible()) - { - startThread(Thread::Priority::background); - } - else { - needsUpdate = true; - } + //startThread(fileTree.isValid() ? Thread::Priority::low : Thread::Priority::background); + startThread(Thread::Priority::low); } - void visibilityChanged() override + ValueTree getCurrentTree() { - if(needsUpdate && isVisible()) - { - startThread(Thread::Priority::low); - needsUpdate = false; - } + ScopedLock treeLock(fileTreeLock); + return fileTree; } - - void filesDropped(StringArray const& files, int x, int y) override - { - auto parentDirectory = File(pd->settingsFile->getProperty("browser_path")); - - for (auto& path : files) { - auto file = File(path); - if (file.exists() && (file.isDirectory() || file.hasFileExtension("pd"))) { - auto alias = parentDirectory.getChildFile(file.getFileName()); - - if (alias.exists()) continue; - -#if JUCE_WINDOWS - // Symlinks on Windows are weird! - if (file.isDirectory()) { - // Create NTFS directory junction - OSUtils::createJunction(alias.getFullPathName().replaceCharacters("/", "\\").toStdString(), file.getFullPathName().toStdString()); - } else { - // Create hard link - OSUtils::createHardLink(alias.getFullPathName().replaceCharacters("/", "\\").toStdString(), file.getFullPathName().toStdString()); - } - -#else - file.createSymbolicLink(alias, true); -#endif - } - } - - updateContent(); - - isDraggingFile = false; - repaint(); - } - - void fileDragEnter(StringArray const&, int, int) override - { - isDraggingFile = true; - repaint(); - } - - void fileDragExit(StringArray const&) override - { - isDraggingFile = false; - repaint(); - } - void run() override - { - auto tree = generateDirectoryValueTree(File(pd->settingsFile->getProperty("browser_path"))); - if(tree.isValid()) { - MessageManager::callAsync([_this = SafePointer(this), tree](){ - if(_this) { - _this->fileTree = tree; - _this->fileList.setValueTree(tree); - } - }); - } - } +private: + + static inline const Identifier fileIdentifier = Identifier("File"); + static inline const Identifier nameIdentifier = Identifier("Name"); + static inline const Identifier pathIdentifier = Identifier("Path"); + static inline const Identifier iconIdentifier = Identifier("Icon"); ValueTree generateDirectoryValueTree(const File& directory) { @@ -245,13 +124,13 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri } ValueTree rootNode("Folder"); - rootNode.setProperty("Name", directory.getFileName(), nullptr); - rootNode.setProperty("Path", directory.getFullPathName(), nullptr); - rootNode.setProperty("Icon", Icons::Folder, nullptr); + rootNode.setProperty(nameIdentifier, directory.getFileName(), nullptr); + rootNode.setProperty(pathIdentifier, directory.getFullPathName(), nullptr); + rootNode.setProperty(iconIdentifier, Icons::Folder, nullptr); // visitedDirectories keeps track of dirs we've already processed to prevent infinite loops static Array visitedDirectories = {}; - + // Protect against symlink loops! if (!visitedDirectories.contains(directory)) { for (const auto& subDirectory : OSUtils::iterateDirectory(directory, false, false)) { @@ -269,27 +148,29 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri for (const auto& file : OSUtils::iterateDirectory(directory, false, true)) { if(file.getFileName().startsWith(".") || file.isDirectory()) continue; - ValueTree childNode("File"); - childNode.setProperty("Name", file.getFileName(), nullptr); - childNode.setProperty("Path", file.getFullPathName(), nullptr); - childNode.setProperty("Icon", Icons::File, nullptr); - + ValueTree childNode(fileIdentifier); + childNode.setProperty(nameIdentifier, file.getFileName(), nullptr); + childNode.setProperty(pathIdentifier, file.getFullPathName(), nullptr); + childNode.setProperty(iconIdentifier, Icons::File, nullptr); + rootNode.appendChild(childNode, nullptr); } + if (threadShouldExit()) return {}; + struct { static int compareElements (const ValueTree& first, const ValueTree& second) { - if(first.getProperty("Icon") == Icons::File && second.getProperty("Icon") == Icons::Folder) + if(first.getProperty(iconIdentifier) == Icons::File && second.getProperty(iconIdentifier) == Icons::Folder) { return 1; } - if(first.getProperty("Icon") == Icons::Folder && second.getProperty("Icon") == Icons::File) + if(first.getProperty(iconIdentifier) == Icons::Folder && second.getProperty(iconIdentifier) == Icons::File) { return -1; } - return first.getProperty("Name").toString().compareNatural(second.getProperty("Name").toString()); + return first.getProperty(nameIdentifier).toString().compareNatural(second.getProperty(nameIdentifier).toString()); } } valueTreeSorter; @@ -297,11 +178,177 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri return rootNode; } + + void propertyChanged(String const& name, var const& value) override { + if(name == "browser_path") + { + fsWatcher.removeAllFolders(); + fsWatcher.addFolder(File(SettingsFile::getInstance()->getProperty("browser_path"))); + update(); + } + } + + void run() override + { + fileTreeLock.enter(); + fileTree = generateDirectoryValueTree(File(SettingsFile::getInstance()->getProperty("browser_path"))); + fileTreeLock.exit(); + sendChangeMessage(); + } + + void filesystemChanged() override + { + update(); + } + + + CriticalSection fileTreeLock; + ValueTree fileTree; + FileSystemWatcher fsWatcher; +}; + +class DocumentationBrowser : public Component, public FileDragAndDropTarget, public ChangeListener, public KeyListener { + +public: + explicit DocumentationBrowser(PluginProcessor* processor) + : pd(processor) + { + if(!updater) updater = new DocumentationBrowserUpdateThread(); + updater->addChangeListener(this); + + searchInput.setBackgroundColour(PlugDataColour::sidebarActiveBackgroundColourId); + searchInput.addKeyListener(this); + searchInput.onTextChange = [this]() { + fileList.setFilterString(searchInput.getText()); + }; + + searchInput.setJustification(Justification::centredLeft); + searchInput.setBorder({ 1, 23, 5, 1 }); + searchInput.setTextToShowWhenEmpty("Type to search documentation", findColour(PlugDataColour::sidebarTextColourId).withAlpha(0.5f)); + searchInput.setInterceptsMouseClicks(true, true); + addAndMakeVisible(searchInput); + + fileList.onClick = [this](ValueTree& tree){ + auto file = File(tree.getProperty("Path").toString()); + if (file.existsAsFile() && file.hasFileExtension("pd")) { + auto* editor = findParentComponentOfClass(); + editor->getTabComponent().openPatch(URL(file)); + SettingsFile::getInstance()->addToRecentlyOpened(file); + } + else if(file.isDirectory()) + { + file.revealToUser(); + } + }; + + fileList.onDragStart = [this](ValueTree& tree){ + DragAndDropContainer::performExternalDragDropOfFiles({ tree.getProperty("Path") }, false, this, nullptr); + }; + + fileTree = updater->getCurrentTree(); + if(fileTree.isValid()) + { + fileList.setValueTree(fileTree); + } + addAndMakeVisible(fileList); + } + + ~DocumentationBrowser() override + { + updater->removeChangeListener(this); + } + + void changeListenerCallback (ChangeBroadcaster *source) override + { + fileTree = updater->getCurrentTree(); + + if(isVisible()) { + fileList.setValueTree(fileTree); + } + } + + bool isInterestedInFileDrag(StringArray const& files) override + { + if (!isVisible()) + return false; + + for (auto& path : files) { + auto file = File(path); + if (file.exists() && (file.isDirectory() || file.hasFileExtension("pd"))) { + return true; + } + } + + return false; + } + + bool keyPressed(KeyPress const& key, Component* originatingComponent) override + { + if (key.isKeyCode(KeyPress::upKey) || key.isKeyCode(KeyPress::downKey)) { + fileList.keyPressed(key, originatingComponent); + return true; + } + + return false; + } + + void visibilityChanged() override + { + if(fileList.getValueTree() != fileTree) { + fileList.setValueTree(fileTree); + } + } + + void filesDropped(StringArray const& files, int x, int y) override + { + auto parentDirectory = File(pd->settingsFile->getProperty("browser_path")); + + for (auto& path : files) { + auto file = File(path); + if (file.exists() && (file.isDirectory() || file.hasFileExtension("pd"))) { + auto alias = parentDirectory.getChildFile(file.getFileName()); + + if (alias.exists()) continue; + +#if JUCE_WINDOWS + // Symlinks on Windows are weird! + if (file.isDirectory()) { + // Create NTFS directory junction + OSUtils::createJunction(alias.getFullPathName().replaceCharacters("/", "\\").toStdString(), file.getFullPathName().toStdString()); + } else { + // Create hard link + OSUtils::createHardLink(alias.getFullPathName().replaceCharacters("/", "\\").toStdString(), file.getFullPathName().toStdString()); + } + +#else + file.createSymbolicLink(alias, true); +#endif + } + } + + updater->update(); + + isDraggingFile = false; + repaint(); + } + + void fileDragEnter(StringArray const&, int, int) override + { + isDraggingFile = true; + repaint(); + } + + void fileDragExit(StringArray const&) override + { + isDraggingFile = false; + repaint(); + } + bool hitTest(int x, int y) override { if (x < 5) return false; - + return true; } @@ -310,20 +357,20 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri g.setColour(findColour(PlugDataColour::sidebarActiveBackgroundColourId)); g.fillRoundedRectangle(searchInput.getBounds().reduced(6, 4).toFloat(), Corners::defaultCornerRadius); } - + void lookAndFeelChanged() override { searchInput.setColour(TextEditor::backgroundColourId, Colours::transparentBlack); searchInput.setColour(TextEditor::textColourId, findColour(PlugDataColour::sidebarTextColourId)); searchInput.setColour(TextEditor::outlineColourId, Colours::transparentBlack); } - + void resized() override { searchInput.setBounds(getLocalBounds().removeFromTop(34).reduced(5, 4)); fileList.setBounds(getLocalBounds().withHeight(getHeight() - 32).withY(32).reduced(2, 0)); } - + void paintOverChildren(Graphics& g) override { g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); @@ -331,12 +378,12 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri auto backgroundColour = findColour(PlugDataColour::sidebarBackgroundColourId); auto transparentColour = backgroundColour.withAlpha(0.0f); - + // Draw a gradient to fade the content out underneath the search input auto scrollOffset = fileList.getViewport().canScrollVertically(); g.setGradientFill(ColourGradient(backgroundColour, 0.0f, 26.0f, transparentColour, 0.0f, 42.0f, false)); g.fillRect(Rectangle(0, searchInput.getBottom(), getWidth() - scrollOffset, 12)); - + Fonts::drawIcon(g, Icons::Search, 2, 1, 32, findColour(PlugDataColour::sidebarTextColourId), 12); if (isDraggingFile) { @@ -344,7 +391,7 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri g.drawRect(getLocalBounds().reduced(1), 2.0f); } } - + std::unique_ptr getExtraSettingsComponent() { auto* settingsCalloutButton = new SmallIconButton(Icons::More); @@ -358,32 +405,26 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri Dialogs::showOpenDialog([this](URL result) { if (result.getLocalFile().isDirectory()) { pd->settingsFile->setProperty("browser_path", result.toString(false)); - updateContent(); } }, - false, true, "", "DocumentationFileChooser", editor); + false, true, "", "DocumentationFileChooser", editor); }; - + auto resetFolderCallback = [this]() { auto location = ProjectInfo::appDataDir; pd->settingsFile->setProperty("browser_path", location.getFullPathName()); - updateContent(); }; - + auto docsSettings = std::make_unique(openFolderCallback, resetFolderCallback); CallOutBox::launchAsynchronously(std::move(docsSettings), bounds, editor); }; - + return std::unique_ptr(settingsCalloutButton); } - + private: PluginProcessor* pd; - - bool needsUpdate = false; - - FileSystemWatcher fsWatcher; - + TextButton revealButton = TextButton(Icons::OpenedFolder); TextButton loadFolderButton = TextButton(Icons::Folder); TextButton resetFolderButton = TextButton(Icons::Restore); @@ -391,6 +432,8 @@ class DocumentationBrowser : public Component, public FileDragAndDropTarget, pri ValueTree fileTree; ValueTreeViewerComponent fileList = ValueTreeViewerComponent("(Folder)"); + + static inline DocumentationBrowserUpdateThread* updater = nullptr; SearchEditor searchInput; bool isDraggingFile = false; diff --git a/Source/Utility/ValueTreeViewer.h b/Source/Utility/ValueTreeViewer.h index 81a0f08a1e..c571b25b8d 100644 --- a/Source/Utility/ValueTreeViewer.h +++ b/Source/Utility/ValueTreeViewer.h @@ -122,10 +122,8 @@ class ValueTreeNodeComponent : public Component void update() { // Compare existing child nodes with current children - for (int i = 0; i < valueTreeNode.getNumChildren(); ++i) + for (const auto& childNode : valueTreeNode) { - ValueTree childNode = valueTreeNode.getChild(i); - // Check if an existing node exists for this child ValueTreeNodeComponent* existingNode = nullptr; for (auto* node : nodes) @@ -389,7 +387,7 @@ class ValueTreeNodeComponent : public Component return totalHeight; } - static bool compareProperties(ValueTree oldTree, ValueTree newTree) + static bool compareProperties(const ValueTree& oldTree, const ValueTree& newTree) { for(int i = 0; i < oldTree.getNumProperties(); i++) { From 2dacf7c302c7cc025b7ec96817f95a17b7be8433 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 8 Jun 2024 18:21:31 +0200 Subject: [PATCH 0927/1030] More cleaning up --- Source/Components/Buttons.h | 16 ++-- Source/Components/ColourPicker.h | 4 +- Source/Components/DraggableNumber.h | 4 +- Source/Components/SuggestionComponent.h | 8 +- Source/Dialogs/AddObjectMenu.h | 4 +- Source/Dialogs/Dialogs.cpp | 5 +- Source/Dialogs/MainMenu.h | 2 +- Source/Dialogs/SaveDialog.h | 4 +- Source/Dialogs/SnapSettings.h | 2 +- Source/Heavy/HeavyExportDialog.cpp | 2 +- Source/Heavy/Toolchain.h | 6 +- Source/LookAndFeel.cpp | 112 +----------------------- Source/LookAndFeel.h | 12 +-- Source/Objects/ArrayObject.h | 2 +- Source/Sidebar/AutomationPanel.h | 4 +- Source/Sidebar/Console.h | 2 +- Source/Sidebar/Sidebar.h | 2 +- Source/Statusbar.cpp | 2 +- Source/TabComponent.cpp | 2 +- Source/TabComponent.h | 4 +- Source/Utility/OSUtils.cpp | 20 +++-- Source/Utility/OSUtils.h | 2 +- 22 files changed, 58 insertions(+), 163 deletions(-) diff --git a/Source/Components/Buttons.h b/Source/Components/Buttons.h index a53eea2689..3794ac7676 100644 --- a/Source/Components/Buttons.h +++ b/Source/Components/Buttons.h @@ -21,7 +21,7 @@ class MainToolbarButton : public TextButton { auto bounds = getLocalBounds().reduced(3, 4).toFloat(); g.setColour(backgroundColour); - PlugDataLook::fillSmoothedRectangle(g, bounds, cornerSize); + g.fillRoundedRectangle(bounds, cornerSize); auto textColour = findColour(PlugDataColour::toolbarTextColourId).withMultipliedAlpha(isEnabled() ? 1.0f : 0.5f); @@ -58,11 +58,13 @@ class ToolbarRadioButton : public TextButton { bounds = bounds.reduced(0.0f, bounds.proportionOfHeight(0.17f)); g.setColour(backgroundColour); - PlugDataLook::fillSmoothedRectangle(g, bounds, Corners::defaultCornerRadius, - !(flatOnLeft || flatOnTop), - !(flatOnRight || flatOnTop), - !(flatOnLeft || flatOnBottom), - !(flatOnRight || flatOnBottom)); + Path p; + p.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), Corners::defaultCornerRadius, Corners::defaultCornerRadius, + !(flatOnLeft || flatOnTop), + !(flatOnRight || flatOnTop), + !(flatOnLeft || flatOnBottom), + !(flatOnRight || flatOnBottom)); + g.fillPath(p); auto textColour = findColour(PlugDataColour::toolbarTextColourId).withMultipliedAlpha(isEnabled() ? 1.0f : 0.5f); @@ -173,7 +175,7 @@ class SettingsToolbarButton : public TextButton { background = background.darker(0.025f); g.setColour(background); - PlugDataLook::fillSmoothedRectangle(g, b.toFloat(), Corners::defaultCornerRadius); + g.fillRoundedRectangle(b.toFloat(), Corners::defaultCornerRadius); } auto textColour = findColour(PlugDataColour::toolbarTextColourId); diff --git a/Source/Components/ColourPicker.h b/Source/Components/ColourPicker.h index 593cff6f59..dab5866dee 100644 --- a/Source/Components/ColourPicker.h +++ b/Source/Components/ColourPicker.h @@ -651,10 +651,10 @@ class ColourPicker : public Component { auto radius = jmin(Corners::defaultCornerRadius, bounds.getWidth() / 2.0f); g.setGradientFill(ColourGradient(colour, 0.0f, 0.0f, Colours::black, bounds.getHeight() / 2, bounds.getHeight() / 2, false)); - PlugDataLook::fillSmoothedRectangle(g, bounds, radius); + g.fillRoundedRectangle(bounds, radius); g.setColour(findColour(PlugDataColour::outlineColourId)); - PlugDataLook::drawSmoothedRectangle(g, PathStrokeType(1.0f), bounds, radius); + g.drawRoundedRectangle(bounds, radius, 1.0f); } void resized() override diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index 3466141223..8d9fab15bd 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -301,7 +301,7 @@ class DraggableNumber : public Label if (hoveredDecimal >= 0) { // TODO: make this colour Id configurable? g.setColour(findColour(ComboBox::outlineColourId).withAlpha(isMouseButtonDown() ? 0.5f : 0.3f)); - PlugDataLook::fillSmoothedRectangle(g, hoveredDecimalPosition, 2.5f); + g.fillRoundedRectangle(hoveredDecimalPosition, 2.5f); } auto font = getFont(); @@ -551,7 +551,7 @@ struct DraggableListNumber : public DraggableNumber { if (hoveredDecimal >= 0) { // TODO: make this colour Id configurable? g.setColour(findColour(ComboBox::outlineColourId).withAlpha(isMouseButtonDown() ? 0.5f : 0.3f)); - PlugDataLook::fillSmoothedRectangle(g, hoveredDecimalPosition, 2.5f); + g.fillRoundedRectangle(hoveredDecimalPosition, 2.5f); } if (!isBeingEdited()) { diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index 93ebcfba4c..8f89cb2d8f 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -214,7 +214,7 @@ class SuggestionComponent : public Component auto buttonArea = getLocalBounds().withTrimmedRight((parent->canBeTransparent() ? 42 : 2) + scrollbarIndent).toFloat().reduced(4, 1); g.setColour(backgroundColour); - PlugDataLook::fillSmoothedRectangle(g, buttonArea, Corners::defaultCornerRadius); + g.fillRoundedRectangle(buttonArea, Corners::defaultCornerRadius); auto colour = findColour(PlugDataColour::popupMenuTextColourId); auto yIndent = jmin(4, proportionOfHeight(0.3f)); @@ -255,7 +255,7 @@ class SuggestionComponent : public Component auto iconbound = getLocalBounds().reduced(4); iconbound.setWidth(getHeight() - 8); iconbound.translate(4, 0); - PlugDataLook::fillSmoothedRectangle(g, iconbound.toFloat(), Corners::defaultCornerRadius); + g.fillRoundedRectangle(iconbound.toFloat(), Corners::defaultCornerRadius); Fonts::drawFittedText(g, iconText, iconbound.reduced(1), Colours::white, 1, 1.0f, type ? 12 : 10, Justification::centred); @@ -498,13 +498,13 @@ class SuggestionComponent : public Component } g.setColour(findColour(PlugDataColour::popupMenuBackgroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, port->getBounds().reduced(1).toFloat(), Corners::defaultCornerRadius); + g.fillRoundedRectangle(port->getBounds().reduced(1).toFloat(), Corners::defaultCornerRadius); } void paintOverChildren(Graphics& g) override { g.setColour(findColour(PlugDataColour::outlineColourId).darker(0.1f)); - PlugDataLook::drawSmoothedRectangle(g, PathStrokeType(1.0f), port->getBounds().reduced(1).toFloat(), Corners::defaultCornerRadius); + g.drawRoundedRectangle(port->getBounds().reduced(1).toFloat(), Corners::defaultCornerRadius, 1.0f); } bool keyPressed(KeyPress const& key, Component* originatingComponent) override diff --git a/Source/Dialogs/AddObjectMenu.h b/Source/Dialogs/AddObjectMenu.h index cc5ce0e574..9b73c260c5 100644 --- a/Source/Dialogs/AddObjectMenu.h +++ b/Source/Dialogs/AddObjectMenu.h @@ -48,7 +48,7 @@ class ObjectItem : public ObjectDragAndDrop if (isHovering) { g.setColour(highlight); - PlugDataLook::fillSmoothedRectangle(g, iconBounds.toFloat(), Corners::defaultCornerRadius); + g.fillRoundedRectangle(iconBounds.toFloat(), Corners::defaultCornerRadius); } Fonts::drawText(g, titleText, textBounds, findColour(PlugDataColour::popupMenuTextColourId), 13.0f, Justification::centred); @@ -573,7 +573,7 @@ class AddObjectMenuButton : public Component { if (isMouseOver()) { g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, b.toFloat(), Corners::defaultCornerRadius); + g.fillRoundedRectangle(b.toFloat(), Corners::defaultCornerRadius); } if (toggleState) { diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index a46fff3a29..b4278fe0d5 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -443,7 +443,7 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent bounds = bounds.withSizeKeepingCentre(bounds.getHeight(), bounds.getHeight()); g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, bounds, Corners::defaultCornerRadius); + g.fillRoundedRectangle(bounds, Corners::defaultCornerRadius); textColour = findColour(PlugDataColour::sidebarTextColourId); } @@ -650,8 +650,7 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent auto colour = findColour(PopupMenu::textColourId).withMultipliedAlpha(isActive ? 1.0f : 0.5f); if (isItemHighlighted() && isActive) { g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId)); - - PlugDataLook::fillSmoothedRectangle(g, r.toFloat().reduced(0, 1), Corners::defaultCornerRadius); + g.fillRoundedRectangle(r.toFloat().reduced(0, 1), Corners::defaultCornerRadius); } g.setColour(colour); diff --git a/Source/Dialogs/MainMenu.h b/Source/Dialogs/MainMenu.h index 39fe1beaec..22c7ed256b 100644 --- a/Source/Dialogs/MainMenu.h +++ b/Source/Dialogs/MainMenu.h @@ -138,7 +138,7 @@ class MainMenu : public PopupMenu { if (isItemHighlighted() && isActive) { g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, r.toFloat().reduced(0, 1), Corners::defaultCornerRadius); + g.fillRoundedRectangle(r.toFloat().reduced(0, 1), Corners::defaultCornerRadius); } g.setColour(colour); diff --git a/Source/Dialogs/SaveDialog.h b/Source/Dialogs/SaveDialog.h index 849a889a17..a3b3649a8e 100644 --- a/Source/Dialogs/SaveDialog.h +++ b/Source/Dialogs/SaveDialog.h @@ -26,7 +26,7 @@ class SaveDialogButton : public TextButton { } g.setColour(backgroundColour); - PlugDataLook::fillSmoothedRectangle(g, bounds, Corners::defaultCornerRadius); + g.fillRoundedRectangle(bounds, Corners::defaultCornerRadius); g.setFont(Fonts::getDefaultFont().withHeight(15)); g.setColour(findColour(PlugDataColour::panelTextColourId)); @@ -35,7 +35,7 @@ class SaveDialogButton : public TextButton { if (hasKeyboardFocus(false)) { g.setColour(activeColour); - PlugDataLook::drawSmoothedRectangle(g, PathStrokeType(1.0f), bounds, Corners::defaultCornerRadius); + g.drawRoundedRectangle(bounds, Corners::defaultCornerRadius, 1.0f); } } }; diff --git a/Source/Dialogs/SnapSettings.h b/Source/Dialogs/SnapSettings.h index b28828e3d8..a17e4165ba 100644 --- a/Source/Dialogs/SnapSettings.h +++ b/Source/Dialogs/SnapSettings.h @@ -106,7 +106,7 @@ class SnapSettings : public Component { { if (dragToggledInteraction) { g.setColour(findColour(PlugDataColour::toolbarHoverColourId)); - PlugDataLook::fillSmoothedRectangle(g, getLocalBounds().toFloat().reduced(1.0f), Corners::defaultCornerRadius); + g.fillRoundedRectangle(getLocalBounds().toFloat().reduced(1.0f), Corners::defaultCornerRadius); } auto iconColour = getToggleState() ? findColour(PlugDataColour::toolbarActiveColourId) : findColour(PlugDataColour::toolbarTextColourId); diff --git a/Source/Heavy/HeavyExportDialog.cpp b/Source/Heavy/HeavyExportDialog.cpp index 7ad0b43bbc..5b8660bf3a 100644 --- a/Source/Heavy/HeavyExportDialog.cpp +++ b/Source/Heavy/HeavyExportDialog.cpp @@ -171,7 +171,7 @@ class ExporterSettingsPanel : public Component if (isPositiveAndBelow(row, items.size())) { if (rowIsSelected) { g.setColour(findColour(PlugDataColour::sidebarActiveBackgroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, Rectangle(3, 3, width - 6, height - 6), Corners::defaultCornerRadius); + g.fillRoundedRectangle(Rectangle(3, 3, width - 6, height - 6), Corners::defaultCornerRadius); } auto const textColour = findColour(PlugDataColour::sidebarTextColourId); diff --git a/Source/Heavy/Toolchain.h b/Source/Heavy/Toolchain.h index d0dacf6b31..26a381376c 100644 --- a/Source/Heavy/Toolchain.h +++ b/Source/Heavy/Toolchain.h @@ -182,10 +182,10 @@ class ToolchainInstaller : public Component auto downloadBar = Rectangle(91.5f, 250.0f - (downloadBarHeight * 0.5), progress, downloadBarHeight); g.setColour(findColour(PlugDataColour::panelTextColourId)); - PlugDataLook::fillSmoothedRectangle(g, downloadBarBg, Corners::defaultCornerRadius); + g.fillRoundedRectangle(downloadBarBg, Corners::defaultCornerRadius); g.setColour(findColour(PlugDataColour::panelActiveBackgroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, downloadBar, Corners::defaultCornerRadius); + g.fillRoundedRectangle(downloadBar, Corners::defaultCornerRadius); } if (errorMessage.isNotEmpty()) { @@ -363,7 +363,7 @@ class ToolchainInstaller : public Component auto colour = findColour(PlugDataColour::panelTextColourId); if (isMouseOver()) { g.setColour(findColour(PlugDataColour::panelActiveBackgroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, Rectangle(1, 1, getWidth() - 2, getHeight() - 2), Corners::largeCornerRadius); + g.fillRoundedRectangle(Rectangle(1, 1, getWidth() - 2, getHeight() - 2), Corners::largeCornerRadius); } Fonts::drawIcon(g, iconText, 20, 5, 40, colour, 24, false); diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index f62570d049..7a7ec541e5 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -269,50 +269,6 @@ void PlugDataLook::fillResizableWindowBackground(Graphics& g, int w, int h, Bord } } -void PlugDataLook::drawTextButtonBackground(Graphics& g, Button& button, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) -{ - auto backgroundColour = findColour(shouldDrawButtonAsDown || button.getToggleState() ? PlugDataColour::dataColourId : PlugDataColour::canvasTextColourId); - if (shouldDrawButtonAsHighlighted) - backgroundColour = backgroundColour.brighter(0.5f); - auto cornerSize = Corners::defaultCornerRadius; - g.setColour(backgroundColour); - fillSmoothedRectangle(g, button.getLocalBounds().toFloat(), cornerSize); - jassertfalse; // I think we don't use this anymore? -} - -void PlugDataLook::drawToolbarButtonBackground(Graphics& g, Button& button, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) -{ - bool active = shouldDrawButtonAsHighlighted || shouldDrawButtonAsDown || button.getToggleState(); - - auto cornerSize = Corners::defaultCornerRadius; - auto flatOnLeft = button.isConnectedOnLeft(); - auto flatOnRight = button.isConnectedOnRight(); - auto flatOnTop = button.isConnectedOnTop(); - auto flatOnBottom = button.isConnectedOnBottom(); - - if (flatOnLeft || flatOnRight || flatOnTop || flatOnBottom) { - - auto backgroundColour = findColour(active ? PlugDataColour::toolbarHoverColourId : PlugDataColour::toolbarBackgroundColourId).contrasting((shouldDrawButtonAsHighlighted && !button.getToggleState()) ? 0.0f : 0.05f); - - auto bounds = button.getLocalBounds().toFloat(); - bounds = bounds.reduced(0.0f, bounds.proportionOfHeight(0.17f)); - - g.setColour(backgroundColour); - fillSmoothedRectangle(g, bounds, Corners::defaultCornerRadius, - !(flatOnLeft || flatOnTop), - !(flatOnRight || flatOnTop), - !(flatOnLeft || flatOnBottom), - !(flatOnRight || flatOnBottom)); - } else { - auto backgroundColour = active ? findColour(PlugDataColour::toolbarHoverColourId) : Colours::transparentBlack; - auto bounds = button.getLocalBounds().toFloat().reduced(2.0f, 4.0f); - - g.setColour(backgroundColour); - fillSmoothedRectangle(g, bounds, cornerSize); - // g.fillRoundedRectangle(bounds, cornerSize); - } -} - void PlugDataLook::drawCallOutBoxBackground(CallOutBox& box, Graphics& g, Path const& path, Image& cachedImage) { @@ -528,10 +484,10 @@ void PlugDataLook::drawPopupMenuBackgroundWithOptions(Graphics& g, int width, in g.setColour(background); auto bounds = Rectangle(5, 6, width - 10, height - 12); - fillSmoothedRectangle(g, bounds, Corners::largeCornerRadius); + g.fillRoundedRectangle(bounds, Corners::largeCornerRadius); g.setColour(findColour(PlugDataColour::outlineColourId)); - drawSmoothedRectangle(g, PathStrokeType(1.0f), bounds, Corners::largeCornerRadius); + g.drawRoundedRectangle(bounds, Corners::largeCornerRadius, 1.0f); } else { auto bounds = Rectangle(0, 0, width, height); @@ -577,7 +533,7 @@ void PlugDataLook::drawPopupMenuItem(Graphics& g, Rectangle const& area, auto colour = findColour(PopupMenu::textColourId).withMultipliedAlpha(isActive ? 1.0f : 0.5f); if (isHighlighted && isActive) { g.setColour(findColour(PlugDataColour::popupMenuActiveBackgroundColourId)); - fillSmoothedRectangle(g, r.toFloat().reduced(4, 0), Corners::defaultCornerRadius); + g.fillRoundedRectangle(r.toFloat().reduced(4, 0), Corners::defaultCornerRadius); } g.setColour(colour); @@ -863,68 +819,6 @@ void PlugDataLook::drawLabel(Graphics& g, Label& label) g.drawRect(label.getLocalBounds()); } -Path PlugDataLook::getSquircle(Rectangle const& bounds, float cornerRadius, bool const curveTopLeft, bool const curveTopRight, bool const curveBottomLeft, bool const curveBottomRight) -{ - Path path; - - float x = bounds.getX(); - float y = bounds.getY(); - float width = bounds.getWidth(); - float height = bounds.getHeight(); - - float radius = cornerRadius; - if (radius > width * 0.5f) - radius = width * 0.5f; - if (radius > height * 0.5f) - radius = height * 0.5f; - - float controlOffset = radius * 0.45f; - - path.startNewSubPath(x + radius, y); - - if (curveTopRight) { - path.lineTo(x + width - radius, y); - path.cubicTo(x + width - radius + controlOffset, y, x + width, y + radius - controlOffset, x + width, y + radius); - } else { - path.lineTo(x + width, y); - } - - if (curveBottomRight) { - path.lineTo(x + width, y + height - radius); - path.cubicTo(x + width, y + height - radius + controlOffset, x + width - radius + controlOffset, y + height, x + width - radius, y + height); - } else { - path.lineTo(x + width, y + height); - } - - if (curveBottomLeft) { - path.lineTo(x + radius, y + height); - path.cubicTo(x + radius - controlOffset, y + height, x, y + height - radius + controlOffset, x, y + height - radius); - } else { - path.lineTo(x, y + height); - } - - if (curveTopLeft) { - path.lineTo(x, y + radius); - path.cubicTo(x, y + radius - controlOffset, x + radius - controlOffset, y, x + radius, y); - } else { - path.lineTo(x, y); - } - - path.closeSubPath(); - - return path; -} - -void PlugDataLook::fillSmoothedRectangle(Graphics& g, Rectangle const& bounds, float cornerRadius, bool const curveTopLeft, bool const curveTopRight, bool const curveBottomLeft, bool const curveBottomRight) -{ - g.fillPath(getSquircle(bounds, cornerRadius, curveTopLeft, curveTopRight, curveBottomLeft, curveBottomRight)); -} - -void PlugDataLook::drawSmoothedRectangle(Graphics& g, PathStrokeType strokeType, Rectangle const& bounds, float cornerRadius, bool const curveTopLeft, bool const curveTopRight, bool const curveBottomLeft, bool const curveBottomRight) -{ - g.strokePath(getSquircle(bounds, cornerRadius, curveTopLeft, curveTopRight, curveBottomLeft, curveBottomRight), strokeType); -} - void PlugDataLook::drawPropertyComponentLabel(Graphics& g, int width, int height, PropertyComponent& component) { auto indent = jmin(10, component.getWidth() / 10); diff --git a/Source/LookAndFeel.h b/Source/LookAndFeel.h index cfb83511d5..79adc94834 100644 --- a/Source/LookAndFeel.h +++ b/Source/LookAndFeel.h @@ -71,11 +71,7 @@ struct PlugDataLook : public LookAndFeel_V4 { void fillResizableWindowBackground(Graphics& g, int w, int h, BorderSize const& border, ResizableWindow& window) override; - void drawResizableWindowBorder(Graphics&, int w, int h, BorderSize const& border, ResizableWindow&) override { } - - void drawTextButtonBackground(Graphics& g, Button& button, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown); - - void drawToolbarButtonBackground(Graphics& g, Button& button, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown); + void drawResizableWindowBorder(Graphics&, int w, int h, BorderSize const& border, ResizableWindow&) override {} void drawCallOutBoxBackground(CallOutBox& box, Graphics& g, Path const& path, Image& cachedImage) override; @@ -145,12 +141,6 @@ struct PlugDataLook : public LookAndFeel_V4 { void drawLabel(Graphics& g, Label& label) override; - static Path getSquircle(Rectangle const& bounds, float cornerRadius, bool const curveTopLeft = true, bool const curveTopRight = true, bool const curveBottomLeft = true, bool const curveBottomRight = true); - - static void fillSmoothedRectangle(Graphics& g, Rectangle const& bounds, float cornerRadius, bool const curveTopLeft = true, bool const curveTopRight = true, bool const curveBottomLeft = true, bool const curveBottomRight = true); - - static void drawSmoothedRectangle(Graphics& g, PathStrokeType strokeType, Rectangle const& bounds, float cornerRadius, bool const curveTopLeft = true, bool const curveTopRight = true, bool const curveBottomLeft = true, bool const curveBottomRight = true); - void drawPropertyComponentLabel(Graphics& g, int width, int height, PropertyComponent& component) override; void drawPropertyPanelSectionHeader(Graphics& g, String const& name, bool isOpen, int width, int height) override; diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index 94d7ca431b..fba4ea0d8b 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -770,7 +770,7 @@ struct ArrayPropertiesPanel : public PropertiesPanelProperty, public Value::List auto colour = findColour(PlugDataColour::sidebarTextColourId); if (mouseIsOver) { g.setColour(findColour(PlugDataColour::sidebarActiveBackgroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, bounds.toFloat(), Corners::defaultCornerRadius); + g.fillRoundedRectangle(bounds.toFloat(), Corners::defaultCornerRadius); } Fonts::drawIcon(g, Icons::Add, iconBounds, colour, 12); diff --git a/Source/Sidebar/AutomationPanel.h b/Source/Sidebar/AutomationPanel.h index 5ef28e652a..9591af0f1a 100644 --- a/Source/Sidebar/AutomationPanel.h +++ b/Source/Sidebar/AutomationPanel.h @@ -378,7 +378,7 @@ class AutomationItem : public ObjectDragAndDrop valueLabel.setColour(Label::textColourId, findColour(PlugDataColour::sidebarTextColourId)); g.setColour(findColour(PlugDataColour::sidebarActiveBackgroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, getLocalBounds().toFloat().reduced(6.0f, 3.0f), Corners::defaultCornerRadius); + g.fillRoundedRectangle(getLocalBounds().toFloat().reduced(6.0f, 3.0f), Corners::defaultCornerRadius); } std::function onDelete = [](AutomationItem*) {}; @@ -527,7 +527,7 @@ class AutomationComponent : public Component { auto colour = findColour(PlugDataColour::sidebarTextColourId); if (mouseIsOver) { g.setColour(findColour(PlugDataColour::sidebarActiveBackgroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, bounds.toFloat(), Corners::defaultCornerRadius); + g.fillRoundedRectangle(bounds.toFloat(), Corners::defaultCornerRadius); } Fonts::drawIcon(g, Icons::Add, iconBounds, colour, 12); diff --git a/Source/Sidebar/Console.h b/Source/Sidebar/Console.h index e7cd71ae9b..d40f3bb304 100644 --- a/Source/Sidebar/Console.h +++ b/Source/Sidebar/Console.h @@ -200,7 +200,7 @@ class Console : public Component if (isSelected) { // Draw selected background g.setColour(findColour(PlugDataColour::sidebarActiveBackgroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, getLocalBounds().reduced(0, 1).toFloat().withTrimmedTop(0.5f), Corners::defaultCornerRadius); + g.fillRoundedRectangle(getLocalBounds().reduced(0, 1).toFloat().withTrimmedTop(0.5f), Corners::defaultCornerRadius); for (auto& item : console.selectedItems) { diff --git a/Source/Sidebar/Sidebar.h b/Source/Sidebar/Sidebar.h index 8a173a150f..4adb3baa45 100644 --- a/Source/Sidebar/Sidebar.h +++ b/Source/Sidebar/Sidebar.h @@ -46,7 +46,7 @@ class SidebarSelectorButton : public TextButton { auto bounds = getLocalBounds().toFloat().reduced(3.0f, 4.0f); g.setColour(backgroundColour); - PlugDataLook::fillSmoothedRectangle(g, bounds, cornerSize); + g.fillRoundedRectangle(bounds, cornerSize); auto font = Fonts::getIconFont().withHeight(13); g.setFont(font); diff --git a/Source/Statusbar.cpp b/Source/Statusbar.cpp index 3a0a1f32be..1fb0e3963a 100644 --- a/Source/Statusbar.cpp +++ b/Source/Statusbar.cpp @@ -208,7 +208,7 @@ class VolumeSlider : public Slider { auto thumb = Rectangle(thumbSize, thumbSize).withCentre(position); thumb = thumb.withSizeKeepingCentre(thumb.getWidth() - 12, thumb.getHeight()); g.setColour(backgroundColour.darker(thumb.contains(getMouseXYRelative().toFloat()) ? 0.3f : 0.0f).withAlpha(0.8f)); - PlugDataLook::fillSmoothedRectangle(g, thumb, Corners::defaultCornerRadius * 0.5f); + g.fillRoundedRectangle(thumb, Corners::defaultCornerRadius * 0.5f); } private: diff --git a/Source/TabComponent.cpp b/Source/TabComponent.cpp index c07310a6fc..41ce5f866f 100644 --- a/Source/TabComponent.cpp +++ b/Source/TabComponent.cpp @@ -1000,7 +1000,7 @@ void TabComponent::showHiddenTabsMenu(int splitIndex) { g.setColour(findColour(PlugDataColour::popupMenuBackgroundColourId)); } - PlugDataLook::fillSmoothedRectangle(g, getLocalBounds().reduced(1).toFloat(), Corners::defaultCornerRadius); + g.fillRoundedRectangle(getLocalBounds().reduced(1).toFloat(), Corners::defaultCornerRadius); auto area = getLocalBounds().reduced(4, 1).toFloat(); diff --git a/Source/TabComponent.h b/Source/TabComponent.h index af9aee2901..37e5e4501b 100644 --- a/Source/TabComponent.h +++ b/Source/TabComponent.h @@ -140,7 +140,7 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); } - PlugDataLook::fillSmoothedRectangle(g, getLocalBounds().toFloat().reduced(4.5f), Corners::defaultCornerRadius); + g.fillRoundedRectangle(getLocalBounds().toFloat().reduced(4.5f), Corners::defaultCornerRadius); auto area = getLocalBounds().reduced(4, 1).toFloat(); @@ -187,7 +187,7 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd g.setOpacity(1.0f); g.setColour(findColour(PlugDataColour::activeTabBackgroundColourId)); - PlugDataLook::fillSmoothedRectangle(g, textBounds.withPosition(10, 10).reduced(2).toFloat(), Corners::defaultCornerRadius); + g.fillRoundedRectangle(textBounds.withPosition(10, 10).reduced(2).toFloat(), Corners::defaultCornerRadius); g.setColour(findColour(PlugDataColour::toolbarTextColourId)); diff --git a/Source/Utility/OSUtils.cpp b/Source/Utility/OSUtils.cpp index ca73e0b7b2..398503e43d 100644 --- a/Source/Utility/OSUtils.cpp +++ b/Source/Utility/OSUtils.cpp @@ -318,17 +318,16 @@ OSUtils::KeyboardLayout OSUtils::getKeyboardLayout() } #endif // Linux/BSD - -juce::Array OSUtils::iterateDirectory(juce::File const& directory, bool recursive, bool onlyFiles, int maximum) +juce::Array iterateDirectoryPaths(juce::File const& directory, bool recursive, bool onlyFiles, int maximum) { - juce::Array result; + juce::Array result; if (recursive) { try { for (auto const& dirEntry : fs::recursive_directory_iterator(directory.getFullPathName().toStdString())) { auto isDir = dirEntry.is_directory(); if ((isDir && !onlyFiles) || !isDir) { - result.add(juce::File(dirEntry.path().string())); + result.add(dirEntry.path().string()); } if (maximum > 0 && result.size() >= maximum) @@ -342,7 +341,7 @@ juce::Array OSUtils::iterateDirectory(juce::File const& directory, b for (auto const& dirEntry : fs::directory_iterator(directory.getFullPathName().toStdString())) { auto isDir = dirEntry.is_directory(); if ((isDir && !onlyFiles) || !isDir) { - result.add(juce::File(dirEntry.path().string())); + result.add(dirEntry.path()); } if (maximum > 0 && result.size() >= maximum) @@ -356,6 +355,17 @@ juce::Array OSUtils::iterateDirectory(juce::File const& directory, b return result; } +juce::Array OSUtils::iterateDirectory(juce::File const& directory, bool recursive, bool onlyFiles, int maximum) +{ + auto paths = iterateDirectoryPaths(directory, recursive, onlyFiles, maximum); + auto files = juce::Array(); + for(auto& path : paths) { + files.add(juce::File(path.string())); + } + + return files; +} + // needs to be in OSutils because it needs unsigned int OSUtils::keycodeToHID(unsigned int scancode) { diff --git a/Source/Utility/OSUtils.h b/Source/Utility/OSUtils.h index 3bfc2353b5..c3e82f8bce 100644 --- a/Source/Utility/OSUtils.h +++ b/Source/Utility/OSUtils.h @@ -32,7 +32,7 @@ struct OSUtils { #endif static juce::Array iterateDirectory(juce::File const& directory, bool recursive, bool onlyFiles, int maximum = -1); - + static KeyboardLayout getKeyboardLayout(); #if JUCE_MAC || JUCE_IOS From d121822867a0096760e0e34269f7c6edd01f15a7 Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 8 Jun 2024 18:31:47 +0200 Subject: [PATCH 0928/1030] Small code style fixes --- Source/Dialogs/AddObjectMenu.h | 9 ++++----- Source/Dialogs/AlignmentTools.h | 5 +++-- Source/Dialogs/ConnectionMessageDisplay.h | 2 +- Source/Objects/ArrayObject.h | 8 ++++---- Source/PluginEditor.cpp | 4 ++-- Source/PluginEditor.h | 4 ++-- Source/TabComponent.h | 6 +++--- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Source/Dialogs/AddObjectMenu.h b/Source/Dialogs/AddObjectMenu.h index 9b73c260c5..3265240f0f 100644 --- a/Source/Dialogs/AddObjectMenu.h +++ b/Source/Dialogs/AddObjectMenu.h @@ -133,8 +133,7 @@ class ObjectList : public Component { int maxColumns = width / itemSize; int offset = 0; - for (int i = 0; i < objectButtons.size(); i++) { - auto* button = objectButtons[i]; + for (auto button : objectButtons) { button->setBounds(column * itemSize, offset, itemSize, itemSize); column++; if (column >= maxColumns) { @@ -170,7 +169,7 @@ class ObjectList : public Component { resized(); } - void printAllObjects() + static void printAllObjects() { static bool hasRun = false; if (hasRun) @@ -557,7 +556,7 @@ class AddObjectMenuButton : public Component { bool clickingTogglesState = false; std::function onClick = []() {}; - AddObjectMenuButton(String iconStr, String textStr = String()) + explicit AddObjectMenuButton(const String& iconStr, const String& textStr = String()) : icon(iconStr) , text(textStr) { @@ -617,7 +616,7 @@ class AddObjectMenuButton : public Component { class AddObjectMenu : public Component { public: - AddObjectMenu(PluginEditor* e) + explicit AddObjectMenu(PluginEditor* e) : objectBrowserButton(Icons::Object, "Show Object Browser") , pinButton(Icons::Pin) , editor(e) diff --git a/Source/Dialogs/AlignmentTools.h b/Source/Dialogs/AlignmentTools.h index f322715856..442009cbcf 100644 --- a/Source/Dialogs/AlignmentTools.h +++ b/Source/Dialogs/AlignmentTools.h @@ -175,8 +175,9 @@ class AlignmentTools : public Component { isShowing = true; auto alignmentTools = std::make_unique(); - alignmentTools->pluginEditor = dynamic_cast(editor); - alignmentTools->pluginEditor->showCalloutBox(std::move(alignmentTools), bounds); + auto* editor = dynamic_cast(editor); + alignmentTools->pluginEditor = editor; + editor->showCalloutBox(std::move(alignmentTools), bounds); } ~AlignmentTools() override diff --git a/Source/Dialogs/ConnectionMessageDisplay.h b/Source/Dialogs/ConnectionMessageDisplay.h index d6f66c7134..1b13b85efb 100644 --- a/Source/Dialogs/ConnectionMessageDisplay.h +++ b/Source/Dialogs/ConnectionMessageDisplay.h @@ -45,7 +45,7 @@ class ConnectionMessageDisplay auto clearSignalDisplayBuffer = [this]() { SignalBlock sample; - while (sampleQueue.try_dequeue(sample)) { }; + while (sampleQueue.try_dequeue(sample)) {} for (int ch = 0; ch < 8; ch++) { std::fill(lastSamples[ch], lastSamples[ch] + signalBlockSize, 0.0f); cycleLength[ch] = 0.0f; diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index fba4ea0d8b..405cb2af4f 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -949,7 +949,7 @@ class ArrayListView : public PropertiesPanel, public Value::Listener label->dragEnd = [this](){ if (auto ptr = array.get()) { plugdata_forward_message(ptr->x_glist, gensym("redraw"), 0, NULL); - }; + } }; properties.set(i, property); } @@ -996,7 +996,7 @@ class ArrayEditorDialog : public Component { OwnedArray lists; PluginProcessor* pd; - ArrayEditorDialog(PluginProcessor* instance, std::vector arrays, Object* parent) + ArrayEditorDialog(PluginProcessor* instance, const std::vector& arrays, Object* parent) : resizer(this, &constrainer) , pd(instance) { @@ -1490,7 +1490,7 @@ class ArrayDefineObject final : public TextBase { std::vector arrays; t_glist* x = c.get(); - t_gobj* gl = (x->gl_list ? pd_checkglist(&x->gl_list->g_pd)->gl_list : 0); + t_gobj* gl = (x->gl_list ? pd_checkglist(&x->gl_list->g_pd)->gl_list : nullptr); if (gl) { arrays.push_back(gl); @@ -1499,7 +1499,7 @@ class ArrayDefineObject final : public TextBase { } } - if (arrays.size() && arrays[0] != nullptr) { + if (!arrays.empty() && arrays[0] != nullptr) { editor = std::make_unique(cnv->pd, arrays, object); editor->onClose = [this]() { editor.reset(nullptr); diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index b5c2bf4638..889742856b 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -385,7 +385,7 @@ CallOutBox& PluginEditor::showCalloutBox(std::unique_ptr content, Rec { PluginEditor* editor; public: - CalloutDeletionListener(PluginEditor* e) : editor(e) {} + explicit CalloutDeletionListener(PluginEditor* e) : editor(e) {} void componentBeingDeleted(Component& c) override { @@ -466,7 +466,7 @@ void PluginEditor::resized() redoButton.setBounds((2 * buttonDistance) + offset, 0, buttonSize, buttonSize); addObjectMenuButton.setBounds((3 * buttonDistance) + offset, 0, buttonSize, buttonSize); - auto startX = (getWidth() / 2) - (toolbarHeight * 1.5); + auto startX = (getWidth() / 2.0f) - (toolbarHeight * 1.5); editButton.setBounds(startX, 1, buttonSize, buttonSize - 2); runButton.setBounds(startX + buttonSize - 1, 1, buttonSize, buttonSize - 2); diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 940a6ec5b9..b18517d9f1 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -28,7 +28,7 @@ class CalloutArea : public Component, public Timer { public: - CalloutArea(Component* parent) : target(parent), tooltipWindow(this) + explicit CalloutArea(Component* parent) : target(parent), tooltipWindow(this) { setVisible(true); setAlwaysOnTop(true); @@ -36,7 +36,7 @@ class CalloutArea : public Component, public Timer startTimerHz(3); } - ~CalloutArea(){} + ~CalloutArea()= default; void timerCallback() override { diff --git a/Source/TabComponent.h b/Source/TabComponent.h index 37e5e4501b..bdbffa1cd5 100644 --- a/Source/TabComponent.h +++ b/Source/TabComponent.h @@ -9,7 +9,7 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd class TabBarButtonComponent; public: - TabComponent(PluginEditor* editor); + explicit TabComponent(PluginEditor* editor); Canvas* newPatch(); @@ -72,7 +72,7 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd struct TabDragConstrainer : public ComponentBoundsConstrainer { - TabDragConstrainer(TabComponent* parent) : parent(parent) + explicit TabDragConstrainer(TabComponent* parent) : parent(parent) { } @@ -162,7 +162,7 @@ class TabComponent : public Component, public DragAndDropTarget, public AsyncUpd closeButton.setCentrePosition(getLocalBounds().getCentre().withX(getWidth() - 15).translated(0, 1)); } - ScaledImage generateTabBarButtonImage() + ScaledImage generateTabBarButtonImage() const { if(!cnv) return {}; From c4707dfda17f06c1cd05e63168c1bd4c5bc698be Mon Sep 17 00:00:00 2001 From: Timothy Schoen Date: Sat, 8 Jun 2024 18:32:08 +0200 Subject: [PATCH 0929/1030] Applied formatting --- Source/Canvas.cpp | 274 ++++---- Source/Canvas.h | 48 +- Source/CanvasViewport.h | 34 +- Source/Components/ArrowPopupMenu.h | 18 +- Source/Components/Buttons.cpp | 4 +- Source/Components/Buttons.h | 43 +- Source/Components/CheckedTooltip.h | 2 +- Source/Components/DraggableNumber.h | 32 +- Source/Components/GraphArea.h | 35 +- Source/Components/MarkupDisplay.h | 42 +- Source/Components/ObjectDragAndDrop.h | 14 +- Source/Components/PropertiesPanel.h | 21 +- Source/Components/SuggestionComponent.h | 89 ++- Source/Components/TouchSelectionHelper.h | 8 +- Source/Components/WelcomePanel.h | 212 +++--- Source/Connection.cpp | 146 ++-- Source/Connection.h | 39 +- Source/Constants.h | 4 +- Source/Dialogs/AboutPanel.h | 127 ++-- Source/Dialogs/AddObjectMenu.h | 7 +- Source/Dialogs/AudioOutputSettings.h | 64 +- Source/Dialogs/AudioSettingsPanel.h | 2 +- Source/Dialogs/ConnectionMessageDisplay.h | 17 +- Source/Dialogs/Dialogs.cpp | 98 ++- Source/Dialogs/Dialogs.h | 22 +- Source/Dialogs/HelpDialog.h | 43 +- Source/Dialogs/KeyMappingPanel.h | 6 +- Source/Dialogs/MainMenu.h | 5 +- Source/Dialogs/ObjectBrowserDialog.h | 25 +- Source/Dialogs/ObjectReferenceDialog.h | 5 +- Source/Dialogs/OverlayDisplaySettings.h | 31 +- Source/Dialogs/PathsAndLibrariesPanel.h | 5 +- Source/Dialogs/SnapSettings.h | 19 +- Source/Dialogs/TextEditorDialog.h | 10 +- Source/Dialogs/ThemePanel.h | 38 +- Source/Heavy/DaisyExporter.h | 7 +- Source/Heavy/ExporterBase.h | 7 +- Source/Heavy/ExportingProgressView.h | 4 +- Source/Heavy/HeavyExportDialog.cpp | 7 +- Source/Heavy/Toolchain.h | 31 +- Source/Iolet.cpp | 39 +- Source/Iolet.h | 2 +- Source/LookAndFeel.cpp | 139 ++-- Source/LookAndFeel.h | 14 +- Source/NVGSurface.cpp | 153 +++-- Source/NVGSurface.h | 263 ++++--- Source/Object.cpp | 190 +++-- Source/Object.h | 29 +- Source/ObjectGrid.cpp | 58 +- Source/ObjectGrid.h | 7 +- Source/Objects/AllGuis.h | 106 +-- Source/Objects/ArrayObject.h | 524 +++++++------- Source/Objects/AtomHelper.h | 19 +- Source/Objects/BangObject.h | 25 +- Source/Objects/BicoeffObject.h | 12 +- Source/Objects/ButtonObject.h | 76 +- Source/Objects/CanvasObject.h | 13 +- Source/Objects/CommentObject.h | 139 ++-- Source/Objects/FloatAtomObject.h | 21 +- Source/Objects/FunctionObject.h | 6 +- Source/Objects/Gem.h | 201 +++--- Source/Objects/GraphOnParent.h | 93 ++- Source/Objects/IEMHelper.h | 15 +- Source/Objects/KeyboardObject.h | 31 +- Source/Objects/KnobObject.h | 70 +- Source/Objects/ListObject.h | 17 +- Source/Objects/LuaObject.h | 650 +++++++++--------- Source/Objects/MessageObject.h | 119 ++-- Source/Objects/MousePadObject.h | 3 +- Source/Objects/NoteObject.h | 4 +- Source/Objects/NumberObject.h | 6 +- Source/Objects/NumboxTildeObject.h | 8 +- Source/Objects/ObjectBase.cpp | 43 +- Source/Objects/ObjectBase.h | 59 +- Source/Objects/ObjectImplementations.h | 8 +- Source/Objects/ObjectParameters.h | 2 +- Source/Objects/OpenFileObject.h | 49 +- Source/Objects/PictureObject.h | 49 +- Source/Objects/RadioObject.h | 10 +- Source/Objects/ScalarObject.h | 107 ++- Source/Objects/ScopeObject.h | 1 - Source/Objects/SliderObject.h | 36 +- Source/Objects/SubpatchObject.h | 6 +- Source/Objects/SymbolAtomObject.h | 14 +- Source/Objects/TextObject.h | 81 ++- Source/Objects/ToggleObject.h | 8 +- Source/Objects/VUMeterObject.h | 4 +- Source/Pd/Instance.cpp | 157 ++--- Source/Pd/Instance.h | 19 +- Source/Pd/Interface.h | 41 +- Source/Pd/Library.cpp | 49 +- Source/Pd/Library.h | 6 +- Source/Pd/MessageListener.h | 24 +- Source/Pd/Patch.cpp | 27 +- Source/Pd/Patch.h | 8 +- Source/Pd/Setup.cpp | 28 +- Source/Pd/Setup.h | 2 +- Source/PluginEditor.cpp | 183 +++-- Source/PluginEditor.h | 45 +- Source/PluginMode.h | 44 +- Source/PluginProcessor.cpp | 235 +++---- Source/PluginProcessor.h | 10 +- Source/Sidebar/AutomationPanel.h | 14 +- Source/Sidebar/Console.h | 25 +- Source/Sidebar/DocumentationBrowser.h | 254 +++---- Source/Sidebar/PaletteItem.cpp | 2 +- Source/Sidebar/Palettes.h | 33 +- Source/Sidebar/SearchPanel.h | 335 +++++---- Source/Sidebar/Sidebar.cpp | 14 +- Source/Sidebar/Sidebar.h | 2 +- Source/Standalone/InternalSynth.cpp | 8 +- Source/Standalone/PlugDataWindow.h | 30 +- Source/Statusbar.cpp | 150 ++-- Source/Statusbar.h | 14 +- Source/TabComponent.cpp | 641 ++++++++--------- Source/TabComponent.h | 215 +++--- Source/Utility/AudioMidiFifo.h | 14 +- Source/Utility/AudioSampleRingBuffer.h | 6 +- Source/Utility/Autosave.h | 21 +- Source/Utility/CachedStringWidth.h | 68 +- Source/Utility/CachedTextRender.h | 36 +- Source/Utility/Config.cpp | 2 - Source/Utility/Config.h | 19 +- Source/Utility/FileSystemWatcher.h | 11 +- Source/Utility/Limiter.h | 4 +- Source/Utility/MidiDeviceManager.h | 5 +- Source/Utility/NanoVGGraphicsContext.cpp | 517 +++++++------- Source/Utility/NanoVGGraphicsContext.h | 82 ++- Source/Utility/OSUtils.cpp | 7 +- Source/Utility/OSUtils.h | 4 +- Source/Utility/ObjectThemeManager.h | 7 +- Source/Utility/OfflineObjectRenderer.cpp | 415 ++++++----- Source/Utility/OfflineObjectRenderer.h | 2 +- Source/Utility/SettingsFile.cpp | 14 +- Source/Utility/SettingsFile.h | 4 +- Source/Utility/Spline.h | 117 ++-- Source/Utility/StackShadow.cpp | 4 +- Source/Utility/StackShadow.h | 18 +- Source/Utility/ThreadSafeStack.h | 17 +- Source/Utility/ValueTreeViewer.h | 396 +++++------ .../Utility/ZoomableDragAndDropContainer.cpp | 29 +- 141 files changed, 4606 insertions(+), 5002 deletions(-) diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 9970e1f93b..b45e35bb76 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -24,7 +24,6 @@ #include "Components/GraphArea.h" #include "Utility/RateReducer.h" - extern "C" { void canvas_setgraph(t_glist* x, int flag, int nogoprect); } @@ -68,12 +67,12 @@ Canvas::Canvas(PluginEditor* parent, pd::Patch::Ptr p, Component* parentGraph) patchWidth.addListener(this); patchHeight.addListener(this); - - globalMouseListener.globalMouseMove = [this](const MouseEvent& e){ + + globalMouseListener.globalMouseMove = [this](MouseEvent const& e) { lastMouseX = e.x; lastMouseY = e.y; }; - globalMouseListener.globalMouseDrag = [this](const MouseEvent& e){ + globalMouseListener.globalMouseDrag = [this](MouseEvent const& e) { lastMouseX = e.x; lastMouseY = e.y; }; @@ -163,14 +162,14 @@ Canvas::Canvas(PluginEditor* parent, pd::Patch::Ptr p, Component* parentGraph) parameters.addParamRange("X range", cGeneral, &xRange, { 0.0f, 1.0f }); parameters.addParamRange("Y range", cGeneral, &yRange, { 1.0f, 0.0f }); - auto onInteractionFn = [this](bool state){ + auto onInteractionFn = [this](bool state) { dimensionsAreBeingEdited = state; repaint(); }; parameters.addParamInt("Width", cDimensions, &patchWidth, 527, onInteractionFn); parameters.addParamInt("Height", cDimensions, &patchHeight, 327, onInteractionFn); - + updatePatchSnapshot(); } @@ -187,109 +186,105 @@ bool Canvas::updateFramebuffers(NVGcontext* nvg, Rectangle invalidRegion, i auto start = Time::getMillisecondCounter(); auto pixelScale = getRenderScale(); auto zoom = isScrolling ? 2.0f : getValue(zoomScale); - + int const logicalIoletsSize = 16 * 4; int const ioletBufferSize = logicalIoletsSize * pixelScale * zoom; - + // First, check if we need to update our iolet buffer - if(ioletBuffer.needsUpdate(ioletBufferSize, ioletBufferSize)) - { - ioletBuffer.renderToFramebuffer(nvg, ioletBufferSize, ioletBufferSize, [this, zoom, ioletBufferSize, pixelScale](NVGcontext* nvg){ + if (ioletBuffer.needsUpdate(ioletBufferSize, ioletBufferSize)) { + ioletBuffer.renderToFramebuffer(nvg, ioletBufferSize, ioletBufferSize, [this, zoom, ioletBufferSize, pixelScale](NVGcontext* nvg) { nvgViewport(0, 0, ioletBufferSize, ioletBufferSize); nvgClear(nvg); - + nvgBeginFrame(nvg, logicalIoletsSize * zoom, logicalIoletsSize * zoom, pixelScale); nvgScale(nvg, zoom, zoom); - - auto renderIolet = [](NVGcontext* nvg, Rectangle bounds, NVGcolor background, NVGcolor outline){ + + auto renderIolet = [](NVGcontext* nvg, Rectangle bounds, NVGcolor background, NVGcolor outline) { if (PlugDataLook::getUseSquareIolets()) { nvgBeginPath(nvg); nvgRect(nvg, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); - + nvgFillColor(nvg, background); nvgFill(nvg); - + nvgStrokeColor(nvg, outline); nvgStroke(nvg); - } - else { + } else { nvgBeginPath(nvg); nvgFillColor(nvg, background); nvgCircle(nvg, bounds.getCentreX(), bounds.getCentreY(), bounds.getWidth() / 2.0f); nvgFill(nvg); - + nvgStrokeColor(nvg, outline); nvgStroke(nvg); } }; - - auto ioletColours = std::vector{ + + auto ioletColours = std::vector { findColour(PlugDataColour::dataColourId), findColour(PlugDataColour::signalColourId), findColour(PlugDataColour::gemColourId), - findColour(PlugDataColour::canvasBackgroundColourId).contrasting(0.5f)}; - + findColour(PlugDataColour::canvasBackgroundColourId).contrasting(0.5f) + }; + auto outlineColour = findNVGColour(PlugDataColour::objectOutlineColourId); - for(int i = 0; i < 4; i++) - { + for (int i = 0; i < 4; i++) { auto backgroundColour = convertColour(ioletColours[i]); auto ioletRow = Rectangle(0, i * 16 + 0.5f, logicalIoletsSize, 12.5f); renderIolet(nvg, ioletRow.removeFromLeft(16).reduced(4.0f), backgroundColour, outlineColour); // normal renderIolet(nvg, ioletRow.removeFromLeft(16).reduced(2.5f), backgroundColour, outlineColour); // hovered } - + nvgEndFrame(nvg); }); - + editor->nvgSurface.invalidateAll(); } - + int const resizerLogicalSize = 9; int const resizerBufferSize = resizerLogicalSize * pixelScale * zoom; - - if(resizeHandleImage.needsUpdate(resizerBufferSize, resizerBufferSize)) - { - resizeHandleImage = NVGImage(nvg, resizerBufferSize, resizerBufferSize, [this, pixelScale, zoom](Graphics& g){ + + if (resizeHandleImage.needsUpdate(resizerBufferSize, resizerBufferSize)) { + resizeHandleImage = NVGImage(nvg, resizerBufferSize, resizerBufferSize, [this, pixelScale, zoom](Graphics& g) { g.addTransform(AffineTransform::scale(pixelScale * zoom, pixelScale * zoom)); - + auto b = Rectangle(0, 0, 9, 9); // use the path with a hole in it to exclude the inner rounded rect from painting Path outerArea; outerArea.addRectangle(b); outerArea.setUsingNonZeroWinding(false); - + Path innerArea; - + auto innerRect = b.translated(Object::margin / 2, Object::margin / 2); innerArea.addRoundedRectangle(innerRect, Corners::objectCornerRadius); outerArea.addPath(innerArea); g.reduceClipRegion(outerArea); - + g.setColour(findColour(PlugDataColour::objectSelectedOutlineColourId)); g.fillRoundedRectangle(0.0f, 0.0f, 9.0f, 9.0f, Corners::objectCornerRadius); }); - - + editor->nvgSurface.invalidateAll(); } - + // Then, check if object framebuffers need to be updated - if(isScrolling) { - if(viewport) invalidRegion = (invalidRegion + viewport->getViewPosition()) / zoom; - for(auto* obj : objects) - { + if (isScrolling) { + if (viewport) + invalidRegion = (invalidRegion + viewport->getViewPosition()) / zoom; + for (auto* obj : objects) { auto b = obj->getBounds(); - if(b.intersects(invalidRegion)) { + if (b.intersects(invalidRegion)) { obj->updateFramebuffer(nvg); - + auto elapsed = Time::getMillisecondCounter() - start; - if(elapsed > maxUpdateTimeMs) { + if (elapsed > maxUpdateTimeMs) { return false; } } } } - + return true; } @@ -299,37 +294,36 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) auto backgroundColour = convertColour(findColour(PlugDataColour::canvasBackgroundColourId)); auto dotsColour = convertColour(findColour(PlugDataColour::canvasDotsColourId)); - const auto halfSize = infiniteCanvasSize / 2; - const auto zoom = getValue(zoomScale); + auto const halfSize = infiniteCanvasSize / 2; + auto const zoom = getValue(zoomScale); // apply translation to the canvas nvg objects nvgSave(nvg); - - if(viewport) { + + if (viewport) { nvgTranslate(nvg, -viewport->getViewPositionX(), -viewport->getViewPositionY()); nvgScale(nvg, zoom, zoom); - + invalidRegion = invalidRegion.translated(viewport->getViewPositionX(), viewport->getViewPositionY()); invalidRegion /= zoom; - + nvgFillColor(nvg, backgroundColour); nvgFillRect(nvg, invalidRegion.getX(), invalidRegion.getY(), invalidRegion.getWidth(), invalidRegion.getHeight()); } - - if(viewport && !getValue(locked)) { + + if (viewport && !getValue(locked)) { nvgBeginPath(nvg); nvgRect(nvg, 0, 0, infiniteCanvasSize, infiniteCanvasSize); - + auto feather = getRenderScale() > 1.0f ? 0.25f : 0.75f; - if(getValue(zoomScale) >= 1.0f) { + if (getValue(zoomScale) >= 1.0f) { nvgSave(nvg); nvgTranslate(nvg, canvasOrigin.x % objectGrid.gridSize, canvasOrigin.y % objectGrid.gridSize); // Make sure grid aligns with origin NVGpaint dots = nvgDotPattern(nvg, dotsColour, nvgRGBA(0, 0, 0, 0), objectGrid.gridSize, 1.0f, feather); nvgFillPaint(nvg, dots); nvgFill(nvg); nvgRestore(nvg); - } - else { + } else { nvgSave(nvg); nvgTranslate(nvg, canvasOrigin.x % (objectGrid.gridSize * 4), canvasOrigin.y % (objectGrid.gridSize * 4)); // Make sure grid aligns with origin auto darkDotColour = convertColour(findColour(PlugDataColour::canvasBackgroundColourId).contrasting()); @@ -337,25 +331,23 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) if (zoom < 0.3f && getRenderScale() <= 1.0f) scaledDotSize = jmap(zoom, 0.3f, 0.25f, 4.0f, 8.0f); - for(int i = 0; i < 4; i++) - { + for (int i = 0; i < 4; i++) { nvgTranslate(nvg, objectGrid.gridSize, 0); - NVGpaint dots = nvgDotPattern(nvg, i == 3 ? darkDotColour : dotsColour, nvgRGBA(0, 0, 0, 0), objectGrid.gridSize * 4, scaledDotSize, feather + 0.2f); + NVGpaint dots = nvgDotPattern(nvg, i == 3 ? darkDotColour : dotsColour, nvgRGBA(0, 0, 0, 0), objectGrid.gridSize * 4, scaledDotSize, feather + 0.2f); nvgFillPaint(nvg, dots); nvgFill(nvg); } nvgRestore(nvg); nvgSave(nvg); - - for(int i = 0; i < 4; i++) - { + + for (int i = 0; i < 4; i++) { nvgTranslate(nvg, 0, objectGrid.gridSize); NVGpaint dots = nvgDotPattern(nvg, i == 3 ? darkDotColour : dotsColour, nvgRGBA(0, 0, 0, 0), objectGrid.gridSize * 4, scaledDotSize, feather + 0.2f); nvgFillPaint(nvg, dots); nvgFill(nvg); } - + nvgRestore(nvg); } } @@ -417,31 +409,28 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) else drawBorder(true, false); - // Render objects like [drawcurve], [fillcurve] etc. at the back - for(auto drawable : drawables) - { - if(drawable) { + for (auto drawable : drawables) { + if (drawable) { auto* component = dynamic_cast(drawable.get()); - if(invalidRegion.intersects(component->getBounds())) { + if (invalidRegion.intersects(component->getBounds())) { drawable->render(nvg); } } } - if (::getValue(presentationMode) || isGraph) { renderAllObjects(nvg, invalidRegion); // render presentation mode as clipped 'virtual' plugin view if (::getValue(presentationMode)) { - const auto borderWidth = getValue(patchWidth); - const auto borderHeight = getValue(patchHeight); - const auto pos = Point(halfSize, halfSize); - const auto scale = getValue(zoomScale); - const auto windowCorner = Corners::windowCornerRadius / scale; + auto const borderWidth = getValue(patchWidth); + auto const borderHeight = getValue(patchHeight); + auto const pos = Point(halfSize, halfSize); + auto const scale = getValue(zoomScale); + auto const windowCorner = Corners::windowCornerRadius / scale; - const auto bgColour = convertColour(findColour(PlugDataColour::presentationBackgroundColourId)); - const auto windowOutlineColour = convertColour(findColour(PlugDataColour::presentationBackgroundColourId).contrasting(0.3f)); + auto const bgColour = convertColour(findColour(PlugDataColour::presentationBackgroundColourId)); + auto const windowOutlineColour = convertColour(findColour(PlugDataColour::presentationBackgroundColourId).contrasting(0.3f)); nvgSave(nvg); @@ -459,11 +448,10 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) nvgPathWinding(nvg, NVG_HOLE); nvgRoundedRect(nvg, pos.getX(), pos.getY(), borderWidth, borderHeight, windowCorner); - const int shadowSize = 24 / scale; + int const shadowSize = 24 / scale; auto borderArea = Rectangle(0, 0, borderWidth, borderHeight).expanded(shadowSize); - if (presentationShadowImage.needsUpdate(borderArea.getWidth(), borderArea.getHeight())) - { - presentationShadowImage = NVGImage(nvg, borderArea.getWidth(), borderArea.getHeight(), [borderArea, shadowSize, windowCorner](Graphics& g){ + if (presentationShadowImage.needsUpdate(borderArea.getWidth(), borderArea.getHeight())) { + presentationShadowImage = NVGImage(nvg, borderArea.getWidth(), borderArea.getHeight(), [borderArea, shadowSize, windowCorner](Graphics& g) { auto shadowPath = Path(); shadowPath.addRoundedRectangle(borderArea.reduced(shadowSize).withPosition(shadowSize, shadowSize), windowCorner); StackShadow::renderDropShadow(g, shadowPath, Colours::black, shadowSize, Point(0, 2)); @@ -491,29 +479,28 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) } } - for(auto* connection : connectionsBeingCreated) - { + for (auto* connection : connectionsBeingCreated) { nvgSave(nvg); connection->render(nvg); nvgRestore(nvg); } - - if(graphArea) { + + if (graphArea) { nvgSave(nvg); nvgTranslate(nvg, graphArea->getX(), graphArea->getY()); graphArea->render(nvg); nvgRestore(nvg); } - + objectGrid.render(nvg); - - if(viewport && lasso.isVisible() && !lasso.getBounds().isEmpty()) { + + if (viewport && lasso.isVisible() && !lasso.getBounds().isEmpty()) { auto lassoBounds = lasso.getBounds().toFloat().reduced(1.0f); auto smallestSide = lassoBounds.getWidth() < lassoBounds.getHeight() ? lassoBounds.getWidth() : lassoBounds.getHeight(); auto fillColour = convertColour(findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.075f)); auto outlineColour = convertColour(findColour(PlugDataColour::canvasBackgroundColourId).interpolatedWith(findColour(PlugDataColour::objectSelectedOutlineColourId), 0.65f)); - + nvgBeginPath(nvg); nvgFillColor(nvg, fillColour); nvgRect(nvg, lassoBounds.getX(), lassoBounds.getY(), lassoBounds.getWidth(), lassoBounds.getHeight()); @@ -522,17 +509,16 @@ void Canvas::performRender(NVGcontext* nvg, Rectangle invalidRegion) nvgStrokeWidth(nvg, smallestSide < 1.0f ? 0.5f : 1.0f); // if one of the sides is smaller than 1px, we need to adjust the stroke width to prevent drawing out of bounds nvgStroke(nvg); } - + suggestor->renderAutocompletion(nvg); if (dimensionsAreBeingEdited) drawBorder(false, true); - + nvgRestore(nvg); - + // Draw scrollbars - if(viewport) - { + if (viewport) { reinterpret_cast(viewport.get())->render(nvg); } } @@ -545,32 +531,29 @@ float Canvas::getRenderScale() const void Canvas::updatePatchSnapshot() { auto patchFile = patch.getCurrentFile(); - - if(patchFile.existsAsFile()) - { + + if (patchFile.existsAsFile()) { auto recentlyOpenedTree = SettingsFile::getInstance()->getValueTree().getChildWithName("RecentlyOpened"); for (int i = 0; i < recentlyOpenedTree.getNumChildren(); i++) { auto recentlyOpenedFile = File(recentlyOpenedTree.getChild(i).getProperty("Path").toString()); // Check if patch is in the recently opened list - if(File(recentlyOpenedFile) == patchFile) - { + if (File(recentlyOpenedFile) == patchFile) { // If so, generate an svg sihouette that we can show on the welcome page String svgSilhouette; - + auto regionOfInterest = Rectangle(); for (auto* object : objects) { regionOfInterest = regionOfInterest.getUnion(object->getBounds().reduced(Object::margin)); } - - for (auto* object : objects) - { + + for (auto* object : objects) { auto rect = object->getBounds().reduced(Object::margin) - regionOfInterest.getPosition(); svgSilhouette += String::formatted( "\n", rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight(), Corners::objectCornerRadius, Corners::objectCornerRadius); } svgSilhouette = "\n" + svgSilhouette + ""; - + recentlyOpenedTree.getChild(i).setProperty("Snapshot", svgSilhouette, nullptr); break; } @@ -578,44 +561,40 @@ void Canvas::updatePatchSnapshot() } } - void Canvas::renderAllObjects(NVGcontext* nvg, Rectangle area) { - for(auto* obj : objects) - { + for (auto* obj : objects) { auto b = obj->getBounds(); - + nvgSave(nvg); nvgTranslate(nvg, b.getX(), b.getY()); - if(b.intersects(area) && obj->isVisible()) { + if (b.intersects(area) && obj->isVisible()) { obj->render(nvg); } nvgRestore(nvg); - + // Draw label in canvas coordinates obj->renderLabel(nvg); } } void Canvas::renderAllConnections(NVGcontext* nvg, Rectangle area) { - if (! connectionLayer.isVisible()) + if (!connectionLayer.isVisible()) return; Array connectionsToDraw; - for(auto* connection : connections) - { + for (auto* connection : connections) { nvgSave(nvg); - if(connection->intersectsRectangle(area) && connection->isVisible()) { + if (connection->intersectsRectangle(area) && connection->isVisible()) { connection->render(nvg); if (showConnectionOrder) connectionsToDraw.add(connection); } nvgRestore(nvg); } - if (!connectionsToDraw.isEmpty()){ - for (auto* connection : connectionsToDraw) - { + if (!connectionsToDraw.isEmpty()) { + for (auto* connection : connectionsToDraw) { nvgSave(nvg); connection->renderConnectionOrder(nvg); nvgRestore(nvg); @@ -718,8 +697,7 @@ void Canvas::jumpToLastKnownPosition() void Canvas::saveViewportPosition() { - if(viewport) - { + if (viewport) { patch.lastViewportPosition = viewport->getViewPosition().transformedBy(getTransform().inverted()) - canvasOrigin; } } @@ -734,7 +712,7 @@ void Canvas::zoomToFitAll() auto regionOfInterest = Rectangle(canvasOrigin.x, canvasOrigin.y, getValue(patchWidth), getValue(patchHeight)); if (!presentationMode.getValue()) { - for (auto *object: objects) { + for (auto* object : objects) { regionOfInterest = regionOfInterest.getUnion(object->getBounds().reduced(Object::margin)); } } @@ -780,7 +758,7 @@ void Canvas::tabChanged() obj->gui->tabChanged(); } - + editor->statusbar->updateZoomLevel(); editor->repaint(); // Make sure everything it up to date } @@ -795,7 +773,7 @@ void Canvas::save(std::function const& nestedCallback) } } } - + if (patch.getCurrentFile().existsAsFile()) { canvasToSave->updatePatchSnapshot(); canvasToSave->patch.savePatch(); @@ -814,9 +792,10 @@ void Canvas::saveAs(std::function const& nestedCallback) if (result.getFullPathName().isNotEmpty()) { if (result.exists()) result.deleteFile(); - - if(!result.hasFileExtension("pd")) result = result.getFullPathName() + ".pd"; - + + if (!result.hasFileExtension("pd")) + result = result.getFullPathName() + ".pd"; + updatePatchSnapshot(); patch.savePatch(resultURL); SettingsFile::getInstance()->addToRecentlyOpened(result); @@ -828,7 +807,6 @@ void Canvas::saveAs(std::function const& nestedCallback) "*.pd", "Patch", this); } - void Canvas::handleAsyncUpdate() { performSynchronise(); @@ -841,8 +819,7 @@ void Canvas::synchronise() void Canvas::synchroniseSplitCanvas() { - for(auto* canvas : editor->getTabComponent().getVisibleCanvases()) - { + for (auto* canvas : editor->getTabComponent().getVisibleCanvases()) { canvas->synchronise(); } } @@ -987,14 +964,14 @@ void Canvas::performSynchronise() editor->updateCommandStatus(); repaint(); - + needsSearchUpdate = true; pd->updateObjectImplementations(); } void Canvas::updateDrawables() -{ +{ for (auto* object : objects) { if (object->gui) { object->gui->updateDrawables(); @@ -1168,7 +1145,7 @@ bool Canvas::autoscroll(MouseEvent const& e) Point Canvas::getLastMousePosition() { - return {lastMouseX, lastMouseY}; + return { lastMouseX, lastMouseY }; } void Canvas::mouseUp(MouseEvent const& e) @@ -1184,9 +1161,9 @@ void Canvas::mouseUp(MouseEvent const& e) deselectAll(); setSelected(objects[objects.size() - 1], true); // Select newly created object } - + // Make sure the drag-over toggle action is ended - if(!isDraggingLasso) { + if (!isDraggingLasso) { for (auto* object : objects) { if (auto* obj = object->gui.get()) { obj->untoggleObject(); @@ -1221,9 +1198,9 @@ void Canvas::updateSidebarSelection() #if JUCE_IOS editor->showTouchSelectionHelper(selectedComponents.getNumSelected()); #endif - + auto lassoSelection = getSelectionOfType(); - + if (lassoSelection.size() > 0) { Array allParameters; for (auto* object : lassoSelection) { @@ -1685,19 +1662,18 @@ void Canvas::removeSelectedConnections() void Canvas::triggerizeSelection() { auto selectedBoxes = getSelectionOfType(); - + std::vector objects; for (auto* object : getSelectionOfType()) { if (auto* ptr = object->getPointer()) { objects.push_back(ptr); } } - - if(auto patchPtr = patch.getPointer()) - { + + if (auto patchPtr = patch.getPointer()) { pd::Interface::triggerize(patchPtr.get(), objects); } - + synchronise(); } @@ -2256,10 +2232,10 @@ bool Canvas::setPanDragMode(bool shouldPan) bool Canvas::isPointOutsidePluginArea(Point point) { - const auto borderWidth = getValue(patchWidth); - const auto borderHeight = getValue(patchHeight); - const auto halfSize = infiniteCanvasSize / 2; - const auto pos = Point(halfSize, halfSize); + auto const borderWidth = getValue(patchWidth); + auto const borderHeight = getValue(patchHeight); + auto const halfSize = infiniteCanvasSize / 2; + auto const pos = Point(halfSize, halfSize); auto pluginBounds = Rectangle(pos.x, pos.y, borderWidth, borderHeight); @@ -2268,7 +2244,7 @@ bool Canvas::isPointOutsidePluginArea(Point point) void Canvas::findLassoItemsInArea(Array>& itemsFound, Rectangle const& area) { - const auto lassoArea = area.withSize(jmax(area.getWidth(), 1), jmax(area.getHeight(), 1)); + auto const lassoArea = area.withSize(jmax(area.getWidth(), 1), jmax(area.getHeight(), 1)); for (auto* object : objects) { if (lassoArea.intersects(object->getSelectableBounds())) { @@ -2308,9 +2284,9 @@ bool Canvas::panningModifierDown() auto& commandManager = editor->commandManager; // check the command manager for the keycode that is assigned to pan drag key auto panDragKeycode = commandManager.getKeyMappings()->getKeyPressesAssignedToCommand(CommandIDs::PanDragKey).getFirst().getKeyCode(); - + // get the current modifier keys, removing the left mouse button modifier (as that is what is needed to activate a pan drag with key down) - auto currentMods = ModifierKeys(ModifierKeys::getCurrentModifiers().getRawFlags() &~ ModifierKeys::leftButtonModifier); + auto currentMods = ModifierKeys(ModifierKeys::getCurrentModifiers().getRawFlags() & ~ModifierKeys::leftButtonModifier); bool isPanDragKeysActive = false; diff --git a/Source/Canvas.h b/Source/Canvas.h index e6e7e46160..bdcc5438f3 100644 --- a/Source/Canvas.h +++ b/Source/Canvas.h @@ -8,11 +8,11 @@ #include #if NANOVG_GL_IMPLEMENTATION -#include +# include using namespace juce::gl; -#undef NANOVG_GL_IMPLEMENTATION -#include -#define NANOVG_GL_IMPLEMENTATION 1 +# undef NANOVG_GL_IMPLEMENTATION +# include +# define NANOVG_GL_IMPLEMENTATION 1 #endif #include "ObjectGrid.h" // move to impl @@ -58,9 +58,8 @@ class Canvas : public Component , public LassoSource> , public ModifierKeyListener , public pd::MessageListener - , public AsyncUpdater - , public NVGComponent -{ + , public AsyncUpdater + , public NVGComponent { public: Canvas(PluginEditor* parent, pd::Patch::Ptr patch, Component* parentGraph = nullptr); @@ -73,9 +72,9 @@ class Canvas : public Component void mouseDrag(MouseEvent const& e) override; void mouseUp(MouseEvent const& e) override; bool hitTest(int x, int y) override; - + Point getLastMousePosition(); - + void commandKeyChanged(bool isHeld) override; void middleMouseChanged(bool isHeld) override; void altKeyChanged(bool isHeld) override; @@ -84,10 +83,10 @@ class Canvas : public Component void focusGained(FocusChangeType cause) override; void focusLost(FocusChangeType cause) override; - + bool updateFramebuffers(NVGcontext* nvg, Rectangle invalidRegion, int maxUpdateTimeMs); void performRender(NVGcontext* nvg, Rectangle invalidRegion); - + void resized() override; void renderAllObjects(NVGcontext* nvg, Rectangle area); @@ -100,9 +99,9 @@ class Canvas : public Component bool shouldShowIndex(); bool shouldShowConnectionDirection(); bool shouldShowConnectionActivity(); - - void save(std::function const& nestedCallback = [](){}); - void saveAs(std::function const& nestedCallback = [](){}); + + void save(std::function const& nestedCallback = []() {}); + void saveAs(std::function const& nestedCallback = []() {}); void synchroniseSplitCanvas(); void synchronise(); @@ -141,11 +140,11 @@ class Canvas : public Component void jumpToOrigin(); void jumpToLastKnownPosition(); void saveViewportPosition(); - + void zoomToFitAll(); - + void updatePatchSnapshot(); - + float getRenderScale() const; bool autoscroll(MouseEvent const& e); @@ -166,7 +165,7 @@ class Canvas : public Component void updateSidebarSelection(); void orderConnections(); - + void showSuggestions(Object* object, TextEditor* textEditor); void hideSuggestions(); @@ -222,12 +221,12 @@ class Canvas : public Component bool showConnectionDirection = false; bool showConnectionActivity = false; - + bool isScrolling = false; - + bool isGraph = false; bool isDraggingLasso = false; - + bool needsSearchUpdate = false; Value isGraphChild = SynchronousValue(var(false)); @@ -260,20 +259,19 @@ class Canvas : public Component Component objectLayer; Component connectionLayer; - + NVGFramebuffer ioletBuffer; NVGImage resizeHandleImage; NVGImage presentationShadowImage; Rectangle lastPresentationBounds; - + Array> drawables; private: - GlobalMouseListener globalMouseListener; bool dimensionsAreBeingEdited = false; - + int lastMouseX, lastMouseY; LassoComponent> lasso; diff --git a/Source/CanvasViewport.h b/Source/CanvasViewport.h index f97c72124c..458a0539c7 100644 --- a/Source/CanvasViewport.h +++ b/Source/CanvasViewport.h @@ -23,8 +23,9 @@ using namespace gl; #include "Utility/SettingsFile.h" // Special viewport that shows scrollbars on top of content instead of next to it -class CanvasViewport : public Viewport, public Timer, public NVGComponent -{ +class CanvasViewport : public Viewport + , public Timer + , public NVGComponent { class MousePanner : public MouseListener { public: explicit MousePanner(CanvasViewport* vp) @@ -65,7 +66,7 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent Point downPosition; Point downCanvasOrigin; }; - + class ViewportScrollBar : public Component { struct FadeTimer : private ::Timer { std::function callback; @@ -223,7 +224,7 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent auto growingBounds = thumbBounds.reduced(1).withTop(thumbBounds.getY() + growPosition); auto thumbCornerRadius = growingBounds.getHeight(); auto fullBounds = growingBounds.withX(2).withWidth(getWidth() - 4); - + auto canvasColour = findColour(PlugDataColour::canvasBackgroundColourId); auto scrollbarColour = findColour(ScrollBar::ColourIds::thumbColourId); auto activeScrollbarColour = scrollbarColour.interpolatedWith(canvasColour.contrasting(0.6f), 0.7f); @@ -241,12 +242,11 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent nvgRoundedRect(nvg, fullBounds.getX(), fullBounds.getY(), fullBounds.getWidth(), fullBounds.getHeight(), scaledTCR); nvgFillColor(nvg, convertColour(fadeColour)); nvgFill(nvg); - + nvgBeginPath(nvg); nvgRoundedRect(nvg, growingBounds.getX(), growingBounds.getY(), growingBounds.getWidth(), growingBounds.getHeight(), scaledTCR); nvgFillColor(nvg, isMouseDragging ? convertColour(activeScrollbarColour) : convertColour(scrollbarColour)); nvgFill(nvg); - } private: @@ -303,28 +303,28 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent addAndMakeVisible(vbar); addAndMakeVisible(hbar); - + cnv->setCachedComponentImage(new NVGSurface::InvalidationListener(editor->nvgSurface, cnv)); setCachedComponentImage(new NVGSurface::InvalidationListener(editor->nvgSurface, this)); } - + ~CanvasViewport() { } - + void render(NVGcontext* nvg) override { nvgSave(nvg); nvgTranslate(nvg, vbar.getX(), vbar.getY()); vbar.render(nvg); nvgRestore(nvg); - + nvgSave(nvg); nvgTranslate(nvg, hbar.getX(), hbar.getY()); hbar.render(nvg); nvgRestore(nvg); } - + void lookAndFeelChanged() override { hbar.repaint(); @@ -366,17 +366,16 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent // Apply and limit zoom magnify(std::clamp(getValue(cnv->zoomScale) * scrollFactor, 0.25f, 3.0f)); - + lastZoomTime = e.eventTime; } - - + void magnify(float newScaleFactor) { if (approximatelyEqual(newScaleFactor, 0.0f)) { newScaleFactor = 1.0f; } - + // Get floating point mouse position relative to screen auto mousePosition = Desktop::getInstance().getMainMouseSource().getScreenPosition(); // Get mouse position relative to canvas @@ -388,7 +387,7 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent // Calculate offset to keep our mouse position the same as before this zoom action auto offset = newPosition - oldPosition; cnv->setTopLeftPosition(cnv->getPosition() + offset.roundToInt()); - + // This is needed to make sure the viewport the current canvas bounds to the lastVisibleArea variable // Without this, future calls to getViewPosition() will give wrong results resized(); @@ -437,7 +436,7 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent adjustScrollbarBounds(); editor->nvgSurface.invalidateAll(); } - + void timerCallback() override { stopTimer(); @@ -489,7 +488,6 @@ class CanvasViewport : public Viewport, public Timer, public NVGComponent std::function onScroll = []() {}; private: - Time lastScrollTime; Time lastZoomTime; PluginEditor* editor; diff --git a/Source/Components/ArrowPopupMenu.h b/Source/Components/ArrowPopupMenu.h index b2a53899ed..1144785e19 100644 --- a/Source/Components/ArrowPopupMenu.h +++ b/Source/Components/ArrowPopupMenu.h @@ -99,26 +99,28 @@ class ArrowPopupMenu : public Component { auto* target = options.getTargetComponent(); auto* parent = options.getParentComponent(); - + auto* arrow = new ArrowPopupMenu(target); menu->showMenuAsync(options, [userCallback, arrow](int result) { - if(arrow->isOnDesktop()) arrow->removeFromDesktop(); + if (arrow->isOnDesktop()) + arrow->removeFromDesktop(); delete arrow; userCallback(result); }); - - if(ProjectInfo::canUseSemiTransparentWindows()) { + + if (ProjectInfo::canUseSemiTransparentWindows()) { if (auto* popupMenuComponent = Component::getCurrentlyModalComponent(0)) { arrow->attachToMenu(popupMenuComponent, parent); } } } - - void componentBroughtToFront (Component& c) override + + void componentBroughtToFront(Component& c) override { - MessageManager::callAsync([_this = SafePointer(this)](){ - if(_this && _this->isOnDesktop()) _this->toFront(false); + MessageManager::callAsync([_this = SafePointer(this)]() { + if (_this && _this->isOnDesktop()) + _this->toFront(false); }); } diff --git a/Source/Components/Buttons.cpp b/Source/Components/Buttons.cpp index 20ad3ae255..d88d917dcd 100644 --- a/Source/Components/Buttons.cpp +++ b/Source/Components/Buttons.cpp @@ -11,7 +11,7 @@ String MainToolbarButton::getTooltip() { auto setTooltip = TextButton::getTooltip(); if (auto* editor = dynamic_cast(getParentComponent())) { - if(auto* cnv = editor->getCurrentCanvas()) { + if (auto* cnv = editor->getCurrentCanvas()) { if (isUndo) { setTooltip = "Undo"; if (cnv->patch.canUndo() && cnv->patch.lastUndoSequence != "") @@ -19,7 +19,7 @@ String MainToolbarButton::getTooltip() } else if (isRedo) { setTooltip = "Redo"; if (cnv->patch.canRedo() && cnv->patch.lastRedoSequence != "") - setTooltip += ": " /* + cnv->patch.getTitle() + ": " */ + cnv->patch.lastRedoSequence; + setTooltip += ": " /* + cnv->patch.getTitle() + ": " */ + cnv->patch.lastRedoSequence; } } } diff --git a/Source/Components/Buttons.h b/Source/Components/Buttons.h index 3794ac7676..d2adf26e00 100644 --- a/Source/Components/Buttons.h +++ b/Source/Components/Buttons.h @@ -23,7 +23,6 @@ class MainToolbarButton : public TextButton { g.setColour(backgroundColour); g.fillRoundedRectangle(bounds, cornerSize); - auto textColour = findColour(PlugDataColour::toolbarTextColourId).withMultipliedAlpha(isEnabled() ? 1.0f : 0.5f); AttributedString attributedIcon; @@ -60,10 +59,10 @@ class ToolbarRadioButton : public TextButton { g.setColour(backgroundColour); Path p; p.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), Corners::defaultCornerRadius, Corners::defaultCornerRadius, - !(flatOnLeft || flatOnTop), - !(flatOnRight || flatOnTop), - !(flatOnLeft || flatOnBottom), - !(flatOnRight || flatOnBottom)); + !(flatOnLeft || flatOnTop), + !(flatOnRight || flatOnTop), + !(flatOnLeft || flatOnBottom), + !(flatOnRight || flatOnBottom)); g.fillPath(p); auto textColour = findColour(PlugDataColour::toolbarTextColourId).withMultipliedAlpha(isEnabled() ? 1.0f : 0.5f); @@ -107,9 +106,11 @@ class SmallIconButton : public TextButton { class WidePanelButton : public TextButton { String icon; int iconSize; - + public: - WidePanelButton(String icon, int iconSize = 13) : icon(icon), iconSize(iconSize) {}; + WidePanelButton(String icon, int iconSize = 13) + : icon(icon) + , iconSize(iconSize) {}; void mouseEnter(MouseEvent const& e) override { @@ -123,28 +124,28 @@ class WidePanelButton : public TextButton { void paint(Graphics& g) override { - const bool flatOnLeft = isConnectedOnLeft(); - const bool flatOnRight = isConnectedOnRight(); - const bool flatOnTop = isConnectedOnTop(); - const bool flatOnBottom = isConnectedOnBottom(); + bool const flatOnLeft = isConnectedOnLeft(); + bool const flatOnRight = isConnectedOnRight(); + bool const flatOnTop = isConnectedOnTop(); + bool const flatOnBottom = isConnectedOnBottom(); - const float width = getWidth() - 1.0f; - const float height = getHeight() - 1.0f; + float const width = getWidth() - 1.0f; + float const height = getHeight() - 1.0f; - const float cornerSize = Corners::largeCornerRadius; + float const cornerSize = Corners::largeCornerRadius; Path outline; - outline.addRoundedRectangle (0.5f, 0.5f, width, height, cornerSize, cornerSize, - ! (flatOnLeft || flatOnTop), - ! (flatOnRight || flatOnTop), - ! (flatOnLeft || flatOnBottom), - ! (flatOnRight || flatOnBottom)); - + outline.addRoundedRectangle(0.5f, 0.5f, width, height, cornerSize, cornerSize, + !(flatOnLeft || flatOnTop), + !(flatOnRight || flatOnTop), + !(flatOnLeft || flatOnBottom), + !(flatOnRight || flatOnBottom)); + g.setColour(findColour(isMouseOver() ? PlugDataColour::panelActiveBackgroundColourId : PlugDataColour::panelForegroundColourId)); g.fillPath(outline); g.setColour(findColour(PlugDataColour::outlineColourId)); g.strokePath(outline, PathStrokeType(1)); - + Fonts::drawText(g, getButtonText(), getLocalBounds().reduced(12, 2), findColour(PlugDataColour::panelTextColourId), 15); Fonts::drawIcon(g, icon, getLocalBounds().reduced(12, 2).removeFromRight(24), findColour(PlugDataColour::panelTextColourId), iconSize); } diff --git a/Source/Components/CheckedTooltip.h b/Source/Components/CheckedTooltip.h index e86532ce35..7847d00d35 100644 --- a/Source/Components/CheckedTooltip.h +++ b/Source/Components/CheckedTooltip.h @@ -23,7 +23,7 @@ class CheckedTooltip : public TooltipWindow { setOpaque(false); tooltipShadow.setOwner(this); } - + float getDesktopScaleFactor() const override { return Component::getDesktopScaleFactor(); diff --git a/Source/Components/DraggableNumber.h b/Source/Components/DraggableNumber.h index 8d9fab15bd..67fb953d0e 100644 --- a/Source/Components/DraggableNumber.h +++ b/Source/Components/DraggableNumber.h @@ -35,7 +35,7 @@ class DraggableNumber : public Label double valueToResetTo = 0.0; double valueToRevertTo = 0.0; bool showEllipses = true; - + std::unique_ptr nvgCtx; public: @@ -96,7 +96,7 @@ class DraggableNumber : public Label { logarithmicHeight = logHeight; } - + // Toggle between showing ellipses or ">" if number is too large to fit void setShowEllipsesIfTooLong(bool shouldShowEllipses) { @@ -285,10 +285,11 @@ class DraggableNumber : public Label return draggedDecimal; } - + void render(NVGcontext* nvg) { - if(!nvgCtx || nvgCtx->getContext() != nvg) nvgCtx = std::make_unique(nvg); + if (!nvgCtx || nvgCtx->getContext() != nvg) + nvgCtx = std::make_unique(nvg); nvgCtx->setPhysicalPixelScaleFactor(2.0f); Graphics g(*nvgCtx); { @@ -311,26 +312,24 @@ class DraggableNumber : public Label auto extraNumberText = String(); auto numDecimals = numberText.fromFirstOccurrenceOf(".", false, false).length(); auto numberTextLength = CachedFontStringWidth::get()->calculateSingleLineWidth(font, numberText); - + for (int i = 0; i < std::min(hoveredDecimal - decimalDrag, 7 - numDecimals); ++i) extraNumberText += "0"; - + // If show ellipses is false, only show ">" when integers are too large to fit - if(!showEllipses && numDecimals == 0) - { - while(numberTextLength > textArea.getWidth() + 3) - { + if (!showEllipses && numDecimals == 0) { + while (numberTextLength > textArea.getWidth() + 3) { numberText = numberText.trimCharactersAtEnd(".>"); numberText = numberText.dropLastCharacters(1); numberText += ">"; numberTextLength = CachedFontStringWidth::get()->calculateSingleLineWidth(font, numberText); } } - + g.setFont(font); g.setColour(findColour(Label::textColourId)); g.drawText(numberText, textArea, Justification::centredLeft, showEllipses); - + if (dragMode == Regular) { g.setColour(findColour(Label::textColourId).withAlpha(0.4f)); g.drawText(extraNumberText, textArea.withTrimmedLeft(numberTextLength), Justification::centredLeft, false); @@ -449,7 +448,8 @@ class DraggableNumber : public Label auto text = String(value, precision == -1 ? 8 : precision); if (dragMode != Integer) { - if(!text.containsChar('.')) text << '.'; + if (!text.containsChar('.')) + text << '.'; text = text.trimCharactersAtEnd("0"); } @@ -497,7 +497,7 @@ struct DraggableListNumber : public DraggableNumber { { if (isBeingEdited() || !targetFound) return; - + // Hide cursor and set unbounded mouse movement setMouseCursor(MouseCursor::NoCursor); updateMouseCursor(); @@ -507,7 +507,7 @@ struct DraggableListNumber : public DraggableNumber { double const deltaY = (e.y - e.mouseDownPosition.y) * 0.7; double const increment = e.mods.isShiftDown() ? (0.01 * std::floor(-deltaY)) : std::floor(-deltaY); - + double newValue = dragValue + increment; newValue = limitValue(newValue); @@ -526,7 +526,7 @@ struct DraggableListNumber : public DraggableNumber { setText(newText, dontSendNotification); onValueChange(0); - + updateListHoverPosition(e.getMouseDownX()); } diff --git a/Source/Components/GraphArea.h b/Source/Components/GraphArea.h index a432249099..4b27d73a83 100644 --- a/Source/Components/GraphArea.h +++ b/Source/Components/GraphArea.h @@ -5,7 +5,9 @@ */ // Graph bounds component -class GraphArea : public Component, public NVGComponent, public Value::Listener { +class GraphArea : public Component + , public NVGComponent + , public Value::Listener { ComponentBoundsConstrainer constrainer; ResizableBorderComponent resizer; Canvas* canvas; @@ -19,7 +21,7 @@ class GraphArea : public Component, public NVGComponent, public Value::Listener { addAndMakeVisible(resizer); updateBounds(); - + constrainer.setMinimumSize(12, 12); resizer.setBorderThickness(BorderSize(4, 4, 4, 4)); @@ -37,7 +39,7 @@ class GraphArea : public Component, public NVGComponent, public Value::Listener { setVisible(!getValue(v)); } - + Array> getCorners() const { auto rect = getLocalBounds().toFloat().reduced(3.5f); @@ -48,7 +50,7 @@ class GraphArea : public Component, public NVGComponent, public Value::Listener return corners; } - + void render(NVGcontext* nvg) override { auto lineBounds = getLocalBounds().toFloat().reduced(4.0f); @@ -59,14 +61,13 @@ class GraphArea : public Component, public NVGComponent, public Value::Listener nvgStrokeColor(nvg, graphAreaColour); nvgStrokeWidth(nvg, 1.0f); nvgStroke(nvg); - - - auto drawCorner = [](NVGcontext* nvg, int x, int y, int angle){ + + auto drawCorner = [](NVGcontext* nvg, int x, int y, int angle) { nvgSave(nvg); - + nvgTranslate(nvg, x, y); nvgRotate(nvg, degreesToRadians(angle)); - + // (Calculated from svg) nvgBeginPath(nvg); nvgMoveTo(nvg, 3.51f, 9.004f); @@ -77,7 +78,7 @@ class GraphArea : public Component, public NVGComponent, public Value::Listener nvgLineTo(nvg, 6.753f, 0.0f); nvgBezierTo(nvg, 7.995f, 0.0f, 9.004f, 1.009f, 9.004f, 2.251f); - + nvgLineTo(nvg, 9.004f, 3.511f); nvgLineTo(nvg, 6.239f, 3.511f); nvgBezierTo(nvg, 4.733f, 3.511f, 3.51f, 4.734f, 3.51f, 6.24f); @@ -86,7 +87,7 @@ class GraphArea : public Component, public NVGComponent, public Value::Listener nvgFill(nvg); nvgRestore(nvg); }; - + nvgFillColor(nvg, graphAreaColour); drawCorner(nvg, 1, 1, 0); drawCorner(nvg, getWidth() - 1, 1, 90); @@ -96,19 +97,19 @@ class GraphArea : public Component, public NVGComponent, public Value::Listener bool hitTest(int x, int y) override { - return (topLeftCorner.contains(x, y) || topRightCorner.contains(x, y) || bottomLeftCorner.contains(x, y)|| bottomRightCorner.contains(x, y)) && !getLocalBounds().reduced(4).contains(x, y); + return (topLeftCorner.contains(x, y) || topRightCorner.contains(x, y) || bottomLeftCorner.contains(x, y) || bottomRightCorner.contains(x, y)) && !getLocalBounds().reduced(4).contains(x, y); } - + void mouseEnter(MouseEvent const& e) override { repaint(); } - + void mouseExit(MouseEvent const& e) override { repaint(); } - + void mouseMove(MouseEvent const& e) override { repaint(); @@ -126,7 +127,7 @@ class GraphArea : public Component, public NVGComponent, public Value::Listener topRightCorner = getLocalBounds().toFloat().removeFromTop(9).removeFromRight(9).translated(-0.5f, 0.5f); bottomLeftCorner = getLocalBounds().toFloat().removeFromBottom(9).removeFromLeft(9).translated(0.5f, -0.5f); bottomRightCorner = getLocalBounds().toFloat().removeFromBottom(9).removeFromRight(9).translated(-0.5f, -0.5f); - + resizer.setBounds(getLocalBounds()); repaint(); } @@ -140,7 +141,7 @@ class GraphArea : public Component, public NVGComponent, public Value::Listener cnv->gl_xmargin = getX() - canvas->canvasOrigin.x + 4; cnv->gl_ymargin = getY() - canvas->canvasOrigin.y + 4; } - + canvas->updateDrawables(); } diff --git a/Source/Components/MarkupDisplay.h b/Source/Components/MarkupDisplay.h index 701279254e..1944167ea1 100644 --- a/Source/Components/MarkupDisplay.h +++ b/Source/Components/MarkupDisplay.h @@ -196,29 +196,25 @@ class Block : public Component { { mouseDownPosition = event.position; // keep track of position } - + void mouseMove(MouseEvent const& event) override { bool isHoveringLink = false; - - for(auto& [link, bounds] : linkBounds) - { - if(bounds.contains(event.x, event.y)) - { + + for (auto& [link, bounds] : linkBounds) { + if (bounds.contains(event.x, event.y)) { isHoveringLink = true; break; } } - + setMouseCursor(isHoveringLink ? MouseCursor::PointingHandCursor : MouseCursor::NormalCursor); } void mouseUp(MouseEvent const& event) override { - for(auto& [link, bounds] : linkBounds) - { - if(bounds.contains(event.x, event.y)) - { + for (auto& [link, bounds] : linkBounds) { + if (bounds.contains(event.x, event.y)) { URL(link).launchInDefaultBrowser(); break; } @@ -368,11 +364,11 @@ class Block : public Component { } return attributedString; } - + void updateLinkBounds(TextLayout& layout) { linkBounds.clear(); - + // Look for clickable links for (auto& [link, start, end] : links) { int offset = 0; @@ -382,13 +378,13 @@ class Block : public Component { for (int i = start - offset; i < end - offset; i++) { if (i < 0 || i >= run->glyphs.size()) continue; - + auto& glyph = run->glyphs.getReference(i); auto lineBounds = Rectangle(glyph.width, 14).withPosition((glyph.anchor + line.lineOrigin)); currentLinkBounds = linkBounds.isEmpty() ? lineBounds : currentLinkBounds.getUnion(lineBounds); } - - linkBounds.add({link, currentLinkBounds.translated(0, -11)}); + + linkBounds.add({ link, currentLinkBounds.translated(0, -11) }); offset += run->glyphs.size(); } } @@ -404,9 +400,8 @@ class Block : public Component { return parseHexColourStatic(s, defaultColour); } - AttributedString attributedString; - + private: Array>> linkBounds; Array> links; @@ -433,7 +428,7 @@ class TextBlock : public Block { layout.createLayout(attributedString, getWidth()); layout.draw(g, getLocalBounds().toFloat()); } - + void resized() override { TextLayout layout; @@ -849,7 +844,7 @@ class ListItem : public Block { label.draw(g, getLocalBounds().withTrimmedLeft(indent).toFloat()); attributedString.draw(g, getLocalBounds().withTrimmedLeft(indent + gap).toFloat()); } - + void resized() override { TextLayout layout; @@ -889,7 +884,7 @@ class MarkupDisplayComponent : public Component { colours.set("darkyellow", "#AA0"); colours.set("purple", "#A0F"); colours.set("gray", "#777"); - + // default font font = Fonts::getDefaultFont().withHeight(15); @@ -922,11 +917,12 @@ class MarkupDisplayComponent : public Component { viewport.setScrollOnDragMode(Viewport::ScrollOnDragMode::nonHover); } - void paint(Graphics& g) override { + void paint(Graphics& g) override + { g.setColour(findColour(PlugDataColour::canvasBackgroundColourId)); g.fillRoundedRectangle(getLocalBounds().toFloat(), Corners::windowCornerRadius); } - + // clear the background void resized() override { diff --git a/Source/Components/ObjectDragAndDrop.h b/Source/Components/ObjectDragAndDrop.h index 07b4e5e037..8c0f03c07e 100644 --- a/Source/Components/ObjectDragAndDrop.h +++ b/Source/Components/ObjectDragAndDrop.h @@ -1,14 +1,17 @@ #pragma once #include -//#include "Utility/ZoomableDragAndDropContainer.h" +// #include "Utility/ZoomableDragAndDropContainer.h" #include "Utility/OfflineObjectRenderer.h" #include "../PluginEditor.h" #include "Canvas.h" class ObjectDragAndDrop : public Component { public: - ObjectDragAndDrop(PluginEditor* e) : editor(e) { } + ObjectDragAndDrop(PluginEditor* e) + : editor(e) + { + } virtual String getObjectString() = 0; @@ -92,7 +95,8 @@ class ObjectClickAndDrop : public Component Canvas* canvas = nullptr; public: - ObjectClickAndDrop(ObjectDragAndDrop* target) : editor(target->editor) + ObjectClickAndDrop(ObjectDragAndDrop* target) + : editor(target->editor) { objectString = target->getObjectString(); objectName = target->getPatchStringName(); @@ -140,7 +144,7 @@ class ObjectClickAndDrop : public Component auto screenPos = Desktop::getMousePosition(); auto mousePosition = Point(); Component* underMouse; - + mousePosition = screenPos; underMouse = editor->getComponentAt(editor->getLocalPoint(nullptr, screenPos)); @@ -160,7 +164,7 @@ class ObjectClickAndDrop : public Component } else { scale = 1.0f; } - + if (foundCanvas && (foundCanvas != canvas)) { canvas = foundCanvas; editor->getTabComponent().setActiveSplit(canvas); diff --git a/Source/Components/PropertiesPanel.h b/Source/Components/PropertiesPanel.h index b31d0b715b..33c234f35e 100644 --- a/Source/Components/PropertiesPanel.h +++ b/Source/Components/PropertiesPanel.h @@ -336,7 +336,6 @@ class PropertiesPanel : public Component { addAndMakeVisible(comboBox); lookAndFeelChanged(); - } PropertiesPanelProperty* createCopy() override @@ -407,7 +406,7 @@ class PropertiesPanel : public Component { void lookAndFeelChanged() override { - for (auto& property : properties){ + for (auto& property : properties) { property->setColour(ComboBox::textColourId, findColour(PlugDataColour::panelTextColourId)); } } @@ -445,14 +444,16 @@ class PropertiesPanel : public Component { BoolComponent(String const& propertyName, Value& value, StringArray options) : PropertiesPanelProperty(propertyName) , textOptions(std::move(options)) - , toggleStateValue(value) { + , toggleStateValue(value) + { init(); } // Also allow creating it without passing in a Value, makes it easier to derive from this class for custom bool components BoolComponent(String const& propertyName, StringArray options) : PropertiesPanelProperty(propertyName) - , textOptions(std::move(options)) { + , textOptions(std::move(options)) + { init(); } @@ -460,7 +461,8 @@ class PropertiesPanel : public Component { // We need this constructor sometimes to prevent feedback caused by the initial value being set after the listener is attached BoolComponent(String const& propertyName, bool initialValue, StringArray options) : PropertiesPanelProperty(propertyName) - , textOptions(std::move(options)) { + , textOptions(std::move(options)) + { toggleStateValue = initialValue; init(); } @@ -472,7 +474,8 @@ class PropertiesPanel : public Component { lookAndFeelChanged(); } - ~BoolComponent() { + ~BoolComponent() + { toggleStateValue.removeListener(this); } @@ -793,8 +796,10 @@ class PropertiesPanel : public Component { draggableNumber->getTextValue().referTo(property); draggableNumber->setFont(draggableNumber->getFont().withHeight(14)); draggableNumber->setEditableOnClick(true); - if(minimum != 0.0f) draggableNumber->setMinimum(minimum); - if(maximum != 0.0f) draggableNumber->setMaximum(maximum); + if (minimum != 0.0f) + draggableNumber->setMinimum(minimum); + if (maximum != 0.0f) + draggableNumber->setMaximum(maximum); if (onInteractionFn) draggableNumber->onInteraction = onInteractionFn; diff --git a/Source/Components/SuggestionComponent.h b/Source/Components/SuggestionComponent.h index 8f89cb2d8f..ff5b4493c6 100644 --- a/Source/Components/SuggestionComponent.h +++ b/Source/Components/SuggestionComponent.h @@ -12,23 +12,24 @@ #include "Utility/NanoVGGraphicsContext.h" #include "Components/BouncingViewport.h" -extern "C" -{ -int is_gem_object(const char* sym); +extern "C" { +int is_gem_object(char const* sym); } // Component that sits on top of a TextEditor and will draw auto-complete suggestions over it class AutoCompleteComponent - : public Component, public NVGComponent + : public Component + , public NVGComponent , public ComponentListener { String suggestion; Canvas* cnv; Component::SafePointer editor; std::unique_ptr nvgCtx; - + public: AutoCompleteComponent(TextEditor* e, Canvas* c) - : NVGComponent(this), cnv(c) + : NVGComponent(this) + , cnv(c) , editor(e) { setAlwaysOnTop(true); @@ -81,7 +82,7 @@ class AutoCompleteComponent return; auto editorText = editor->getText(); - + if (editorText.startsWith(suggestionText)) { suggestion = ""; repaint(); @@ -106,12 +107,13 @@ class AutoCompleteComponent suggestion = suggestion.upToFirstOccurrenceOf(" ", false, false); repaint(); } - + void render(NVGcontext* nvg) override { nvgSave(nvg); nvgTranslate(nvg, getX(), getY()); - if(!nvgCtx || nvgCtx->getContext() != nvg) nvgCtx = std::make_unique(nvg); + if (!nvgCtx || nvgCtx->getContext() != nvg) + nvgCtx = std::make_unique(nvg); Graphics g(*nvgCtx); { paintEntireComponent(g, true); @@ -119,7 +121,6 @@ class AutoCompleteComponent nvgRestore(nvg); } - private: bool shouldAutocomplete = true; String stashedText; @@ -158,14 +159,13 @@ class SuggestionComponent : public Component , public ComponentListener { class Suggestion : public TextButton { - - enum ObjectType - { + + enum ObjectType { Data = 0, Signal = 1, Gem = 2 }; - + ObjectType type; String objectDescription; @@ -187,9 +187,8 @@ class SuggestionComponent : public Component objectDescription = description; setButtonText(name); type = name.contains("~") ? Signal : Data; - - if(!type && is_gem_object(name.toRawUTF8())) - { + + if (!type && is_gem_object(name.toRawUTF8())) { type = Gem; } @@ -208,7 +207,7 @@ class SuggestionComponent : public Component void paint(Graphics& g) override { auto scrollbarIndent = parent->port->canScrollVertically() ? 6 : 0; - + auto backgroundColour = findColour(getToggleState() ? PlugDataColour::popupMenuActiveBackgroundColourId : PlugDataColour::popupMenuBackgroundColourId); auto buttonArea = getLocalBounds().withTrimmedRight((parent->canBeTransparent() ? 42 : 2) + scrollbarIndent).toFloat().reduced(4, 1); @@ -238,26 +237,23 @@ class SuggestionComponent : public Component if (drawIcon) { Colour iconColour; String iconText; - if(type == Data) { + if (type == Data) { iconColour = findColour(PlugDataColour::dataColourId); iconText = "pd"; - } - else if(type == Signal) { + } else if (type == Signal) { iconColour = findColour(PlugDataColour::signalColourId); iconText = "~"; - } - else if (type == Gem) { + } else if (type == Gem) { iconColour = findColour(PlugDataColour::gemColourId); iconText = "g"; } g.setColour(iconColour); - + auto iconbound = getLocalBounds().reduced(4); iconbound.setWidth(getHeight() - 8); iconbound.translate(4, 0); g.fillRoundedRectangle(iconbound.toFloat(), Corners::defaultCornerRadius); - Fonts::drawFittedText(g, iconText, iconbound.reduced(1), Colours::white, 1, 1.0f, type ? 12 : 10, Justification::centred); } } @@ -297,7 +293,7 @@ class SuggestionComponent : public Component constrainer.setSizeLimits(150, 120, 500, 400); setSize(310 + (2 * windowMargin), 140 + (2 * windowMargin)); - //resizer.setAllowHostManagedResize(false); + // resizer.setAllowHostManagedResize(false); addAndMakeVisible(resizer); setInterceptsMouseClicks(true, true); @@ -309,10 +305,11 @@ class SuggestionComponent : public Component { buttons.clear(); } - + void renderAutocompletion(NVGcontext* nvg) { - if(autoCompleteComponent) autoCompleteComponent->render(nvg); + if (autoCompleteComponent) + autoCompleteComponent->render(nvg); } void createCalloutBox(Object* object, TextEditor* editor) @@ -665,47 +662,47 @@ class SuggestionComponent : public Component state = ShowingArguments; auto name = currentText.upToFirstOccurrenceOf(" ", false, false); auto objectInfo = library->getObjectInfo(name); - if(objectInfo.isValid()) { + if (objectInfo.isValid()) { auto found = objectInfo.getChildWithName("arguments").createCopy(); for (auto flag : objectInfo.getChildWithName("flags")) { auto flagCopy = flag.createCopy(); auto name = flagCopy.getProperty("name").toString().trim(); - + if (!name.startsWith("-")) name = "-" + name; - + flagCopy.setProperty("type", name, nullptr); found.appendChild(flagCopy, nullptr); } - + numOptions = std::min(buttons.size(), found.getNumChildren()); for (int i = 0; i < numOptions; i++) { auto type = found.getChild(i).getProperty("type").toString(); auto description = found.getChild(i).getProperty("description").toString(); auto def = found.getChild(i).getProperty("default").toString(); - + if (def.isNotEmpty()) description += " (default: " + def + ")"; - + buttons[i]->setText(type, description, false); buttons[i]->setInterceptsMouseClicks(false, false); buttons[i]->setToggleState(false, dontSendNotification); } - + for (int i = numOptions; i < buttons.size(); i++) { buttons[i]->setText("", "", false); buttons[i]->setToggleState(false, dontSendNotification); } - + setVisible(numOptions); - + if (autoCompleteComponent) { autoCompleteComponent->enableAutocomplete(false); currentObject->updateBounds(); } - + resized(); - + return; } } @@ -766,8 +763,9 @@ class SuggestionComponent : public Component auto& name = suggestions[i]; auto info = library->getObjectInfo(name); - if(!info.isValid()) continue; - + if (!info.isValid()) + continue; + auto description = info.getProperty("description").toString(); buttons[i]->setText(name, description, true); @@ -821,8 +819,9 @@ class SuggestionComponent : public Component } library->getExtraSuggestions(found.size(), currentText, [_this = SafePointer(this), this, filterObjects, applySuggestionsToButtons, found, currentText](StringArray s) mutable { - if(!_this) return; - + if (!_this) + return; + filterObjects(s); // This means the extra suggestions have returned too late to still be relevant @@ -854,7 +853,7 @@ class SuggestionComponent : public Component continue; auto info = cnv->pd->objectLibrary->getObjectInfo(objectName); - if(info.isValid()) { + if (info.isValid()) { auto methods = info.getChildWithName("methods"); objects.add({ objectName, methods, distance }); } @@ -914,7 +913,7 @@ class SuggestionComponent : public Component return nearbyMethods; } - + void deselectAll() { for (auto* button : buttons) { diff --git a/Source/Components/TouchSelectionHelper.h b/Source/Components/TouchSelectionHelper.h index b4fc14c11e..5b325c18a2 100644 --- a/Source/Components/TouchSelectionHelper.h +++ b/Source/Components/TouchSelectionHelper.h @@ -12,19 +12,21 @@ #include "PluginEditor.h" #include "Objects/ObjectBase.h" -class TouchSelectionHelper : public Component, public NVGComponent { +class TouchSelectionHelper : public Component + , public NVGComponent { PluginEditor* editor; public: TouchSelectionHelper(PluginEditor* e) - : NVGComponent(this), editor(e) + : NVGComponent(this) + , editor(e) { addAndMakeVisible(actionButtons.add(new MainToolbarButton(Icons::ExportState))); // This icon doubles as a "open" icon in the mobile app addAndMakeVisible(actionButtons.add(new MainToolbarButton(Icons::Help))); addAndMakeVisible(actionButtons.add(new MainToolbarButton(Icons::Trash))); addAndMakeVisible(actionButtons.add(new MainToolbarButton(Icons::More))); - + setCachedComponentImage(new NVGSurface::InvalidationListener(e->nvgSurface, this)); actionButtons[0]->onClick = [this]() { diff --git a/Source/Components/WelcomePanel.h b/Source/Components/WelcomePanel.h index 7f1e390da0..7fb90ae21e 100644 --- a/Source/Components/WelcomePanel.h +++ b/Source/Components/WelcomePanel.h @@ -10,143 +10,143 @@ #include "Utility/NanoVGGraphicsContext.h" #include "Components/BouncingViewport.h" -class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater { +class WelcomePanel : public Component + , public NVGComponent + , public AsyncUpdater { - class WelcomePanelTile : public Component - { + class WelcomePanelTile : public Component { float snapshotScale; bool isHovered = false; String tileName, tileSubtitle; std::unique_ptr snapshot = nullptr; NVGImage titleImage, subtitleImage; - + public: bool isFavourited; - std::function onClick = [](){}; + std::function onClick = []() {}; std::function onFavourite = nullptr; - + WelcomePanelTile(String name, String subtitle, String svgImage, Colour iconColour, float scale, bool favourited) - : snapshotScale(scale), tileName(name), tileSubtitle(subtitle), isFavourited(favourited) + : snapshotScale(scale) + , tileName(name) + , tileSubtitle(subtitle) + , isFavourited(favourited) { snapshot = Drawable::createFromImageData(svgImage.toRawUTF8(), svgImage.getNumBytesAsUTF8()); - if(snapshot) { - snapshot->replaceColour (Colours::black, iconColour); + if (snapshot) { + snapshot->replaceColour(Colours::black, iconColour); } resized(); } - + void paint(Graphics& g) override { auto bounds = getLocalBounds().reduced(12); - + Path tilePath; tilePath.addRoundedRectangle(bounds.getX() + 1, bounds.getY() + 1, bounds.getWidth() - 2, bounds.getHeight() - 2, Corners::largeCornerRadius); - - StackShadow::renderDropShadow(g, tilePath, Colour(0, 0, 0).withAlpha(0.08f), 6, {0, 1}); + + StackShadow::renderDropShadow(g, tilePath, Colour(0, 0, 0).withAlpha(0.08f), 6, { 0, 1 }); g.setColour(findColour(PlugDataColour::canvasBackgroundColourId)); g.fillPath(tilePath); - - if(snapshot) { + + if (snapshot) { snapshot->drawAt(g, 0, 0, 1.0f); } - + Path textAreaPath; textAreaPath.addRoundedRectangle(bounds.getX(), bounds.getHeight() - 32, bounds.getWidth(), 44, Corners::largeCornerRadius, Corners::largeCornerRadius, false, false, true, true); - + auto hoverColour = findColour(PlugDataColour::toolbarHoverColourId).interpolatedWith(findColour(PlugDataColour::toolbarBackgroundColourId), 0.5f); g.setColour(isHovered ? hoverColour : findColour(PlugDataColour::toolbarBackgroundColourId)); g.fillPath(textAreaPath); - + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.strokePath(tilePath, PathStrokeType(1.0f)); - + auto* nvg = dynamic_cast(g.getInternalContext()).getContext(); - + auto textWidth = bounds.getWidth() - 8; - if(titleImage.needsUpdate(textWidth * 2, 24 * 2) || subtitleImage.needsUpdate(textWidth * 2, 16 * 2)) - { + if (titleImage.needsUpdate(textWidth * 2, 24 * 2) || subtitleImage.needsUpdate(textWidth * 2, 16 * 2)) { auto textColour = findColour(PlugDataColour::panelTextColourId); - titleImage = NVGImage(nvg, textWidth * 2, 24 * 2, [this, textColour, textWidth](Graphics& g){ + titleImage = NVGImage(nvg, textWidth * 2, 24 * 2, [this, textColour, textWidth](Graphics& g) { g.addTransform(AffineTransform::scale(2.0f, 2.0f)); g.setColour(textColour); g.setFont(Fonts::getBoldFont().withHeight(14)); g.drawText(tileName, Rectangle(0, 0, textWidth, 24), Justification::centredLeft, true); }); - - subtitleImage = NVGImage(nvg, textWidth * 2, 16 * 2, [this, textColour, textWidth](Graphics& g){ + + subtitleImage = NVGImage(nvg, textWidth * 2, 16 * 2, [this, textColour, textWidth](Graphics& g) { g.addTransform(AffineTransform::scale(2.0f, 2.0f)); g.setColour(textColour.withAlpha(0.75f)); g.setFont(Fonts::getDefaultFont().withHeight(13.5f)); g.drawText(tileSubtitle, Rectangle(0, 0, textWidth, 16), Justification::centredLeft, true); }); } - + nvgSave(nvg); nvgTranslate(nvg, 22, bounds.getHeight() - 30); titleImage.render(nvg, Rectangle(0, 0, bounds.getWidth() - 8, 24)); nvgTranslate(nvg, 0, 20); subtitleImage.render(nvg, Rectangle(0, 0, bounds.getWidth() - 8, 16)); nvgRestore(nvg); - - if(onFavourite) - { + + if (onFavourite) { auto favouriteIconBounds = getHeartIconBounds(); nvgFontFace(nvg, "icon_font-Regular"); - - if(isFavourited) { + + if (isFavourited) { nvgFillColor(nvg, nvgRGBA(250, 50, 40, 200)); nvgText(nvg, favouriteIconBounds.getX(), favouriteIconBounds.getY() + 14, Icons::HeartFilled.toRawUTF8(), nullptr); - } - else if(isMouseOver()) - { + } else if (isMouseOver()) { nvgFillColor(nvg, NVGComponent::convertColour(findColour(PlugDataColour::panelTextColourId))); nvgText(nvg, favouriteIconBounds.getX(), favouriteIconBounds.getY() + 14, Icons::HeartStroked.toRawUTF8(), nullptr); } } } - + Rectangle getHeartIconBounds() { return Rectangle(20, getHeight() - 80, 16, 16); } - - void mouseEnter(const MouseEvent& e) override + + void mouseEnter(MouseEvent const& e) override { isHovered = true; repaint(); } - - void mouseExit(const MouseEvent& e) override + + void mouseExit(MouseEvent const& e) override { isHovered = false; repaint(); } - - void mouseUp(const MouseEvent& e) override + + void mouseUp(MouseEvent const& e) override { - if(onFavourite && getHeartIconBounds().contains(e.x, e.y)) - { + if (onFavourite && getHeartIconBounds().contains(e.x, e.y)) { isFavourited = !isFavourited; onFavourite(isFavourited); repaint(); - } - else { + } else { onClick(); } } - + void resized() override { - if(snapshot) { + if (snapshot) { auto bounds = getLocalBounds().reduced(12).withTrimmedBottom(44); snapshot->setTransformToFit(bounds.withSizeKeepingCentre(bounds.getWidth() * snapshotScale, bounds.getHeight() * snapshotScale).toFloat(), RectanglePlacement::centred); } } }; - + public: - WelcomePanel(PluginEditor* pluginEditor) : NVGComponent(this), editor(pluginEditor) + WelcomePanel(PluginEditor* pluginEditor) + : NVGComponent(this) + , editor(pluginEditor) { recentlyOpenedViewport.setViewedComponent(&recentlyOpenedComponent, false); recentlyOpenedViewport.setScrollBarsShown(true, false, false, false); @@ -161,22 +161,24 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater { auto bounds = getLocalBounds().reduced(24).withTrimmedTop(36); auto rowBounds = bounds.removeFromTop(160); - - const int desiredTileWidth = 190; - const int tileSpacing = 4; + + int const desiredTileWidth = 190; + int const tileSpacing = 4; int totalWidth = bounds.getWidth(); // Calculate the number of columns that can fit in the total width int numColumns = std::max(1, totalWidth / (desiredTileWidth + tileSpacing)); // Adjust the tile width to fit within the available width int actualTileWidth = (totalWidth - (numColumns - 1) * tileSpacing) / numColumns; - - if(newPatchTile) newPatchTile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); + + if (newPatchTile) + newPatchTile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); rowBounds.removeFromLeft(4); - if(openPatchTile) openPatchTile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); - + if (openPatchTile) + openPatchTile->setBounds(rowBounds.removeFromLeft(actualTileWidth)); + bounds.removeFromTop(16); - + auto viewPos = recentlyOpenedViewport.getViewPosition(); recentlyOpenedViewport.setBounds(getLocalBounds().withTrimmedTop(260)); @@ -188,9 +190,8 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater // Start positioning the tiles rowBounds = scrollable.removeFromTop(160); - for (auto* tile : tiles) - { - if(tile->isFavourited) { + for (auto* tile : tiles) { + if (tile->isFavourited) { if (rowBounds.getWidth() < actualTileWidth) { rowBounds = scrollable.removeFromTop(160); } @@ -198,10 +199,9 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater rowBounds.removeFromLeft(tileSpacing); } } - - for (auto* tile : tiles) - { - if(!tile->isFavourited) { + + for (auto* tile : tiles) { + if (!tile->isFavourited) { if (rowBounds.getWidth() < actualTileWidth) { rowBounds = scrollable.removeFromTop(160); } @@ -211,23 +211,23 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater } recentlyOpenedViewport.setViewPosition(viewPos); } - + void handleAsyncUpdate() override { newPatchTile = std::make_unique("New Patch", "Create a new empty patch", newIcon, findColour(PlugDataColour::panelTextColourId), 0.33f, false); openPatchTile = std::make_unique("Open Patch", "Browse for a patch to open", openIcon, findColour(PlugDataColour::panelTextColourId), 0.33f, false); - + newPatchTile->onClick = [this]() { editor->getTabComponent().newPatch(); }; - openPatchTile->onClick = [this](){ editor->getTabComponent().openPatch(); }; - + openPatchTile->onClick = [this]() { editor->getTabComponent().openPatch(); }; + addAndMakeVisible(*newPatchTile); addAndMakeVisible(*openPatchTile); - + tiles.clear(); auto settingsTree = SettingsFile::getInstance()->getValueTree(); auto recentlyOpenedTree = settingsTree.getChildWithName("RecentlyOpened"); - + if (recentlyOpenedTree.isValid()) { // Place favourited patches at the top for (int i = 0; i < recentlyOpenedTree.getNumChildren(); i++) { @@ -236,21 +236,23 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater auto silhoutteSvg = subTree.getProperty("Snapshot").toString(); auto favourited = subTree.hasProperty("Pinned") && static_cast(subTree.getProperty("Pinned")); auto snapshotColour = LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.3f); - - if(silhoutteSvg.isEmpty() && patchFile.existsAsFile()) - { + + if (silhoutteSvg.isEmpty() && patchFile.existsAsFile()) { silhoutteSvg = OfflineObjectRenderer::patchToSVGFast(patchFile.loadFileAsString()); } - + auto openTime = Time(static_cast(subTree.getProperty("Time"))); auto diff = Time::getCurrentTime() - openTime; String date; - if(diff.inDays() == 0) date = "Today"; - else if(diff.inDays() == 1) date = "Yesterday"; - else date = openTime.toString(true, false); + if (diff.inDays() == 0) + date = "Today"; + else if (diff.inDays() == 1) + date = "Yesterday"; + else + date = openTime.toString(true, false); String time = openTime.toString(false, true, false, true); String timeDescription = date + ", " + time; - + auto* tile = tiles.add(new WelcomePanelTile(patchFile.getFileName(), timeDescription, silhoutteSvg, snapshotColour, 1.0f, favourited)); tile->onClick = [this, patchFile]() mutable { editor->autosave->checkForMoreRecentAutosave(patchFile, editor, [this, patchFile]() { @@ -262,7 +264,7 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater tile->onFavourite = [this, path = subTree.getProperty("Path")](bool shouldBeFavourite) mutable { auto settingsTree = SettingsFile::getInstance()->getValueTree(); auto recentlyOpenedTree = settingsTree.getChildWithName("RecentlyOpened"); - + // Settings file could be reloaded, we can't assume the old recently opened tree is still valid! // So look up the entry by file path auto subTree = recentlyOpenedTree.getChildWithProperty("Path", path); @@ -276,7 +278,6 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater resized(); } - void show() { triggerAsyncUpdate(); @@ -287,30 +288,31 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater { setVisible(false); } - + void render(NVGcontext* nvg) override { - if(!nvgContext || nvgContext->getContext() != nvg) nvgContext = std::make_unique(nvg); - + if (!nvgContext || nvgContext->getContext() != nvg) + nvgContext = std::make_unique(nvg); + nvgFillColor(nvg, convertColour(findColour(PlugDataColour::panelBackgroundColourId))); nvgFillRect(nvg, 0, 0, getWidth(), getHeight()); - + Graphics g(*nvgContext); g.reduceClipRegion(editor->nvgSurface.getInvalidArea()); paintEntireComponent(g, false); - + auto gradient = nvgLinearGradient(nvg, 0, recentlyOpenedViewport.getY(), 0, recentlyOpenedViewport.getY() + 20, convertColour(findColour(PlugDataColour::panelBackgroundColourId)), nvgRGBAf(1, 1, 1, 0)); - + nvgFillPaint(nvg, gradient); nvgFillRect(nvg, recentlyOpenedViewport.getX() + 8, recentlyOpenedViewport.getY(), recentlyOpenedViewport.getWidth() - 16, 20); - + nvgBeginPath(nvg); nvgFillColor(nvg, findNVGColour(PlugDataColour::panelTextColourId)); nvgTextAlign(nvg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); nvgFontSize(nvg, 30); nvgFontFace(nvg, "Inter-Bold"); nvgText(nvg, 35, 38, "Welcome to plugdata", nullptr); - + nvgBeginPath(nvg); nvgFontSize(nvg, 24); nvgText(nvg, 35, 244, "Recently Opened", nullptr); @@ -321,29 +323,29 @@ class WelcomePanel : public Component, public NVGComponent, public AsyncUpdater triggerAsyncUpdate(); } - static inline const String newIcon = "\n" - "\n" - "\n" - " \n" - "\n"; - - static inline const String openIcon = "\n" - "\n" - "\n" - " \n" - "\n"; + static inline String const newIcon = "\n" + "\n" + "\n" + " \n" + "\n"; + + static inline String const openIcon = "\n" + "\n" + "\n" + " \n" + "\n"; std::unique_ptr newPatchTile; std::unique_ptr openPatchTile; - + Component recentlyOpenedComponent; BouncingViewport recentlyOpenedViewport; - + std::unique_ptr nvgContext = nullptr; - + OwnedArray tiles; PluginEditor* editor; }; diff --git a/Source/Connection.cpp b/Source/Connection.cpp index 15c936782d..2215006d51 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -33,7 +33,7 @@ Connection::Connection(Canvas* parent, Iolet* s, Iolet* e, t_outconnect* oc) , ptr(parent->pd) { cnv->selectedComponents.addChangeListener(this); - + locked.referTo(parent->locked); presentationMode.referTo(parent->presentationMode); @@ -44,15 +44,13 @@ Connection::Connection(Canvas* parent, Iolet* s, Iolet* e, t_outconnect* oc) jassertfalse; return; } - + cableType = DataCable; - - if(outlet && outlet->isSignal) - { + + if (outlet && outlet->isSignal) { cableType = SignalCable; } - if(outlet && outlet->isGemState) - { + if (outlet && outlet->isGemState) { cableType = GemCable; } @@ -83,8 +81,8 @@ Connection::Connection(Canvas* parent, Iolet* s, Iolet* e, t_outconnect* oc) // Listen to changes at iolets outobj->addComponentListener(this); inobj->addComponentListener(this); - //outlet->addComponentListener(this); - //inlet->addComponentListener(this); + // outlet->addComponentListener(this); + // inlet->addComponentListener(this); setInterceptsMouseClicks(true, true); @@ -116,9 +114,10 @@ Connection::~Connection() if (inobj) { inobj->removeComponentListener(this); } - + auto* nvg = cnv->editor->nvgSurface.getRawContext(); - if(nvg && cacheId >= 0) nvgDeletePath(nvg, cacheId); + if (nvg && cacheId >= 0) + nvgDeletePath(nvg, cacheId); if (nvg && cacheId >= 0 && cableType == SignalCable) { nvgDeletePath(nvg, std::numeric_limits::max() - cacheId); } @@ -137,11 +136,11 @@ void Connection::lookAndFeelChanged() signalColour = convertColour(findColour(PlugDataColour::signalColourId)); handleColour = outlet->isSignal ? dataColour : signalColour; shadowColour = convertColour(findColour(PlugDataColour::canvasBackgroundColourId).contrasting(0.06f).withAlpha(0.24f)); - outlineColour = convertColour(findColour(PlugDataColour::objectOutlineColourId)); + outlineColour = convertColour(findColour(PlugDataColour::objectOutlineColourId)); gemColour = convertColour(findColour(PlugDataColour::gemColourId)); textColour = convertColour(findColour(PlugDataColour::objectSelectedOutlineColourId).contrasting()); - + updatePath(); repaint(); } @@ -150,27 +149,23 @@ void Connection::render(NVGcontext* nvg) { connectionColour = baseColour; if (isSelected() || isHovering) { - if(outlet->isSignal) - { + if (outlet->isSignal) { connectionColour = signalColour; - } - else if(outlet->isGemState) - { + } else if (outlet->isGemState) { connectionColour = gemColour; - } - else { + } else { connectionColour = dataColour; } } - + if (isHovering) { connectionColour.r *= 1.2f; connectionColour.g *= 1.2f; connectionColour.b *= 1.2f; } - + bool useThinConnection = PlugDataLook::getUseThinConnections(); - + nvgSave(nvg); nvgTranslate(nvg, getX(), getY()); @@ -197,39 +192,36 @@ void Connection::render(NVGcontext* nvg) nvgStrokePaint(nvg, nvgDoubleStroke(nvg, connectionColour, shadowColour)); nvgStrokeWidth(nvg, useThinConnection ? 2.5f : 4.0f); - if(!cachedIsValid) nvgDeletePath(nvg, cacheId); - if(nvgLoadPath(nvg, cacheId)) - { + if (!cachedIsValid) + nvgDeletePath(nvg, cacheId); + if (nvgLoadPath(nvg, cacheId)) { nvgStroke(nvg); - } - else - { + } else { auto pathFromOrigin = getPath(); pathFromOrigin.applyTransform(AffineTransform::translation(-getX(), -getY())); - + setJUCEPath(nvg, pathFromOrigin); nvgStroke(nvg); cacheId = nvgSavePath(nvg, cacheId); } - + if (cableType == SignalCable) { auto dashColor = shadowColour; dashColor.a = 1.0f; dashColor.r *= 0.4f; dashColor.g *= 0.4f; dashColor.b *= 0.4f; - + nvgStrokeColor(nvg, dashColor); nvgLineStyle(nvg, NVG_LINE_DASHED); nvgDashLength(nvg, numSignalChannels <= 1 ? 5.0f : 3.5f); nvgStrokeWidth(nvg, useThinConnection ? 1.5f : 2.0f); - - if(!cachedIsValid) nvgDeletePath(nvg, std::numeric_limits::max() - cacheId); - if(nvgLoadPath(nvg, std::numeric_limits::max() - cacheId)) - { + + if (!cachedIsValid) + nvgDeletePath(nvg, std::numeric_limits::max() - cacheId); + if (nvgLoadPath(nvg, std::numeric_limits::max() - cacheId)) { nvgStroke(nvg); - } - else { + } else { auto pathFromOrigin = getPath(); pathFromOrigin.applyTransform(AffineTransform::translation(-getX(), -getY())); setJUCEPath(nvg, pathFromOrigin); @@ -237,22 +229,22 @@ void Connection::render(NVGcontext* nvg) nvgSavePath(nvg, std::numeric_limits::max() - cacheId); } } - + nvgRestore(nvg); cachedIsValid = true; auto mousePos = cnv->getLastMousePosition(); - + if (isSelected() && isHovering) { auto expandedStartHandle = startReconnectHandle.contains(mousePos.toFloat()) ? startReconnectHandle.expanded(3.0f) : startReconnectHandle; auto expandedEndHandle = endReconnectHandle.contains(mousePos.toFloat()) ? endReconnectHandle.expanded(3.0f) : endReconnectHandle; - + nvgFillColor(nvg, handleColour); nvgBeginPath(nvg); nvgCircle(nvg, expandedStartHandle.getCentreX(), expandedStartHandle.getCentreY(), expandedStartHandle.getWidth() / 2); nvgFill(nvg); - + nvgBeginPath(nvg); nvgCircle(nvg, expandedEndHandle.getCentreX(), expandedEndHandle.getCentreY(), expandedEndHandle.getWidth() / 2); nvgFill(nvg); @@ -271,10 +263,10 @@ void Connection::render(NVGcontext* nvg) // b // setup arrow parameters - const float arrowWidth = 8.0f; - const float arrowLength = 12.0f; + float const arrowWidth = 8.0f; + float const arrowLength = 12.0f; - auto renderArrow = [this, nvg, arrowLength, arrowWidth](Path& path, float connectionLength){ + auto renderArrow = [this, nvg, arrowLength, arrowWidth](Path& path, float connectionLength) { // get the center point of the connection path const auto arrowCenter = connectionLength * 0.5f; @@ -299,14 +291,14 @@ void Connection::render(NVGcontext* nvg) nvgStroke(nvg); }; - //TODO: refactor this outside of the render function + // TODO: refactor this outside of the render function if (cnv->shouldShowConnectionDirection()) { if (isSegmented()) { for (int i = 1; i < currentPlan.size(); i++) { - const auto pathLine = Line(currentPlan[i - 1], currentPlan[i]); - const auto length = pathLine.getLength(); + auto const pathLine = Line(currentPlan[i - 1], currentPlan[i]); + auto const length = pathLine.getLength(); // don't show arrow if start or end segment is too small, to give room for the reconnect handle - const auto isStartOrEnd = (i == 1) || (i == currentPlan.size() - 1); + auto const isStartOrEnd = (i == 1) || (i == currentPlan.size() - 1); if (length > arrowLength * (isStartOrEnd ? 3 : 2)) { Path segmentedPath; segmentedPath.addLineSegment(pathLine, 0.0f); @@ -317,7 +309,7 @@ void Connection::render(NVGcontext* nvg) } else { auto connectionPath = getPath(); connectionPath.applyTransform(AffineTransform::translation(-getX(), -getY())); - const auto connectionLength = connectionPath.getLength(); + auto const connectionLength = connectionPath.getLength(); if (connectionLength > arrowLength * 2) { renderArrow(connectionPath, connectionLength); @@ -336,9 +328,9 @@ void Connection::renderConnectionOrder(NVGcontext* nvg) nvgBeginPath(nvg); nvgStrokeColor(nvg, outlineColour); nvgFillColor(nvg, connectionColour); - const auto radius = 7.0f; - const auto diameter = radius * 2.0f; - const auto circleTopLeft = pos - Point(radius, radius); + auto const radius = 7.0f; + auto const diameter = radius * 2.0f; + auto const circleTopLeft = pos - Point(radius, radius); nvgRoundedRect(nvg, circleTopLeft.getX(), circleTopLeft.getY(), diameter, diameter, radius); nvgStrokeWidth(nvg, 1.0f); nvgFill(nvg); @@ -449,7 +441,7 @@ bool Connection::hitTest(int x, int y) Point position = Point(static_cast(x), static_cast(y)) + getPosition().toFloat(); Point nearestPoint; - + auto path = getPath(); path.getNearestPoint(position, nearestPoint); @@ -547,15 +539,15 @@ void Connection::mouseMove(MouseEvent const& e) void Connection::timerCallback(int ID) { - switch(ID){ - case StopAnimation: - stopTimer(Animation); - stopTimer(StopAnimation); + switch (ID) { + case StopAnimation: + stopTimer(Animation); + stopTimer(StopAnimation); break; - case Animation: - animate(); + case Animation: + animate(); break; - default: + default: break; } } @@ -657,7 +649,7 @@ void Connection::mouseDown(MouseEvent const& e) void Connection::mouseDrag(MouseEvent const& e) { cnv->editor->connectionMessageDisplay->setConnection(nullptr); - + if (selectedFlag && startReconnectHandle.contains(e.getMouseDownPosition().toFloat().translated(getX(), getY())) && e.getDistanceFromDragStart() > 6) { cnv->connectingWithDrag = true; reconnect(inlet); @@ -796,21 +788,21 @@ void Connection::componentMovedOrResized(Component& component, bool wasMoved, bo for (auto& point : currentPlan) { point += pointOffset; } - + auto translation = AffineTransform::translation(pointOffset.x, pointOffset.y); - + auto offsetPath = getPath(); offsetPath.applyTransform(translation); setPath(offsetPath); - + clipRegion.transformAll(translation); return; } - + previousPStart = pstart; cachedIsValid = false; - + if (currentPlan.size() <= 2) { updatePath(); repaint(); @@ -909,7 +901,7 @@ int Connection::getSignalData(t_float* output, int maxChannels) { if (auto oc = ptr.get()) { if (auto* signal = outconnect_get_signal(oc.get())) { - auto numChannels = std::min(signal->s_nchans, maxChannels-1); + auto numChannels = std::min(signal->s_nchans, maxChannels - 1); auto* samples = signal->s_vec; if (!samples) return 0; @@ -940,11 +932,11 @@ void Connection::updatePath() { if (!outlet || !inlet) return; - + auto pstart = getStartPoint(); auto pend = getEndPoint(); Path toDraw; - + if (!segmented) { toDraw = getNonSegmentedPath(pstart, pend); currentPlan.clear(); @@ -980,13 +972,13 @@ void Connection::updatePath() connectionPath.lineTo(pend); toDraw = connectionPath.createPathWithRoundedCorners(PlugDataLook::getUseStraightConnections() ? 0.0f : 8.0f); } - + setPath(toDraw); previousPStart = pstart; - + clipRegion = RectangleList(); auto pathIter = PathFlatteningIterator(toDraw, AffineTransform(), 12.0f); - while(pathIter.next()) // skip first item, since only the x2/y2 coords of that one are valid (and they will be the x1/y1 of the next item) + while (pathIter.next()) // skip first item, since only the x2/y2 coords of that one are valid (and they will be the x1/y1 of the next item) { auto bounds = Rectangle(Point(pathIter.x1, pathIter.y1), Point(pathIter.x2, pathIter.y2)); clipRegion.add(bounds.expanded(3)); @@ -994,18 +986,18 @@ void Connection::updatePath() startReconnectHandle = Rectangle(5, 5).withCentre(toDraw.getPointAlongPath(8.5f)); endReconnectHandle = Rectangle(5, 5).withCentre(toDraw.getPointAlongPath(jmax(toDraw.getLength() - 8.5f, 9.5f))); - + clipRegion.add(startReconnectHandle.toNearestIntEdges().expanded(4)); clipRegion.add(endReconnectHandle.toNearestIntEdges().expanded(4)); - + cachedIsValid = false; } bool Connection::intersectsRectangle(Rectangle rectToIntersect) { - if(rectToIntersect.contains(getBounds())) + if (rectToIntersect.contains(getBounds())) return true; - + return clipRegion.intersectsRectangle(rectToIntersect); } diff --git a/Source/Connection.h b/Source/Connection.h index 90b2b0dee5..afccf9a653 100644 --- a/Source/Connection.h +++ b/Source/Connection.h @@ -26,10 +26,9 @@ class PathUpdater; class Connection : public DrawablePath , public ComponentListener , public ChangeListener - , public pd::MessageListener + , public pd::MessageListener , public NVGComponent - , public MultiTimer -{ + , public MultiTimer { public: int inIdx; int outIdx; @@ -65,12 +64,12 @@ class Connection : public DrawablePath bool isSegmented() const; void setSegmented(bool segmented); - + bool intersectsRectangle(Rectangle rectToIntersect); - + void render(NVGcontext* nvg) override; void renderConnectionOrder(NVGcontext* nvg); - + void updatePath(); void forceUpdate(); @@ -123,8 +122,8 @@ class Connection : public DrawablePath int getSignalData(t_float* output, int maxChannels); private: - - enum Timer { StopAnimation, Animation }; + enum Timer { StopAnimation, + Animation }; void timerCallback(int ID) override; @@ -133,7 +132,7 @@ class Connection : public DrawablePath int getMultiConnectNumber(); int getNumSignalChannels(); int getNumberOfConnections(); - + void setSelected(bool shouldBeSelected); Array> reconnecting; @@ -142,12 +141,12 @@ class Connection : public DrawablePath bool selectedFlag = false; bool segmented = false; bool isHovering = false; - + PathPlan currentPlan; Value locked; Value presentationMode; - + NVGcolor baseColour; NVGcolor dataColour; NVGcolor signalColour; @@ -158,10 +157,13 @@ class Connection : public DrawablePath NVGcolor connectionColour; NVGcolor textColour; - + RectangleList clipRegion; - - enum CableType { DataCable, GemCable, SignalCable, MultichannelCable }; + + enum CableType { DataCable, + GemCable, + SignalCable, + MultichannelCable }; CableType cableType; Canvas* cnv; @@ -187,7 +189,8 @@ class Connection : public DrawablePath JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Connection) }; -class ConnectionBeingCreated : public DrawablePath, public NVGComponent { +class ConnectionBeingCreated : public DrawablePath + , public NVGComponent { SafePointer iolet; Component* cnv; @@ -198,7 +201,7 @@ class ConnectionBeingCreated : public DrawablePath, public NVGComponent { , cnv(canvas) { setStrokeThickness(5.0f); - + // Only listen for mouse-events on canvas and the original iolet setInterceptsMouseClicks(false, true); cnv->addMouseListener(this, true); @@ -234,11 +237,11 @@ class ConnectionBeingCreated : public DrawablePath, public NVGComponent { auto connectionPath = Connection::getNonSegmentedPath(startPoint.toFloat(), endPoint.toFloat()); setPath(connectionPath); - + repaint(); iolet->repaint(); } - + void render(NVGcontext* nvg) override { auto lineColour = cnv->findColour(PlugDataColour::dataColourId).brighter(0.6f); diff --git a/Source/Constants.h b/Source/Constants.h index 03abf155c7..9c2f4ffcf9 100644 --- a/Source/Constants.h +++ b/Source/Constants.h @@ -82,7 +82,7 @@ struct Icons { inline static String const Eyedropper = "@"; inline static String const HeartFilled = "?"; inline static String const HeartStroked = ">"; - + inline static String const Reset = "'"; inline static String const More = "."; inline static String const MIDI = "`"; @@ -111,8 +111,6 @@ struct Icons { inline static String const AlignBottom = "8"; inline static String const AlignVCentre = "9"; inline static String const AlignVDistribute = "*"; - - // ================== OBJECT ICONS ================== diff --git a/Source/Dialogs/AboutPanel.h b/Source/Dialogs/AboutPanel.h index 1c05beb908..010d8c719c 100644 --- a/Source/Dialogs/AboutPanel.h +++ b/Source/Dialogs/AboutPanel.h @@ -13,40 +13,38 @@ class AboutPanel : public Component { WidePanelButton viewOnGithub = WidePanelButton(Icons::OpenLink); WidePanelButton reportIssue = WidePanelButton(Icons::OpenLink); WidePanelButton sponsor = WidePanelButton(Icons::OpenLink); - + WidePanelButton showCredits = WidePanelButton(Icons::Forward, 15); WidePanelButton showLicense = WidePanelButton(Icons::Forward, 15); - - class CreditsViewport : public BouncingViewport - { + + class CreditsViewport : public BouncingViewport { void paint(Graphics& g) override { g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); g.fillRoundedRectangle(getLocalBounds().toFloat(), Corners::windowCornerRadius); } - + void paintOverChildren(Graphics& g) override { g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); g.fillRoundedRectangle(getLocalBounds().removeFromTop(16).withTrimmedRight(8).toFloat(), Corners::windowCornerRadius); - + // Draw fade for credits viewport g.setGradientFill(ColourGradient::vertical(findColour(PlugDataColour::panelBackgroundColourId), 36, findColour(PlugDataColour::panelBackgroundColourId).withAlpha(0.0f), 48)); g.fillRect(0, 16, getWidth() - 16, 48); } }; - class CreditsPanel : public Component - { - const std::vector> contributors = { - {"Timothy Schoen", "Lead development, UI/UX design"}, - {"Alex Mitchell", "Development, UI/UX design"}, - {"Joshua A.C. Newman", "Community management, logo and identity design"}, - {"Bas de Bruin", "Logo design"}, - {"dreamer", "Hvcc development"}, - {"tomara-x", "Documentation, testing"} + class CreditsPanel : public Component { + std::vector> const contributors = { + { "Timothy Schoen", "Lead development, UI/UX design" }, + { "Alex Mitchell", "Development, UI/UX design" }, + { "Joshua A.C. Newman", "Community management, logo and identity design" }, + { "Bas de Bruin", "Logo design" }, + { "dreamer", "Hvcc development" }, + { "tomara-x", "Documentation, testing" } }; - - const StringArray sponsors = { + + StringArray const sponsors = { "Deskew Technologies", "Nasko", "PowerUser64", @@ -68,80 +66,77 @@ class AboutPanel : public Component { "polarity", "ludnny" }; - + public: CreditsPanel() = default; void paint(Graphics& g) override { auto bounds = getLocalBounds().withTrimmedTop(46).reduced(16, 4); - + Fonts::drawStyledText(g, "Contributors", bounds.getX(), bounds.getY() - 8, bounds.getWidth(), 15.0f, findColour(PlugDataColour::panelTextColourId), Semibold, 15.0f); - + bounds.removeFromTop(16); - + Path firstShadowPath; firstShadowPath.addRoundedRectangle(Rectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), contributors.size() * 48).reduced(4), Corners::largeCornerRadius); StackShadow::renderDropShadow(g, firstShadowPath, Colour(0, 0, 0).withAlpha(0.32f), 8); - - for(int i = 0; i < contributors.size(); i++) - { + + for (int i = 0; i < contributors.size(); i++) { auto rowBounds = bounds.removeFromTop(48); auto first = i == 0; auto last = i == (contributors.size() - 1); auto& [name, role] = contributors[i]; Path outline; outline.addRoundedRectangle(rowBounds.getX(), rowBounds.getY(), rowBounds.getWidth(), rowBounds.getHeight(), Corners::largeCornerRadius, Corners::largeCornerRadius, first, first, last, last); - + g.setColour(findColour(PlugDataColour::panelForegroundColourId)); g.fillPath(outline); g.setColour(findColour(PlugDataColour::outlineColourId)); g.strokePath(outline, PathStrokeType(1)); - + Fonts::drawText(g, name, rowBounds.reduced(12, 2).translated(0, -8), findColour(PlugDataColour::panelTextColourId), 15); Fonts::drawText(g, role, rowBounds.reduced(12, 2).translated(0, 8), findColour(PlugDataColour::panelTextColourId).withAlpha(0.5f), 15); } - + bounds.removeFromTop(24); - + Fonts::drawStyledText(g, "Sponsors", bounds.getX(), bounds.getY() - 8, bounds.getWidth(), 15.0f, findColour(PlugDataColour::panelTextColourId), Semibold, 15.0f); - + bounds.removeFromTop(16); - + Path secondShadowPath; secondShadowPath.addRoundedRectangle(Rectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), sponsors.size() * 32).reduced(4), Corners::largeCornerRadius); StackShadow::renderDropShadow(g, secondShadowPath, Colour(0, 0, 0).withAlpha(0.32f), 8); - for(int i = 0; i < sponsors.size(); i++) - { + for (int i = 0; i < sponsors.size(); i++) { auto rowBounds = bounds.removeFromTop(36); auto first = i == 0; auto last = i == (sponsors.size() - 1); auto& name = sponsors[i]; Path outline; outline.addRoundedRectangle(rowBounds.getX(), rowBounds.getY(), rowBounds.getWidth(), rowBounds.getHeight(), Corners::largeCornerRadius, Corners::largeCornerRadius, first, first, last, last); - + g.setColour(findColour(PlugDataColour::panelForegroundColourId)); g.fillPath(outline); g.setColour(findColour(PlugDataColour::outlineColourId)); g.strokePath(outline, PathStrokeType(1)); - + Fonts::drawText(g, name, rowBounds.reduced(12, 2), findColour(PlugDataColour::panelTextColourId), 15); - + jassert(!bounds.isEmpty()); } } }; - - class LicensePanel : public Component - { + + class LicensePanel : public Component { TextEditor license; - + String licenseText = "Copyright Timothy Schoen\n\n" - "This app is licensed under the GNU General Public License version 3 (GPL-3.0). You are free to use, modify, and distribute the software, provided that any derivative works also carry the same license and the source code remains accessible.\n" - "This application comes with absolutely no warranty."; - + "This app is licensed under the GNU General Public License version 3 (GPL-3.0). You are free to use, modify, and distribute the software, provided that any derivative works also carry the same license and the source code remains accessible.\n" + "This application comes with absolutely no warranty."; + public: LicensePanel() { @@ -154,23 +149,23 @@ class AboutPanel : public Component { license.setLineSpacing(1.1f); addAndMakeVisible(license); } - + void resized() override { license.setBounds(getLocalBounds().withTrimmedTop(32).reduced(16, 8)); } - + void paint(Graphics& g) override { g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); g.fillRoundedRectangle(getLocalBounds().toFloat(), Corners::windowCornerRadius); } }; - + CreditsViewport creditsViewport; CreditsPanel creditsComponent; LicensePanel licenseComponent; - + MainToolbarButton backButton; Image logo = ImageFileFormat::loadFrom(BinaryData::plugdata_large_logo_png, BinaryData::plugdata_large_logo_pngSize); @@ -185,19 +180,19 @@ class AboutPanel : public Component { showCredits.setButtonText("Credits"); showLicense.setButtonText("License"); - + addAndMakeVisible(viewWebsite); addAndMakeVisible(viewOnGithub); addAndMakeVisible(reportIssue); addAndMakeVisible(sponsor); - + viewWebsite.setConnectedEdges(Button::ConnectedOnBottom); viewOnGithub.setConnectedEdges(Button::ConnectedOnTop); viewWebsite.onClick = []() { URL("https://plugdata.org").launchInDefaultBrowser(); }; - + viewOnGithub.onClick = []() { URL("https://github.com/plugdata-team/plugdata").launchInDefaultBrowser(); }; @@ -209,9 +204,9 @@ class AboutPanel : public Component { sponsor.onClick = []() { URL("https://github.com/sponsors/timothyschoen").launchInDefaultBrowser(); }; - + backButton.setButtonText(Icons::Back); - backButton.onClick = [this](){ + backButton.onClick = [this]() { creditsViewport.setVisible(false); licenseComponent.setVisible(false); backButton.setVisible(false); @@ -220,21 +215,21 @@ class AboutPanel : public Component { addChildComponent(backButton); addAndMakeVisible(showCredits); addAndMakeVisible(showLicense); - - creditsViewport.setScrollBarsShown (true, false); + + creditsViewport.setScrollBarsShown(true, false); creditsViewport.setViewedComponent(&creditsComponent, false); creditsComponent.setVisible(true); addChildComponent(creditsViewport); addChildComponent(licenseComponent); - + showCredits.setConnectedEdges(Button::ConnectedOnBottom); showLicense.setConnectedEdges(Button::ConnectedOnTop); - + showCredits.onClick = [this]() { creditsViewport.setVisible(true); backButton.setVisible(true); }; - + showLicense.onClick = [this]() { licenseComponent.setVisible(true); backButton.setVisible(true); @@ -245,12 +240,12 @@ class AboutPanel : public Component { { g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); g.fillRoundedRectangle(getLocalBounds().toFloat(), Corners::windowCornerRadius); - + Fonts::drawStyledText(g, "plugdata", 0, 100, getWidth(), 30, findColour(PlugDataColour::panelTextColourId), Bold, 30, Justification::centred); g.setFont(Font(16)); g.drawFittedText("By Timothy Schoen", 0, 132, getWidth(), 30, Justification::centred, 1); - + g.setColour(findColour(PlugDataColour::dataColourId).withAlpha(0.2f)); auto versionBounds = getLocalBounds().withTrimmedTop(162).removeFromTop(32).withSizeKeepingCentre(64, 24); g.fillRoundedRectangle(versionBounds.toFloat(), 12.0f); @@ -261,10 +256,8 @@ class AboutPanel : public Component { g.setImageResamplingQuality(Graphics::highResamplingQuality); g.drawImage(logo, logoBounds); g.setImageResamplingQuality(Graphics::mediumResamplingQuality); - - - for(auto& shadow : std::vector>{viewWebsite.getBounds().getUnion(viewOnGithub.getBounds()), reportIssue.getBounds(), sponsor.getBounds(), showCredits.getBounds().getUnion(showLicense.getBounds())}) - { + + for (auto& shadow : std::vector> { viewWebsite.getBounds().getUnion(viewOnGithub.getBounds()), reportIssue.getBounds(), sponsor.getBounds(), showCredits.getBounds().getUnion(showLicense.getBounds()) }) { Path shadowPath; shadowPath.addRoundedRectangle(shadow.reduced(4), Corners::largeCornerRadius); StackShadow::renderDropShadow(g, shadowPath, Colour(0, 0, 0).withAlpha(0.32f), 8); @@ -272,24 +265,24 @@ class AboutPanel : public Component { backButton.setBounds(2, 0, 40, 40); } - + void resized() override { creditsViewport.setBounds(getLocalBounds()); creditsComponent.setSize(getWidth(), 1132); licenseComponent.setBounds(getLocalBounds()); - + auto bounds = getLocalBounds().withTrimmedTop(190).reduced(16, 10); viewWebsite.setBounds(bounds.removeFromTop(44).reduced(4)); viewOnGithub.setBounds(bounds.removeFromTop(44).reduced(4).translated(0, -9)); - + bounds.removeFromTop(3); reportIssue.setBounds(bounds.removeFromTop(44).reduced(4)); - + bounds.removeFromTop(6); sponsor.setBounds(bounds.removeFromTop(44).reduced(4)); - + bounds.removeFromTop(6); showCredits.setBounds(bounds.removeFromTop(44).reduced(4)); showLicense.setBounds(bounds.removeFromTop(44).reduced(4).translated(0, -9)); diff --git a/Source/Dialogs/AddObjectMenu.h b/Source/Dialogs/AddObjectMenu.h index 3265240f0f..1d4232ca3c 100644 --- a/Source/Dialogs/AddObjectMenu.h +++ b/Source/Dialogs/AddObjectMenu.h @@ -192,8 +192,7 @@ class ObjectList : public Component { OwnedArray objectButtons; - - static inline const std::vector>>> defaultObjectList = { + static inline std::vector>>> const defaultObjectList = { { "Default", { { Icons::GlyphEmptyObject, "#X obj 0 0", "(@keypress) Empty object", "Object", NewObject }, @@ -356,7 +355,7 @@ class ObjectList : public Component { } }, }; - static inline const std::vector>>> heavyObjectList = { + static inline std::vector>>> const heavyObjectList = { { "Default", { { Icons::GlyphEmptyObject, "#X obj 0 0", "(@keypress) Empty object", "Object", NewObject }, @@ -556,7 +555,7 @@ class AddObjectMenuButton : public Component { bool clickingTogglesState = false; std::function onClick = []() {}; - explicit AddObjectMenuButton(const String& iconStr, const String& textStr = String()) + explicit AddObjectMenuButton(String const& iconStr, String const& textStr = String()) : icon(iconStr) , text(textStr) { diff --git a/Source/Dialogs/AudioOutputSettings.h b/Source/Dialogs/AudioOutputSettings.h index bf2f7f57d5..6ed8173b21 100644 --- a/Source/Dialogs/AudioOutputSettings.h +++ b/Source/Dialogs/AudioOutputSettings.h @@ -11,7 +11,7 @@ class OversampleSettings : public Component { public: std::function onChange = [](int) {}; - + explicit OversampleSettings(int currentSelection) { one.setConnectedEdges(Button::ConnectedOnRight); @@ -65,7 +65,7 @@ class OversampleSettings : public Component { class LimiterSettings : public Component { public: std::function onChange = [](int) {}; - + explicit LimiterSettings(int currentSelection) { one.setConnectedEdges(Button::ConnectedOnRight); @@ -98,7 +98,7 @@ class LimiterSettings : public Component { setSize(180, 50); } - private: +private: void resized() override { auto b = getLocalBounds().reduced(4, 4); @@ -116,21 +116,20 @@ class LimiterSettings : public Component { TextButton four = TextButton("3db"); }; - class AudioOutputSettings : public Component { - + class LimiterEnableButton : public Component , public SettableTooltipClient { - - String icon; - String text; - bool state; - bool buttonHover = false; - + + String icon; + String text; + bool state; + bool buttonHover = false; + public: std::function onClick; - LimiterEnableButton(AudioOutputSettings* parent, String const& iconText, String text, bool initState) + LimiterEnableButton(AudioOutputSettings* parent, String const& iconText, String text, bool initState) : icon(iconText) , text(text) , state(initState) @@ -151,7 +150,6 @@ class AudioOutputSettings : public Component { Fonts::drawText(g, text, Rectangle(30, 0, getWidth(), getHeight()), textColour, 14); } - void mouseEnter(MouseEvent const& e) override { buttonHover = true; @@ -171,58 +169,57 @@ class AudioOutputSettings : public Component { repaint(); } }; - + public: - AudioOutputSettings(PluginProcessor* pd) - : limiterSettings(SettingsFile::getInstance()->getProperty("limiter_threshold")) - , oversampleSettings(SettingsFile::getInstance()->getProperty("oversampling")) + AudioOutputSettings(PluginProcessor* pd) + : limiterSettings(SettingsFile::getInstance()->getProperty("limiter_threshold")) + , oversampleSettings(SettingsFile::getInstance()->getProperty("oversampling")) { addAndMakeVisible(limiterSettings); - limiterSettings.onChange = [pd](int value){ + limiterSettings.onChange = [pd](int value) { pd->setLimiterThreshold(value); }; - + addAndMakeVisible(oversampleSettings); - oversampleSettings.onChange = [pd](int value){ + oversampleSettings.onChange = [pd](int value) { pd->setOversampling(value); }; - + setSize(170, 125); } - + ~AudioOutputSettings() { isShowing = false; } - + void resized() override { auto bounds = getLocalBounds().reduced(4.0f).withTrimmedTop(24); - + limiterSettings.setBounds(bounds.removeFromTop(28)); - + bounds.removeFromTop(32); oversampleSettings.setBounds(bounds.removeFromTop(28)); } - + void paint(Graphics& g) override { g.setColour(findColour(PlugDataColour::popupMenuTextColourId)); g.setFont(Fonts::getBoldFont().withHeight(15)); g.drawText("Limiter Threshold", 0, 0, getWidth(), 24, Justification::centred); - + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.drawLine(4, 24, getWidth() - 8, 24); - - + g.setColour(findColour(PlugDataColour::popupMenuTextColourId)); g.setFont(Fonts::getBoldFont().withHeight(15)); g.drawText("Oversampling", 0, 56, getWidth(), 24, Justification::centred); - + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.drawLine(4, 84, getWidth() - 8, 84); } - + static void show(PluginEditor* editor, Rectangle bounds) { if (isShowing) @@ -233,11 +230,10 @@ class AudioOutputSettings : public Component { auto audioOutputSettings = std::make_unique(editor->pd); editor->showCalloutBox(std::move(audioOutputSettings), bounds); } - + private: static inline bool isShowing = false; - + LimiterSettings limiterSettings; OversampleSettings oversampleSettings; - }; diff --git a/Source/Dialogs/AudioSettingsPanel.h b/Source/Dialogs/AudioSettingsPanel.h index f1e04925d7..dc07ce662b 100644 --- a/Source/Dialogs/AudioSettingsPanel.h +++ b/Source/Dialogs/AudioSettingsPanel.h @@ -464,7 +464,7 @@ class DAWAudioSettings : public SettingsDialogPanel tailLengthValue.referTo(proc->tailLength); latencyValue.addListener(this); - + latencyValue = proc->getLatencySamples() - pd::Instance::getBlockSize(); latencyNumberBox = new PropertiesPanel::EditableComponent("Latency (samples)", latencyValue); diff --git a/Source/Dialogs/ConnectionMessageDisplay.h b/Source/Dialogs/ConnectionMessageDisplay.h index 1b13b85efb..7d5f5bfdc2 100644 --- a/Source/Dialogs/ConnectionMessageDisplay.h +++ b/Source/Dialogs/ConnectionMessageDisplay.h @@ -18,10 +18,12 @@ class ConnectionMessageDisplay : public Component , public MultiTimer { - + PluginEditor* editor; + public: - ConnectionMessageDisplay(PluginEditor* parentEditor) : editor(parentEditor) + ConnectionMessageDisplay(PluginEditor* parentEditor) + : editor(parentEditor) { setSize(36, 36); setVisible(false); @@ -45,7 +47,7 @@ class ConnectionMessageDisplay auto clearSignalDisplayBuffer = [this]() { SignalBlock sample; - while (sampleQueue.try_dequeue(sample)) {} + while (sampleQueue.try_dequeue(sample)) { } for (int ch = 0; ch < 8; ch++) { std::fill(lastSamples[ch], lastSamples[ch] + signalBlockSize, 0.0f); cycleLength[ch] = 0.0f; @@ -115,7 +117,7 @@ class ConnectionMessageDisplay auto firstOrLast = (i == 0 || i == textString.size() - 1); stringItem = textString[i]; stringItem += firstOrLast ? "" : ","; - + // first item uses system font // use cached width calculation for performance stringWidth = CachedFontStringWidth::get()->calculateSingleLineWidth(textFont, stringItem); @@ -123,7 +125,7 @@ class ConnectionMessageDisplay if ((totalStringWidth + stringWidth) > halfEditorWidth) { auto elideText = String("(" + String(textString.size() - i) + String(")...")); auto elideFont = Font(Fonts::getSemiBoldFont()); - + auto elideWidth = CachedFontStringWidth::get()->calculateSingleLineWidth(elideFont, elideText); messageItemsWithFormat.add(TextStringWithMetrics(elideText, FontStyle::Semibold, elideWidth)); totalStringWidth += elideWidth + 4; @@ -146,10 +148,9 @@ class ConnectionMessageDisplay if (totalStringWidth > getWidth() || isHoverEntered) { updateBoundsFromProposed(Rectangle().withSize(totalStringWidth, 36)); } - + // Check if changed - if(lastTextString != textString) - { + if (lastTextString != textString) { lastTextString = textString; repaint(); } diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index b4278fe0d5..16bf67df74 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -41,11 +41,11 @@ #include "Standalone/PlugDataWindow.h" Dialog::Dialog(std::unique_ptr* ownerPtr, Component* editor, int childWidth, int childHeight, bool showCloseButton, int margin) -: height(childHeight) -, width(childWidth) -, parentComponent(editor) -, owner(ownerPtr) -, backgroundMargin(margin) + : height(childHeight) + , width(childWidth) + , parentComponent(editor) + , owner(ownerPtr) + , backgroundMargin(margin) { #if JUCE_LINUX || JUCE_BSD addToDesktop(0); @@ -57,12 +57,11 @@ Dialog::Dialog(std::unique_ptr* ownerPtr, Component* editor, int childWi setBounds(parentComponent->getScreenX(), parentComponent->getScreenY(), parentComponent->getWidth(), parentComponent->getHeight()); parentComponent->addComponentListener(this); - if(ProjectInfo::isStandalone) { - if (auto* mainWindow = dynamic_cast(parentComponent->getTopLevelComponent())) + if (ProjectInfo::isStandalone) { + if (auto* mainWindow = dynamic_cast(parentComponent->getTopLevelComponent())) mainWindow->dialog = SafePointer(this); toFront(true); - } - else { + } else { setAlwaysOnTop(true); } setWantsKeyboardFocus(true); @@ -92,10 +91,10 @@ Dialog::Dialog(std::unique_ptr* ownerPtr, Component* editor, int childWi } #if !JUCE_IOS -void Dialog::mouseDrag(MouseEvent const &e) +void Dialog::mouseDrag(MouseEvent const& e) { if (dragging) { - if (auto mainWindow = dynamic_cast(parentComponent->getTopLevelComponent())) { + if (auto mainWindow = dynamic_cast(parentComponent->getTopLevelComponent())) { mainWindow->movedFromDialog = true; } dragger.dragWindow(parentComponent->getTopLevelComponent(), e, nullptr); @@ -110,7 +109,7 @@ bool Dialog::wantsRoundedCorners() const if (auto* editor = dynamic_cast(parentComponent)) { return editor->wantsRoundedCorners(); } - // Otherwise assume rounded corners for the rest of the UI + // Otherwise assume rounded corners for the rest of the UI else { return true; } @@ -268,8 +267,8 @@ void Dialogs::showMainMenu(PluginEditor* editor, Component* centre) delete popup; }); }); - - if(ProjectInfo::canUseSemiTransparentWindows()) { + + if (ProjectInfo::canUseSemiTransparentWindows()) { editor->calloutArea->addToDesktop(ComponentPeer::windowIsTemporary); } } @@ -278,9 +277,9 @@ void Dialogs::showOkayCancelDialog(std::unique_ptr* target, Component* p { class OkayCancelDialog : public Component { - + TextLayout layout; - + public: OkayCancelDialog(Dialog* dialog, String const& title, std::function const& callback, StringArray const& options) : label("", title) @@ -289,7 +288,7 @@ void Dialogs::showOkayCancelDialog(std::unique_ptr* target, Component* p attributedTitle.setJustification(Justification::centred); attributedTitle.setFont(Fonts::getBoldFont().withHeight(14)); attributedTitle.setColour(findColour(PlugDataColour::panelTextColourId)); - + setSize(270, 220); layout.createLayout(attributedTitle, getWidth() - 32); @@ -320,15 +319,15 @@ void Dialogs::showOkayCancelDialog(std::unique_ptr* target, Component* p setOpaque(false); } - + void paint(Graphics& g) override - { + { AttributedString warningIcon(Icons::Warning); warningIcon.setFont(Fonts::getIconFont().withHeight(48)); warningIcon.setColour(findColour(PlugDataColour::panelTextColourId)); warningIcon.setJustification(Justification::centred); warningIcon.draw(g, getLocalBounds().toFloat().removeFromTop(90)); - + auto contentBounds = getLocalBounds().withTrimmedTop(63).reduced(16); layout.draw(g, contentBounds.removeFromTop(48).toFloat()); } @@ -337,7 +336,7 @@ void Dialogs::showOkayCancelDialog(std::unique_ptr* target, Component* p { auto contentBounds = getLocalBounds().reduced(16); contentBounds.removeFromTop(126); - + okay.setBounds(contentBounds.removeFromTop(28)); contentBounds.removeFromTop(6); cancel.setBounds(contentBounds.removeFromTop(28)); @@ -427,7 +426,7 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent struct QuickActionsBar : public PopupMenu::CustomComponent { struct QuickActionButton : public TextButton { - explicit QuickActionButton(const String& buttonText) + explicit QuickActionButton(String const& buttonText) : TextButton(buttonText) { } @@ -467,7 +466,7 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent auto id = commandIds.removeAndReturn(0); button->setCommandToTrigger(&editor->commandManager, id, false); - + if (auto* registeredInfo = editor->commandManager.getCommandForID(id)) { ApplicationCommandInfo info(*registeredInfo); editor->commandManager.getTargetForCommand(id, info); @@ -532,11 +531,9 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent auto selectedBoxes = cnv->getSelectionOfType(); // If we directly right-clicked on an object, make sure it has been added to selection - if(!originalComponent) - { + if (!originalComponent) { return; - } - else if (auto* obj = dynamic_cast(originalComponent)) { + } else if (auto* obj = dynamic_cast(originalComponent)) { selectedBoxes.addIfNotAlreadyThere(obj); } else if (auto* parentOfTypeObject = originalComponent->findParentComponentOfClass()) { selectedBoxes.addIfNotAlreadyThere(parentOfTypeObject); @@ -569,7 +566,7 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent Backward, ToBack, Properties, - + AlignLeft, AlignHCentre, AlignRight, @@ -622,7 +619,7 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent orderMenu.addItem(Backward, "Move backward", object != nullptr && !locked); orderMenu.addItem(ToBack, "To Back", object != nullptr && !locked); popupMenu.addSubMenu("Order", orderMenu, !locked); - + class AlignmentMenuItem : public PopupMenu::CustomComponent { String menuItemIcon; @@ -669,7 +666,7 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent Fonts::drawFittedText(g, menuItemText, r, colour, fontHeight); } }; - + PopupMenu alignMenu; alignMenu.addCustomItem(AlignLeft, std::make_unique(Icons::AlignLeft, "Align left"), nullptr, "Align left"); alignMenu.addCustomItem(AlignHCentre, std::make_unique(Icons::AlignVCentre, "Align centre"), nullptr, "Align centre"); @@ -807,37 +804,38 @@ void Dialogs::showCanvasRightClickMenu(Canvas* cnv, Component* originalComponent Dialogs::showObjectReferenceDialog(&editor->openedDialog, editor, object->getType()); break; case AlignLeft: - cnv->alignObjects(Align::Left); - break; + cnv->alignObjects(Align::Left); + break; case AlignHCentre: - cnv->alignObjects(Align::HCentre); - break; + cnv->alignObjects(Align::HCentre); + break; case AlignRight: - cnv->alignObjects(Align::Right); - break; + cnv->alignObjects(Align::Right); + break; case AlignHDistribute: - cnv->alignObjects(Align::HDistribute); - break; + cnv->alignObjects(Align::HDistribute); + break; case AlignTop: - cnv->alignObjects(Align::Top); - break; + cnv->alignObjects(Align::Top); + break; case AlignVCentre: - cnv->alignObjects(Align::VCentre); - break; + cnv->alignObjects(Align::VCentre); + break; case AlignBottom: - cnv->alignObjects(Align::Bottom); - break; + cnv->alignObjects(Align::Bottom); + break; case AlignVDistribute: - cnv->alignObjects(Align::VDistribute); - break; + cnv->alignObjects(Align::VDistribute); + break; default: break; } }; auto* parent = ProjectInfo::canUseSemiTransparentWindows() ? editor->calloutArea.get() : nullptr; - if(parent) parent->addToDesktop(ComponentPeer::windowIsTemporary); - + if (parent) + parent->addToDesktop(ComponentPeer::windowIsTemporary); + popupMenu.showMenuAsync(PopupMenu::Options().withMinimumWidth(100).withMaximumNumColumns(1).withParentComponent(parent).withTargetScreenArea(Rectangle(position, position.translated(1, 1))), ModalCallbackFunction::create(callback)); } @@ -870,14 +868,13 @@ void Dialogs::showOpenDialog(std::function const& callback, bool canS if (canSelectDirectories) openChooserFlags = static_cast(openChooserFlags | FileBrowserComponent::canSelectDirectories); - fileChooser->launchAsync(openChooserFlags, [callback, lastFileId](FileChooser const& fileChooser) { auto result = fileChooser.getResult(); auto lastDir = result.isDirectory() ? result : result.getParentDirectory(); SettingsFile::getInstance()->setLastBrowserPathForId(lastFileId, lastDir); - if(result.exists()) { + if (result.exists()) { callback(fileChooser.getURLResult()); } Dialogs::fileChooser = nullptr; @@ -913,6 +910,5 @@ void Dialogs::showSaveDialog(std::function const& callback, String co callback(fileChooser.getURLResult()); Dialogs::fileChooser = nullptr; } - }); } diff --git a/Source/Dialogs/Dialogs.h b/Source/Dialogs/Dialogs.h index e84d675550..6f05f6b8a3 100644 --- a/Source/Dialogs/Dialogs.h +++ b/Source/Dialogs/Dialogs.h @@ -13,7 +13,8 @@ class Canvas; -class Dialog : public Component, public ComponentListener { +class Dialog : public Component + , public ComponentListener { public: Dialog(std::unique_ptr* ownerPtr, Component* editor, int childWidth, int childHeight, bool showCloseButton, int margin = 0); @@ -21,7 +22,7 @@ class Dialog : public Component, public ComponentListener { ~Dialog() override { parentComponent->removeComponentListener(this); - + if (auto* window = dynamic_cast(getTopLevelComponent())) { if (ProjectInfo::isStandalone) { if (auto* closeButton = window->getCloseButton()) @@ -33,7 +34,7 @@ class Dialog : public Component, public ComponentListener { } } } - + void componentMovedOrResized(Component& comp, bool wasMoved, bool wasResized) override { setBounds(parentComponent->getScreenX(), parentComponent->getScreenY(), parentComponent->getWidth(), parentComponent->getHeight()); @@ -51,14 +52,12 @@ class Dialog : public Component, public ComponentListener { void paint(Graphics& g) override { - if(!ProjectInfo::canUseSemiTransparentWindows()) - { + if (!ProjectInfo::canUseSemiTransparentWindows()) { g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); - } - else { + } else { g.setColour(Colours::black.withAlpha(0.5f)); } - + auto bounds = getLocalBounds().toFloat().reduced(backgroundMargin); if (wantsRoundedCorners()) { @@ -103,7 +102,7 @@ class Dialog : public Component, public ComponentListener { #if !JUCE_IOS void mouseDown(MouseEvent const& e) override { - if(!hasKeyboardFocus(false)) { + if (!hasKeyboardFocus(false)) { parentComponent->toFront(false); toFront(true); } @@ -124,12 +123,11 @@ class Dialog : public Component, public ComponentListener { dragging = false; } #endif - + void setBlockFromClosing(bool block) { blockCloseAction = block; } - bool keyPressed(KeyPress const& key) override { @@ -185,7 +183,7 @@ struct Dialogs { static void showPatchStorage(PluginEditor* editor); static void dismissFileDialog(); - + static void showOpenDialog(std::function const& callback, bool canSelectFiles, bool canSelectDirectories, String const& lastFileId, String const& extension, Component* parentComponent); static void showSaveDialog(std::function const& callback, String const& extension, String const& lastFileId, Component* parentComponent = nullptr, bool directoryMode = false); diff --git a/Source/Dialogs/HelpDialog.h b/Source/Dialogs/HelpDialog.h index 6a1849353b..21da7ff383 100644 --- a/Source/Dialogs/HelpDialog.h +++ b/Source/Dialogs/HelpDialog.h @@ -72,7 +72,7 @@ class HelpDialog : public TopLevelWindow OwnedArray buttons; }; - //IndexComponent index; + // IndexComponent index; public: std::function onClose; @@ -82,8 +82,10 @@ class HelpDialog : public TopLevelWindow int margin; explicit HelpDialog(PluginProcessor* instance) - : TopLevelWindow("Help", true), pd(instance), margin(ProjectInfo::canUseSemiTransparentWindows() ? 15 : 0) - //, index([this](File const& file) { markupDisplay.setMarkdownString(file.loadFileAsString()); }) + : TopLevelWindow("Help", true) + , pd(instance) + , margin(ProjectInfo::canUseSemiTransparentWindows() ? 15 : 0) + //, index([this](File const& file) { markupDisplay.setMarkdownString(file.loadFileAsString()); }) { markupDisplay.setFileSource(this); markupDisplay.setFont(Fonts::getVariableFont()); @@ -92,12 +94,12 @@ class HelpDialog : public TopLevelWindow closeButton.reset(LookAndFeel::getDefaultLookAndFeel().createDocumentWindowButton(-1)); addAndMakeVisible(closeButton.get()); - + setVisible(true); setOpaque(false); setUsingNativeTitleBar(false); setDropShadowEnabled(false); - + closeButton->onClick = [this]() { MessageManager::callAsync([this]() { onClose(); @@ -109,14 +111,14 @@ class HelpDialog : public TopLevelWindow setTopLeftPosition(Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea.getCentre() - Point(350, 250)); constrainer.setSizeLimits(500, 300, 1400, 1000); constrainer.setFixedAspectRatio(0.0f); - + resizer = std::make_unique>(this, &constrainer); - //resizer->setAllowHostManagedResize(false); + // resizer->setAllowHostManagedResize(false); resizer->setAlwaysOnTop(true); addAndMakeVisible(resizer.get()); setSize(700, 500); - //addAndMakeVisible(index); + // addAndMakeVisible(index); } Image getImageForFilename(String filename) override @@ -133,10 +135,10 @@ class HelpDialog : public TopLevelWindow auto closeButtonBounds = getLocalBounds().reduced(margin).removeFromTop(30).removeFromRight(30).translated(-5, 5); closeButton->setBounds(closeButtonBounds); - //index.setBounds(bounds.removeFromLeft(200)); + // index.setBounds(bounds.removeFromLeft(200)); markupDisplay.setBounds(bounds.reduced(2, 0)); } - + int getDesktopWindowStyleFlags() const override { int styleFlags = TopLevelWindow::getDesktopWindowStyleFlags(); @@ -147,7 +149,7 @@ class HelpDialog : public TopLevelWindow void mouseDown(MouseEvent const& e) override { auto dragHitBox = getLocalBounds().reduced(margin).removeFromTop(38).reduced(4); - if(dragHitBox.contains(e.x, e.y)) { + if (dragHitBox.contains(e.x, e.y)) { windowDragger.startDraggingComponent(this, e); } } @@ -155,7 +157,7 @@ class HelpDialog : public TopLevelWindow void mouseDrag(MouseEvent const& e) override { auto dragHitBox = getLocalBounds().reduced(margin).removeFromTop(38).reduced(4); - if(dragHitBox.contains(e.getMouseDownX(), e.getMouseDownY())) { + if (dragHitBox.contains(e.getMouseDownX(), e.getMouseDownY())) { windowDragger.dragComponent(this, e, nullptr); } } @@ -167,14 +169,14 @@ class HelpDialog : public TopLevelWindow auto b = totalBounds; auto titlebarBounds = b.removeFromTop(toolbarHeight).toFloat(); auto bgBounds = b.toFloat(); - //auto sidebarBounds = b.removeFromLeft(200); - - if(ProjectInfo::canUseSemiTransparentWindows()) { + // auto sidebarBounds = b.removeFromLeft(200); + + if (ProjectInfo::canUseSemiTransparentWindows()) { auto shadowPath = Path(); shadowPath.addRoundedRectangle(getLocalBounds().reduced(20), Corners::windowCornerRadius); StackShadow::renderDropShadow(g, shadowPath, Colour(0, 0, 0).withAlpha(0.6f), 13.0f); } - + float cornerRadius = ProjectInfo::canUseSemiTransparentWindows() ? Corners::windowCornerRadius : 0.0f; Path toolbarPath; @@ -186,7 +188,7 @@ class HelpDialog : public TopLevelWindow backgroundPath.addRoundedRectangle(bgBounds.getX(), bgBounds.getY(), bgBounds.getWidth(), bgBounds.getHeight(), cornerRadius, cornerRadius, false, false, true, true); g.setColour(findColour(PlugDataColour::canvasBackgroundColourId)); g.fillPath(backgroundPath); - + /* Path sidebarPath; backgroundPath.addRoundedRectangle(sidebarBounds.getX(), sidebarBounds.getY(), sidebarBounds.getWidth(), sidebarBounds.getHeight(), cornerRadius, cornerRadius, false, false, true, false); @@ -195,12 +197,11 @@ class HelpDialog : public TopLevelWindow g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.drawHorizontalLine(b.getY() + toolbarHeight, b.getX(), b.getWidth()); - + g.setColour(findColour(PlugDataColour::outlineColourId)); g.drawRoundedRectangle(totalBounds.toFloat().reduced(0.5f), cornerRadius, 1.f); - - //g.drawVerticalLine(b.getX() + 200, b.getY() + 40, g.getHeight()); - + + // g.drawVerticalLine(b.getX() + 200, b.getY() + 40, g.getHeight()); Fonts::drawStyledText(g, "Help", Rectangle(totalBounds.getX(), totalBounds.getY() + 4.0f, b.getWidth(), 32.0f), findColour(PlugDataColour::panelTextColourId), Semibold, 15, Justification::centred); } diff --git a/Source/Dialogs/KeyMappingPanel.h b/Source/Dialogs/KeyMappingPanel.h index 9418bf0320..ac0f2b73a0 100644 --- a/Source/Dialogs/KeyMappingPanel.h +++ b/Source/Dialogs/KeyMappingPanel.h @@ -211,14 +211,12 @@ class KeyMappingComponent : public SettingsDialogPanel { addButton("OK", 1); addButton("Cancel", 0); - // (avoid return + escape keys getting processed by the buttons..) for (auto* child : getChildren()) child->setWantsKeyboardFocus(false); - - for (int i = 0; i < getNumButtons(); i++) - { + + for (int i = 0; i < getNumButtons(); i++) { auto& button = *getButton(i); auto backgroundColour = findColour(PlugDataColour::dialogBackgroundColourId); button.setColour(TextButton::buttonColourId, backgroundColour.contrasting(0.05f)); diff --git a/Source/Dialogs/MainMenu.h b/Source/Dialogs/MainMenu.h index 22c7ed256b..cf4d7aaf26 100644 --- a/Source/Dialogs/MainMenu.h +++ b/Source/Dialogs/MainMenu.h @@ -185,7 +185,8 @@ class MainMenu : public PopupMenu { } }; - class ThemeSelector : public Component, public AsyncUpdater { + class ThemeSelector : public Component + , public AsyncUpdater { Value theme; ValueTree settingsTree; @@ -253,7 +254,7 @@ class MainMenu : public PopupMenu { triggerAsyncUpdate(); } } - + void handleAsyncUpdate() override { // Make sure the actual popup menu updates its theme diff --git a/Source/Dialogs/ObjectBrowserDialog.h b/Source/Dialogs/ObjectBrowserDialog.h index a6b4acf066..9cca9d1805 100644 --- a/Source/Dialogs/ObjectBrowserDialog.h +++ b/Source/Dialogs/ObjectBrowserDialog.h @@ -53,7 +53,7 @@ class CategoriesListBox : public ListBox g.setColour(findColour(PlugDataColour::panelActiveBackgroundColourId)); g.fillRoundedRectangle({ 4.0f, 1.0f, width - 8.0f, height - 2.0f }, Corners::defaultCornerRadius); } - + Fonts::drawText(g, categories[rowNumber], 12, 0, width - 9, height, findColour(PlugDataColour::panelTextColourId), 15); } @@ -232,19 +232,16 @@ class ObjectsListBox : public ListBox return itemComponent; } } - + void removeAliasedDuplicates(StringArray& objectsToShow) { StringArray gemObjects; - for(auto& object : objectsToShow) - { - if(object.startsWith("Gem")) - { + for (auto& object : objectsToShow) { + if (object.startsWith("Gem")) { gemObjects.add(object.fromLastOccurrenceOf("/", false, false)); } } - for(auto& gemObject : gemObjects) - { + for (auto& gemObject : gemObjects) { objectsToShow.removeString(gemObject); } } @@ -268,7 +265,8 @@ class ObjectsListBox : public ListBox class ObjectViewerDragArea : public ObjectDragAndDrop { public: ObjectViewerDragArea(PluginEditor* editor, std::function dismissMenu) - : ObjectDragAndDrop(editor), dismissMenu(dismissMenu) + : ObjectDragAndDrop(editor) + , dismissMenu(dismissMenu) { setBufferedToImage(true); } @@ -495,7 +493,7 @@ class ObjectViewer : public Component { { auto objectInfo = library.getObjectInfo(name); bool valid = name.isNotEmpty() && objectInfo.isValid(); - + // openHelp.setVisible(valid); openReference.setVisible(valid); objectDragArea.setVisible(valid); @@ -513,7 +511,7 @@ class ObjectViewer : public Component { bool hasUnknownInletLayout = false; bool hasUnknownOutletLayout = false; - + auto ioletDescriptions = objectInfo.getChildWithName("iolets"); for (auto iolet : ioletDescriptions) { auto variable = iolet.getProperty("variable").toString() == "1"; @@ -818,8 +816,9 @@ class ObjectBrowserDialog : public Component { for (auto& object : library.getAllObjects()) { auto info = library.getObjectInfo(object); - if(!info.isValid()) continue; - + if (!info.isValid()) + continue; + auto categoriesTree = info.getChildWithName("categories"); for (auto category : categoriesTree) { diff --git a/Source/Dialogs/ObjectReferenceDialog.h b/Source/Dialogs/ObjectReferenceDialog.h index a99496d490..73f106a0d8 100644 --- a/Source/Dialogs/ObjectReferenceDialog.h +++ b/Source/Dialogs/ObjectReferenceDialog.h @@ -377,8 +377,9 @@ class ObjectReferenceDialog : public Component { bool hasUnknownOutletLayout = false; auto objectInfo = library.getObjectInfo(name); - if(!objectInfo.isValid()) return; - + if (!objectInfo.isValid()) + return; + auto ioletDescriptions = objectInfo.getChildWithName("iolets"); for (auto iolet : ioletDescriptions) { auto variable = iolet.getProperty("variable").toString() == "1"; diff --git a/Source/Dialogs/OverlayDisplaySettings.h b/Source/Dialogs/OverlayDisplaySettings.h index 71b753cff2..a8a8ffdecc 100644 --- a/Source/Dialogs/OverlayDisplaySettings.h +++ b/Source/Dialogs/OverlayDisplaySettings.h @@ -9,7 +9,8 @@ #include "LookAndFeel.h" #include "Components/PropertiesPanel.h" -class OverlayDisplaySettings : public Component, public Value::Listener { +class OverlayDisplaySettings : public Component + , public Value::Listener { public: class OverlaySelector : public Component , public Button::Listener { @@ -29,7 +30,6 @@ class OverlayDisplaySettings : public Component, public Value::Listener { Overlay group; public: - OverlaySelector(ValueTree const& settings, Overlay groupType, String nameOfSetting, String nameOfGroup, String toolTipString) : groupName(std::move(nameOfGroup)) , settingName(std::move(nameOfSetting)) @@ -144,7 +144,7 @@ class OverlayDisplaySettings : public Component, public Value::Listener { connectionDebugToggle.reset(new PropertiesPanel::BoolComponent("Debug", debugModeValue, { "No", "Yes" })); connectionDebugToggle->setTooltip("Enable connection debugging tooltips"); addAndMakeVisible(*connectionDebugToggle); - + groups.add(&canvas); groups.add(&object); groups.add(&connection); @@ -156,11 +156,10 @@ class OverlayDisplaySettings : public Component, public Value::Listener { } setSize(335, 200); } - + void valueChanged(Value& v) override { - if(v.refersToSameSourceAs(debugModeValue)) - { + if (v.refersToSameSourceAs(debugModeValue)) { set_plugdata_debugging_enabled(getValue(debugModeValue)); } } @@ -174,7 +173,7 @@ class OverlayDisplaySettings : public Component, public Value::Listener { auto leftSide = bounds.removeFromLeft(bounds.proportionOfWidth(0.5f)).withTrimmedRight(4); auto rightSide = bounds.withTrimmedLeft(4); - + canvasLabel.setBounds(leftSide.removeFromTop(labelHeight)); for (auto& item : canvas) { item->setBounds(leftSide.removeFromTop(itemHeight)); @@ -190,7 +189,7 @@ class OverlayDisplaySettings : public Component, public Value::Listener { for (auto& item : connection) { item->setBounds(rightSide.removeFromTop(itemHeight)); } - + connectionDebugToggle->setBounds(rightSide.removeFromTop(itemHeight)); } @@ -199,19 +198,18 @@ class OverlayDisplaySettings : public Component, public Value::Listener { g.setColour(findColour(PlugDataColour::popupMenuTextColourId)); g.setFont(Fonts::getBoldFont().withHeight(15)); g.drawText("Overlays", 0, 0, getWidth(), 24, Justification::centred); - + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.drawLine(4, 24, getWidth() - 8, 24); - + for (auto& group : groups) { auto groupBounds = group->getFirst()->getBounds().getUnion(group->getLast()->getBounds()); - + bool isConnectionGroup = group == &connection; - if(isConnectionGroup) - { + if (isConnectionGroup) { groupBounds = groupBounds.withTrimmedBottom(-28); } - + // draw background rectangle g.setColour(findColour(PlugDataColour::popupMenuBackgroundColourId).contrasting(0.035f)); g.fillRoundedRectangle(groupBounds.toFloat(), Corners::largeCornerRadius); @@ -221,7 +219,7 @@ class OverlayDisplaySettings : public Component, public Value::Listener { g.drawRoundedRectangle(groupBounds.toFloat(), Corners::largeCornerRadius, 1.0f); // draw lines between items - for (auto& item : *group){ + for (auto& item : *group) { if ((group->size() >= 2) && ((item != group->getLast()) || isConnectionGroup)) g.drawHorizontalLine(item->getBottom(), groupBounds.getX(), groupBounds.getRight()); } @@ -261,10 +259,9 @@ class OverlayDisplaySettings : public Component, public Value::Listener { OwnedArray canvas; OwnedArray object; OwnedArray connection; - + Value debugModeValue; std::unique_ptr connectionDebugToggle; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OverlayDisplaySettings) }; diff --git a/Source/Dialogs/PathsAndLibrariesPanel.h b/Source/Dialogs/PathsAndLibrariesPanel.h index 8429b12b6d..64e75380a6 100644 --- a/Source/Dialogs/PathsAndLibrariesPanel.h +++ b/Source/Dialogs/PathsAndLibrariesPanel.h @@ -118,7 +118,6 @@ class SearchPathPanel : public Component addAndMakeVisible(resetButton); resetButton.onClick = [this]() { - Dialogs::showOkayCancelDialog(&confirmationDialog, findParentComponentOfClass(), "Are you sure you want to reset all the search paths?", [this](int result) { if (result == 0) @@ -202,7 +201,7 @@ class SearchPathPanel : public Component g.setColour(findColour(PlugDataColour::toolbarOutlineColourId).withAlpha(0.5f)); g.drawHorizontalLine(height - 1.0f, x, x + newWidth); - + Fonts::drawText(g, paths[rowNumber], x + 12, 0, width - 9, height, findColour(PlugDataColour::panelTextColourId), 15); } @@ -618,7 +617,7 @@ class LibraryLoadPanel : public Component librariesTree.removeAllChildren(nullptr); for (auto const& name : librariesToLoad) { - if(name.isNotEmpty()) { + if (name.isNotEmpty()) { auto newLibrary = ValueTree("Library"); newLibrary.setProperty("Name", name, nullptr); librariesTree.appendChild(newLibrary, nullptr); diff --git a/Source/Dialogs/SnapSettings.h b/Source/Dialogs/SnapSettings.h index a17e4165ba..785ed09ea0 100644 --- a/Source/Dialogs/SnapSettings.h +++ b/Source/Dialogs/SnapSettings.h @@ -59,7 +59,7 @@ class SnapSettings : public Component { EdgesBit = 2, CentersBit = 4 }; - + static void show(PluginEditor* editor, Rectangle bounds) { if (isShowing) @@ -70,7 +70,7 @@ class SnapSettings : public Component { auto snapSettings = std::make_unique(); editor->showCalloutBox(std::move(snapSettings), bounds); } - + class SnapSelector : public Component , public Value::Listener , public SettableTooltipClient { @@ -175,11 +175,11 @@ class SnapSettings : public Component { snapLabel.setText("Snap", dontSendNotification); snapLabel.setFont(Fonts::getSemiBoldFont().withHeight(14)); addAndMakeVisible(snapLabel); - + gridSizeLabel.setText("Grid Size", dontSendNotification); gridSizeLabel.setFont(Fonts::getSemiBoldFont().withHeight(14)); addAndMakeVisible(gridSizeLabel); - + for (auto* group : buttonGroups) { addAndMakeVisible(group); group->addMouseListener(this, true); @@ -192,12 +192,12 @@ class SnapSettings : public Component { addAndMakeVisible(gridSlider.get()); setSize(140, 182); } - + ~SnapSettings() { isShowing = false; } - + void resized() override { auto bounds = getLocalBounds().withTrimmedTop(24); @@ -205,7 +205,7 @@ class SnapSettings : public Component { buttonGroups[SnapItem::Edges]->setBounds(bounds.removeFromTop(26)); buttonGroups[SnapItem::Centers]->setBounds(bounds.removeFromTop(26)); buttonGroups[SnapItem::Grid]->setBounds(bounds.removeFromTop(26)); - + gridSizeLabel.setBounds(bounds.removeFromTop(24)); gridSlider->setBounds(bounds.removeFromTop(34)); } @@ -227,13 +227,13 @@ class SnapSettings : public Component { } } } - + void paint(Graphics& g) override { g.setColour(findColour(PlugDataColour::popupMenuTextColourId)); g.setFont(Fonts::getBoldFont().withHeight(15)); g.drawText("Grid", 0, 0, getWidth(), 24, Justification::centred); - + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.drawLine(4, 24, getWidth() - 8, 24); } @@ -246,7 +246,6 @@ class SnapSettings : public Component { MouseInteraction mouseInteraction = ToggledButtonOff; private: - static inline bool isShowing = false; Label snapLabel, gridSizeLabel; std::unique_ptr gridSlider = std::make_unique(); diff --git a/Source/Dialogs/TextEditorDialog.h b/Source/Dialogs/TextEditorDialog.h index 60faa829e9..7a1089047b 100644 --- a/Source/Dialogs/TextEditorDialog.h +++ b/Source/Dialogs/TextEditorDialog.h @@ -2098,7 +2098,7 @@ struct TextEditorDialog : public Component { addAndMakeVisible(editor); addAndMakeVisible(resizer); resizer.setAlwaysOnTop(true); - //resizer.setAllowHostManagedResize(false); + // resizer.setAllowHostManagedResize(false); editor.grabKeyboardFocus(); } @@ -2132,22 +2132,22 @@ struct TextEditorDialog : public Component { void paint(Graphics& g) override { - if(ProjectInfo::canUseSemiTransparentWindows()) { + if (ProjectInfo::canUseSemiTransparentWindows()) { auto shadowPath = Path(); shadowPath.addRoundedRectangle(getLocalBounds().reduced(20), Corners::windowCornerRadius); StackShadow::renderDropShadow(g, shadowPath, Colour(0, 0, 0).withAlpha(0.6f), 13.0f); } - + auto radius = ProjectInfo::canUseSemiTransparentWindows() ? Corners::windowCornerRadius : 0.0f; auto b = getLocalBounds().reduced(margin); g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); g.fillRoundedRectangle(b.toFloat(), radius); - + g.setColour(findColour(PlugDataColour::outlineColourId)); g.drawRoundedRectangle(b.toFloat().reduced(0.5f), radius, 1.0f); - + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); // g.drawHorizontalLine(b.getX() + 39, b.getY() + 48, b.getWidth()); g.drawHorizontalLine(b.getHeight() - 20, b.getY() + 48, b.getWidth()); diff --git a/Source/Dialogs/ThemePanel.h b/Source/Dialogs/ThemePanel.h index a88a6b8bbe..00e1d32eec 100644 --- a/Source/Dialogs/ThemePanel.h +++ b/Source/Dialogs/ThemePanel.h @@ -229,7 +229,7 @@ class ThemePanel : public SettingsDialogPanel void updateSwatches() { auto scrollPosition = panel.getViewport().getViewPositionY(); - + panel.clear(); allPanels.clear(); @@ -428,14 +428,14 @@ class ThemePanel : public SettingsDialogPanel auto allThemes = PlugDataLook::getAllThemes(); auto firstThemes = allThemes; auto secondThemes = allThemes; - + // Remove theme selected in other combobox (so you can't pick the same theme twice) firstThemes.removeString(PlugDataLook::selectedThemes[1]); secondThemes.removeString(PlugDataLook::selectedThemes[0]); - + primaryThemeSelector->setOptions(firstThemes); secondaryThemeSelector->setOptions(secondThemes); - + primaryThemeSelector->setSelectedItem(firstThemes.indexOf(PlugDataLook::selectedThemes[0])); secondaryThemeSelector->setSelectedItem(secondThemes.indexOf(PlugDataLook::selectedThemes[1])); @@ -536,29 +536,21 @@ class ThemePanel : public SettingsDialogPanel auto themeName = theme.getProperty("theme").toString(); if (v.refersToSameSourceAs(swatches[themeName]["dashed_signal_connections"])) { theme.setProperty("dashed_signal_connections", v.toString(), nullptr); - } - else if(v.refersToSameSourceAs(swatches[themeName]["straight_connections"])) - { + } else if (v.refersToSameSourceAs(swatches[themeName]["straight_connections"])) { theme.setProperty("straight_connections", v.toString(), nullptr); - } - else if(v.refersToSameSourceAs(swatches[themeName]["square_iolets"])) - { + } else if (v.refersToSameSourceAs(swatches[themeName]["square_iolets"])) { theme.setProperty("square_iolets", v.toString(), nullptr); - } - else if(v.refersToSameSourceAs(swatches[themeName]["square_object_corners"])) - { + } else if (v.refersToSameSourceAs(swatches[themeName]["square_object_corners"])) { theme.setProperty("square_object_corners", v.toString(), nullptr); - } - else if(v.refersToSameSourceAs(swatches[themeName]["thin_connections"])) - { + } else if (v.refersToSameSourceAs(swatches[themeName]["thin_connections"])) { theme.setProperty("thin_connections", v.toString(), nullptr); } } - + pd->setTheme(PlugDataLook::currentTheme, true); return; } - + for (auto theme : themeTree) { auto themeName = theme.getProperty("theme").toString(); @@ -599,21 +591,21 @@ class ThemePanel : public SettingsDialogPanel PlugDataLook::setDefaultFont(fontValue.toString()); SettingsFile::getInstance()->setProperty("default_font", fontValue.getValue()); - + auto allThemes = PlugDataLook::getAllThemes(); auto firstThemes = allThemes; auto secondThemes = allThemes; - + firstThemes.removeString(PlugDataLook::selectedThemes[1]); secondThemes.removeString(PlugDataLook::selectedThemes[0]); - + primaryThemeSelector->setSelectedItem(firstThemes.indexOf(PlugDataLook::selectedThemes[0])); secondaryThemeSelector->setSelectedItem(secondThemes.indexOf(PlugDataLook::selectedThemes[1])); - + SettingsFile::getInstance()->getSelectedThemesTree().setProperty("first", "light", nullptr); SettingsFile::getInstance()->getSelectedThemesTree().setProperty("second", "dark", nullptr); SettingsFile::getInstance()->setProperty("theme", "light"); - + updateSwatches(); pd->setTheme(PlugDataLook::selectedThemes[0], true); sendLookAndFeelChange(); diff --git a/Source/Heavy/DaisyExporter.h b/Source/Heavy/DaisyExporter.h index 0de99a9b39..ac21017731 100644 --- a/Source/Heavy/DaisyExporter.h +++ b/Source/Heavy/DaisyExporter.h @@ -28,9 +28,7 @@ class DaisyExporter : public ExporterBase { : ExporterBase(editor, exportingView) { Array properties; - properties.add(new PropertiesPanel::ComboComponent("Target board", targetBoardValue, { - "Pod", "Petal", "Patch", "Patch.Init()", "Field", "Versio", "Terrarium", "Simple", "Custom JSON..." - })); + properties.add(new PropertiesPanel::ComboComponent("Target board", targetBoardValue, { "Pod", "Petal", "Patch", "Patch.Init()", "Field", "Versio", "Terrarium", "Simple", "Custom JSON..." })); properties.add(new PropertiesPanel::ComboComponent("Export type", exportTypeValue, { "Source code", "Binary", "Flash" })); usbMidiProperty = new PropertiesPanel::BoolComponent("USB MIDI", usbMidiValue, { "No", "Yes" }); properties.add(usbMidiProperty); @@ -137,8 +135,7 @@ class DaisyExporter : public ExporterBase { int patchSize = getValue(patchSizeValue); appTypeProperty->setEnabled(patchSize == 4); - if(patchSize <= 3) - { + if (patchSize <= 3) { appTypeValue.setValue(patchSize - 1); } diff --git a/Source/Heavy/ExporterBase.h b/Source/Heavy/ExporterBase.h index 9c86b977ce..a89e9ee739 100644 --- a/Source/Heavy/ExporterBase.h +++ b/Source/Heavy/ExporterBase.h @@ -17,7 +17,7 @@ struct ExporterBase : public Component Value inputPatchValue = SynchronousValue(); Value projectNameValue; Value projectCopyrightValue; - + bool blockDialog = false; #if JUCE_WINDOWS @@ -131,8 +131,7 @@ struct ExporterBase : public Component auto projectTitle = projectNameValue.toString(); auto projectCopyright = projectCopyrightValue.toString(); - if (!projectTitle.unquoted().containsNonWhitespaceChars()) - { + if (!projectTitle.unquoted().containsNonWhitespaceChars()) { if (!realPatchFile.getFileNameWithoutExtension().isEmpty()) projectTitle = realPatchFile.getFileNameWithoutExtension(); else @@ -143,7 +142,7 @@ struct ExporterBase : public Component auto searchPaths = StringArray { patchFile.getParentDirectory().getFullPathName() }; editor->pd->setThis(); - + // Get pd's search paths char* paths[1024]; int numItems; diff --git a/Source/Heavy/ExportingProgressView.h b/Source/Heavy/ExportingProgressView.h index b7122bc58c..3f38b908ab 100644 --- a/Source/Heavy/ExportingProgressView.h +++ b/Source/Heavy/ExportingProgressView.h @@ -158,10 +158,10 @@ class ExportingProgressView : public Component void paint(Graphics& g) override { auto b = getLocalBounds(); - + Path background; background.addRoundedRectangle(b.getX(), b.getY(), b.getWidth(), b.getHeight(), Corners::windowCornerRadius, Corners::windowCornerRadius, false, false, true, true); - + g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); g.fillPath(background); diff --git a/Source/Heavy/HeavyExportDialog.cpp b/Source/Heavy/HeavyExportDialog.cpp index 5b8660bf3a..1ea75a4be0 100644 --- a/Source/Heavy/HeavyExportDialog.cpp +++ b/Source/Heavy/HeavyExportDialog.cpp @@ -84,8 +84,7 @@ class ExporterSettingsPanel : public Component auto heavyState = settingsTree.getChildWithName("HeavyState"); if (heavyState.isValid()) { this->setState(heavyState); - for(int i = 0; i < 4; i++) - { + for (int i = 0; i < 4; i++) { views[i]->blockDialog = true; views[i]->setState(heavyState); views[i]->blockDialog = false; @@ -140,7 +139,7 @@ class ExporterSettingsPanel : public Component views[lastRowSelected]->patchFile = view->patchFile; views[lastRowSelected]->projectNameValue = view->projectNameValue.getValue(); views[lastRowSelected]->projectCopyrightValue = view->projectCopyrightValue.getValue(); - + views[lastRowSelected]->blockDialog = true; views[lastRowSelected]->inputPatchValue = view->inputPatchValue.getValue(); views[lastRowSelected]->blockDialog = false; @@ -248,7 +247,7 @@ HeavyExportDialog::HeavyExportDialog(Dialog* dialog) HeavyExportDialog::~HeavyExportDialog() { Dialogs::dismissFileDialog(); - + // Clean up temp files Toolchain::deleteTempFiles(); } diff --git a/Source/Heavy/Toolchain.h b/Source/Heavy/Toolchain.h index 26a381376c..4732ba3c5c 100644 --- a/Source/Heavy/Toolchain.h +++ b/Source/Heavy/Toolchain.h @@ -97,14 +97,15 @@ class ToolchainInstaller : public Component public: explicit ToolchainInstaller(PluginEditor* pluginEditor, Dialog* parentDialog) : Thread("Toolchain Install Thread") - , editor(pluginEditor), dialog(parentDialog) + , editor(pluginEditor) + , dialog(parentDialog) { addAndMakeVisible(&installButton); installButton.onClick = [this]() { errorMessage = ""; repaint(); - + dialog->setBlockFromClosing(true); String latestVersion; @@ -213,7 +214,7 @@ class ToolchainInstaller : public Component int64 bytesDownloaded = 0; MemoryOutputStream mo(toolchainData, false); - + // pre-allocate memory to improve download speed #if JUCE_MAC mo.preallocate(1024 * 1024 * 128); @@ -339,25 +340,25 @@ class ToolchainInstaller : public Component #else String downloadSize = "1.45 GB"; #endif - + class ToolchainInstallerButton : public Component { - + public: String iconText; String topText; String bottomText; - + std::function onClick = []() {}; - + ToolchainInstallerButton(String icon, String mainText, String subText) - : iconText(std::move(icon)) - , topText(std::move(mainText)) - , bottomText(std::move(subText)) + : iconText(std::move(icon)) + , topText(std::move(mainText)) + , bottomText(std::move(subText)) { setInterceptsMouseClicks(true, false); setAlwaysOnTop(true); } - + void paint(Graphics& g) override { auto colour = findColour(PlugDataColour::panelTextColourId); @@ -365,22 +366,22 @@ class ToolchainInstaller : public Component g.setColour(findColour(PlugDataColour::panelActiveBackgroundColourId)); g.fillRoundedRectangle(Rectangle(1, 1, getWidth() - 2, getHeight() - 2), Corners::largeCornerRadius); } - + Fonts::drawIcon(g, iconText, 20, 5, 40, colour, 24, false); Fonts::drawText(g, topText, 60, 7, getWidth() - 60, 20, colour, 16); Fonts::drawStyledText(g, bottomText, 60, 25, getWidth() - 60, 16, colour, Thin, 14); } - + void mouseUp(MouseEvent const& e) override { onClick(); } - + void mouseEnter(MouseEvent const& e) override { repaint(); } - + void mouseExit(MouseEvent const& e) override { repaint(); diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index 5bbf7c8ef3..adf80025b8 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -61,28 +61,29 @@ void Iolet::render(NVGcontext* nvg) return; auto& fb = cnv->ioletBuffer; - if(!fb.isValid()) return; - + if (!fb.isValid()) + return; + bool isLocked = getValue(locked) || getValue(commandLocked); bool overObject = object->drawIoletExpanded; - bool isHovering = isTargeted && !isLocked; + bool isHovering = isTargeted && !isLocked; int type = isSignal + (isGemState * 2); - if(isLocked) type = 3; - + if (isLocked) + type = 3; + nvgSave(nvg); - - if(isLocked || !(overObject || isHovering)) - { + + if (isLocked || !(overObject || isHovering)) { auto clipBounds = getLocalArea(object, object->getLocalBounds().reduced(Object::margin)); nvgIntersectScissor(nvg, clipBounds.getX(), clipBounds.getY(), clipBounds.getWidth(), clipBounds.getHeight()); } - + auto scale = getWidth() / 13.0f; nvgScale(nvg, scale, scale); // If the iolet is shrunk because there is little space, we scale it down - + nvgFillPaint(nvg, nvgImagePattern(nvg, isHovering * -16 - 1.5f, type * -16 - 0.5f, 16 * 4, 16 * 4, 0, fb.getImage(), 1)); nvgFillRect(nvg, 0, 0, 13, 13); - + nvgRestore(nvg); } @@ -131,7 +132,7 @@ void Iolet::mouseDrag(MouseEvent const& e) if (nearest && cnv->nearestIolet != nearest) { nearest->isTargeted = true; cnv->editor->tooltipWindow.displayTip(nearest->getScreenPosition(), nearest->getTooltip()); - + if (cnv->nearestIolet) { cnv->nearestIolet->isTargeted = false; cnv->nearestIolet->repaint(); @@ -157,7 +158,7 @@ void Iolet::mouseDown(MouseEvent const& e) void Iolet::mouseUp(MouseEvent const& e) { mouseIsDown = false; - + if (getValue(locked) || e.mods.isRightButtonDown()) return; @@ -306,9 +307,8 @@ void Iolet::mouseEnter(MouseEvent const& e) { isTargeted = true; object->drawIoletExpanded = true; - - if(cnv->connectionsBeingCreated.size() == 1) - { + + if (cnv->connectionsBeingCreated.size() == 1) { cnv->editor->tooltipWindow.displayTip(getScreenPosition(), getTooltip()); } @@ -320,12 +320,11 @@ void Iolet::mouseExit(MouseEvent const& e) { isTargeted = false; object->drawIoletExpanded = false; - - if(cnv->connectionsBeingCreated.size() == 1) - { + + if (cnv->connectionsBeingCreated.size() == 1) { cnv->editor->tooltipWindow.hideTip(); } - + for (auto& iolet : object->iolets) iolet->repaint(); } diff --git a/Source/Iolet.h b/Source/Iolet.h index 03b9eded44..8042d6b22d 100644 --- a/Source/Iolet.h +++ b/Source/Iolet.h @@ -26,7 +26,7 @@ class Iolet : public Component void mouseExit(MouseEvent const& e) override; bool hitTest(int x, int y) override; - + void render(NVGcontext* nvg) override; void valueChanged(Value& v) override; diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index 7a7ec541e5..88bdead6fd 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -271,9 +271,8 @@ void PlugDataLook::fillResizableWindowBackground(Graphics& g, int w, int h, Bord void PlugDataLook::drawCallOutBoxBackground(CallOutBox& box, Graphics& g, Path const& path, Image& cachedImage) { - - if(!ProjectInfo::canUseSemiTransparentWindows()) - { + + if (!ProjectInfo::canUseSemiTransparentWindows()) { auto bounds = path.getBounds(); g.setColour(box.findColour(PlugDataColour::popupMenuBackgroundColourId)); g.fillRect(bounds); @@ -578,38 +577,36 @@ void PlugDataLook::drawPopupMenuItem(Graphics& g, Rectangle const& area, r.removeFromRight(3); Fonts::drawFittedText(g, text, r, colour); - + auto shortcutBounds = r.translated(-4, 0); - + #if JUCE_MAC - for(int i = shortcutKeyText.length() - 1; i >= 0; i--) - { + for (int i = shortcutKeyText.length() - 1; i >= 0; i--) { auto font = Fonts::getSemiBoldFont().withHeight(10.5f); - auto text = shortcutKeyText.substring(i, i+1); + auto text = shortcutKeyText.substring(i, i + 1); auto width = std::max(font.getStringWidth(text) + 4, 16); auto b = shortcutBounds.removeFromRight(width).toFloat().reduced(1.0f, 5.0f).translated(1.5f, 0.5f); - + g.setColour(findColour(PlugDataColour::popupMenuTextColourId).withAlpha(isActive ? 0.9f : 0.35f)); g.fillRoundedRectangle(b.toFloat(), 3.0f); - + g.setColour(findColour(PlugDataColour::popupMenuBackgroundColourId)); - + g.setFont(Fonts::getSemiBoldFont().withHeight(11)); g.drawText(text, b, Justification::centred); } #else auto keys = StringArray::fromTokens(shortcutKeyText, "+", ""); - for(int i = keys.size() - 1; i >= 0; i--) - { + for (int i = keys.size() - 1; i >= 0; i--) { auto font = Fonts::getSemiBoldFont().withHeight(10.5f); auto width = std::max(font.getStringWidth(keys[i].trim()) + 8, 15); auto b = shortcutBounds.removeFromRight(width).reduced(1, 5); - + g.setColour(findColour(PlugDataColour::popupMenuTextColourId).withAlpha(isActive ? 0.9f : 0.35f)); g.fillRoundedRectangle(b.toFloat(), 3.0f); - + g.setColour(findColour(PlugDataColour::popupMenuBackgroundColourId)); - + g.setFont(font); g.drawText(keys[i], b, Justification::centred); } @@ -988,84 +985,80 @@ void PlugDataLook::setColours(std::map colours) colours.at(PlugDataColour::outlineColourId)); setColour(AlertWindow::textColourId, colours.at(PlugDataColour::panelTextColourId)); - + setColour(Slider::textBoxOutlineColourId, Colours::transparentBlack); setColour(TreeView::backgroundColourId, Colours::transparentBlack); } -void PlugDataLook::drawAlertBox (Graphics& g, AlertWindow& alert, - const Rectangle& textArea, TextLayout& textLayout) +void PlugDataLook::drawAlertBox(Graphics& g, AlertWindow& alert, + Rectangle const& textArea, TextLayout& textLayout) { auto cornerSize = Corners::largeCornerRadius; - - g.setColour (alert.findColour (PlugDataColour::outlineColourId)); - g.drawRoundedRectangle (alert.getLocalBounds().toFloat(), cornerSize, 1.0f); - - auto bounds = alert.getLocalBounds().reduced (1); - g.reduceClipRegion (bounds); - - g.setColour (alert.findColour (PlugDataColour::dialogBackgroundColourId)); - g.fillRoundedRectangle (bounds.toFloat(), cornerSize); - + + g.setColour(alert.findColour(PlugDataColour::outlineColourId)); + g.drawRoundedRectangle(alert.getLocalBounds().toFloat(), cornerSize, 1.0f); + + auto bounds = alert.getLocalBounds().reduced(1); + g.reduceClipRegion(bounds); + + g.setColour(alert.findColour(PlugDataColour::dialogBackgroundColourId)); + g.fillRoundedRectangle(bounds.toFloat(), cornerSize); + auto iconSpaceUsed = 0; - + auto iconWidth = 80; - auto iconSize = jmin (iconWidth + 50, bounds.getHeight() + 20); - + auto iconSize = jmin(iconWidth + 50, bounds.getHeight() + 20); + if (alert.containsAnyExtraComponents() || alert.getNumButtons() > 2) - iconSize = jmin (iconSize, textArea.getHeight() + 50); - - Rectangle iconRect (iconSize / -10, iconSize / -10, - iconSize, iconSize); - - if (alert.getAlertType() != MessageBoxIconType::NoIcon) - { + iconSize = jmin(iconSize, textArea.getHeight() + 50); + + Rectangle iconRect(iconSize / -10, iconSize / -10, + iconSize, iconSize); + + if (alert.getAlertType() != MessageBoxIconType::NoIcon) { Path icon; char character; uint32 colour; - - if (alert.getAlertType() == MessageBoxIconType::WarningIcon) - { + + if (alert.getAlertType() == MessageBoxIconType::WarningIcon) { character = '!'; - - icon.addTriangle ((float) iconRect.getX() + (float) iconRect.getWidth() * 0.5f, (float) iconRect.getY(), - static_cast (iconRect.getRight()), static_cast (iconRect.getBottom()), - static_cast (iconRect.getX()), static_cast (iconRect.getBottom())); - - icon = icon.createPathWithRoundedCorners (5.0f); + + icon.addTriangle((float)iconRect.getX() + (float)iconRect.getWidth() * 0.5f, (float)iconRect.getY(), + static_cast(iconRect.getRight()), static_cast(iconRect.getBottom()), + static_cast(iconRect.getX()), static_cast(iconRect.getBottom())); + + icon = icon.createPathWithRoundedCorners(5.0f); colour = 0x66ff2a00; - } - else - { - colour = Colour (0xff00b0b9).withAlpha (0.4f).getARGB(); + } else { + colour = Colour(0xff00b0b9).withAlpha(0.4f).getARGB(); character = alert.getAlertType() == MessageBoxIconType::InfoIcon ? 'i' : '?'; - - icon.addEllipse (iconRect.toFloat()); + + icon.addEllipse(iconRect.toFloat()); } - + GlyphArrangement ga; - ga.addFittedText ({ (float) iconRect.getHeight() * 0.9f, Font::bold }, - String::charToString ((juce_wchar) (uint8) character), - static_cast (iconRect.getX()), static_cast (iconRect.getY()), - static_cast (iconRect.getWidth()), static_cast (iconRect.getHeight()), - Justification::centred, false); - ga.createPath (icon); - - icon.setUsingNonZeroWinding (false); - g.setColour (Colour (colour)); - g.fillPath (icon); - + ga.addFittedText({ (float)iconRect.getHeight() * 0.9f, Font::bold }, + String::charToString((juce_wchar)(uint8)character), + static_cast(iconRect.getX()), static_cast(iconRect.getY()), + static_cast(iconRect.getWidth()), static_cast(iconRect.getHeight()), + Justification::centred, false); + ga.createPath(icon); + + icon.setUsingNonZeroWinding(false); + g.setColour(Colour(colour)); + g.fillPath(icon); + iconSpaceUsed = iconWidth; } - - g.setColour (alert.findColour (AlertWindow::textColourId)); - - Rectangle alertBounds (bounds.getX() + iconSpaceUsed, 30, - bounds.getWidth(), bounds.getHeight() - getAlertWindowButtonHeight() - 20); - - textLayout.draw (g, alertBounds.toFloat()); + + g.setColour(alert.findColour(AlertWindow::textColourId)); + + Rectangle alertBounds(bounds.getX() + iconSpaceUsed, 30, + bounds.getWidth(), bounds.getHeight() - getAlertWindowButtonHeight() - 20); + + textLayout.draw(g, alertBounds.toFloat()); } void PlugDataLook::setDefaultFont(String const& fontName) diff --git a/Source/LookAndFeel.h b/Source/LookAndFeel.h index 79adc94834..268950a35d 100644 --- a/Source/LookAndFeel.h +++ b/Source/LookAndFeel.h @@ -28,7 +28,7 @@ inline std::map> const PlugDa { gemColourId, { "Gem", "gem_colour", "Canvas" } }, { graphAreaColourId, { "Graph resizer", "graph_area", "Canvas" } }, { gridLineColourId, { "Grid line", "grid_colour", "Canvas" } }, - + { guiObjectBackgroundColourId, { "GUI object background", "default_object_background", "Object" } }, { guiObjectInternalOutlineColour, { "GUI object internal outline colour", "gui_internal_outline_colour", "Object" } }, { textObjectBackgroundColourId, { "Object background", "text_object_background", "Object" } }, @@ -47,7 +47,7 @@ inline std::map> const PlugDa { caretColourId, { "Text editor caret", "caret_colour", "Other" } }, { toolbarOutlineColourId, { "Outline", "toolbar_outline_colour", "Other" } }, { scrollbarThumbColourId, { "Scrollbar thumb", "scrollbar_thumb", "Other" } }, - + { levelMeterActiveColourId, { "Level meter active", "levelmeter_active", "Level Meter" } }, { levelMeterBackgroundColourId, { "Level meter track", "levelmeter_background", "Level Meter" } }, { levelMeterThumbColourId, { "Level meter thumb", "levelmeter_thumb", "Level Meter" } }, @@ -56,7 +56,7 @@ inline std::map> const PlugDa { panelForegroundColourId, { "Panel foreground", "panel_foreground", "Properties Panel" } }, { panelTextColourId, { "Panel text", "panel_text", "Properties Panel" } }, { panelActiveBackgroundColourId, { "Panel background active", "panel_background_active", "Properties Panel" } }, - + { sidebarBackgroundColourId, { "Sidebar background", "sidebar_colour", "Sidebar" } }, { sidebarTextColourId, { "Sidebar text", "sidebar_text", "Sidebar" } }, { sidebarActiveBackgroundColourId, { "Sidebar background active", "sidebar_background_active", "Sidebar" } }, @@ -71,7 +71,7 @@ struct PlugDataLook : public LookAndFeel_V4 { void fillResizableWindowBackground(Graphics& g, int w, int h, BorderSize const& border, ResizableWindow& window) override; - void drawResizableWindowBorder(Graphics&, int w, int h, BorderSize const& border, ResizableWindow&) override {} + void drawResizableWindowBorder(Graphics&, int w, int h, BorderSize const& border, ResizableWindow&) override { } void drawCallOutBoxBackground(CallOutBox& box, Graphics& g, Path const& path, Image& cachedImage) override; @@ -133,9 +133,9 @@ struct PlugDataLook : public LookAndFeel_V4 { void drawCornerResizer(Graphics& g, int w, int h, bool isMouseOver, bool isMouseDragging) override; void drawLasso(Graphics& g, Component& lassoComp) override; - - void drawAlertBox (Graphics& g, AlertWindow& alert, - const Rectangle& textArea, TextLayout& textLayout) override; + + void drawAlertBox(Graphics& g, AlertWindow& alert, + Rectangle const& textArea, TextLayout& textLayout) override; void drawTooltip(Graphics& g, String const& text, int width, int height) override; diff --git a/Source/NVGSurface.cpp b/Source/NVGSurface.cpp index ce839eb126..b604872ccd 100644 --- a/Source/NVGSurface.cpp +++ b/Source/NVGSurface.cpp @@ -10,8 +10,8 @@ using namespace juce::gl; #include #ifdef NANOVG_GL_IMPLEMENTATION -#include -#include +# include +# include #endif #include "NVGSurface.h" @@ -22,23 +22,22 @@ using namespace juce::gl; #define ENABLE_FPS_COUNT 0 -class FrameTimer -{ +class FrameTimer { public: FrameTimer() { startTime = getNow(); prevTime = startTime; } - + void render(NVGcontext* nvg) { nvgFillColor(nvg, nvgRGBA(40, 40, 40, 255)); nvgFillRect(nvg, 0, 0, 40, 22); - + nvgFontSize(nvg, 20.0f); - nvgTextAlign(nvg,NVG_ALIGN_LEFT|NVG_ALIGN_TOP); - nvgFillColor(nvg, nvgRGBA(240,240,240,255)); + nvgTextAlign(nvg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); + nvgFillColor(nvg, nvgRGBA(240, 240, 240, 255)); char fpsBuf[16]; snprintf(fpsBuf, 16, "%d", static_cast(round(1.0f / getAverageFrameTime()))); nvgText(nvg, 7, 2, fpsBuf, nullptr); @@ -47,19 +46,20 @@ class FrameTimer { auto timeSeconds = getTime(); auto dt = timeSeconds - prevTime; - perf_head = (perf_head+1) % 32; + perf_head = (perf_head + 1) % 32; frame_times[perf_head] = dt; prevTime = timeSeconds; } - + double getTime() { return getNow() - startTime; } + private: double getNow() { auto ticks = Time::getHighResolutionTicks(); return Time::highResolutionTicksToSeconds(ticks); } - + float getAverageFrameTime() { float avg = 0; @@ -68,14 +68,14 @@ class FrameTimer } return avg / (float)32; } - + float frame_times[32] = {}; int perf_head = 0; double startTime = 0, prevTime = 0; }; - -NVGSurface::NVGSurface(PluginEditor* e) : editor(e) +NVGSurface::NVGSurface(PluginEditor* e) + : editor(e) { #ifdef NANOVG_GL_IMPLEMENTATION glContext = std::make_unique(); @@ -83,25 +83,25 @@ NVGSurface::NVGSurface(PluginEditor* e) : editor(e) glContext->setMultisamplingEnabled(false); glContext->setSwapInterval(0); #endif - + #if ENABLE_FPS_COUNT frameTimer = std::make_unique(); #endif - + setInterceptsMouseClicks(false, false); setWantsKeyboardFocus(false); - - setSize(1,1); - + + setSize(1, 1); + // Start rendering asynchronously, so we are sure the window has been added to the desktop // kind of a hack, but works well enough - MessageManager::callAsync([_this = SafePointer(this)](){ - if(_this) { + MessageManager::callAsync([_this = SafePointer(this)]() { + if (_this) { _this->initialise(); - + // Render on vblank - _this->vBlankAttachment = std::make_unique(_this.getComponent(), [_this](){ - if(_this) { + _this->vBlankAttachment = std::make_unique(_this.getComponent(), [_this]() { + if (_this) { _this->editor->pd->messageDispatcher->dequeueMessages(); _this->render(); } @@ -122,7 +122,7 @@ void NVGSurface::initialise() auto* view = OSUtils::MTLCreateView(peer, 0, 0, getWidth(), getHeight()); setView(view); setVisible(true); - + auto renderScale = getRenderScale(); nvg = nvgCreateContext(view, NVG_ANTIALIAS | NVG_TRIPLE_BUFFER, getWidth() * renderScale, getHeight() * renderScale); resized(); @@ -133,12 +133,13 @@ void NVGSurface::initialise() glContext->makeActive(); nvg = nvgCreateContext(NVG_ANTIALIAS); #endif - + surfaces[nvg] = this; - + invalidateAll(); - - if (!nvg) std::cerr << "could not initialise nvg" << std::endl; + + if (!nvg) + std::cerr << "could not initialise nvg" << std::endl; nvgCreateFontMem(nvg, "Inter", (unsigned char*)BinaryData::InterRegular_ttf, BinaryData::InterRegular_ttfSize, 0); nvgCreateFontMem(nvg, "Inter-Regular", (unsigned char*)BinaryData::InterRegular_ttf, BinaryData::InterRegular_ttfSize, 0); nvgCreateFontMem(nvg, "Inter-Bold", (unsigned char*)BinaryData::InterBold_ttf, BinaryData::InterBold_ttfSize, 0); @@ -149,27 +150,26 @@ void NVGSurface::initialise() void NVGSurface::detachContext() { - if(makeContextActive()) { + if (makeContextActive()) { NVGFramebuffer::clearAll(nvg); NVGImage::clearAll(nvg); - - if(invalidFBO) { + + if (invalidFBO) { nvgDeleteFramebuffer(invalidFBO); invalidFBO = nullptr; } - if(mainFBO) { + if (mainFBO) { nvgDeleteFramebuffer(mainFBO); mainFBO = nullptr; } - if(nvg) - { + if (nvg) { nvgDeleteContext(nvg); nvg = nullptr; surfaces.erase(nvg); } - + #ifdef NANOVG_METAL_IMPLEMENTATION - if(auto* view = getView()) { + if (auto* view = getView()) { OSUtils::MTLDeleteView(view); setView(nullptr); } @@ -184,10 +184,12 @@ void NVGSurface::updateBufferSize() float pixelScale = getRenderScale(); int scaledWidth = getWidth() * pixelScale; int scaledHeight = getHeight() * pixelScale; - - if(fbWidth != scaledWidth || fbHeight != scaledHeight || !mainFBO) { - if(invalidFBO) nvgDeleteFramebuffer(invalidFBO); - if(mainFBO) nvgDeleteFramebuffer(mainFBO); + + if (fbWidth != scaledWidth || fbHeight != scaledHeight || !mainFBO) { + if (invalidFBO) + nvgDeleteFramebuffer(invalidFBO); + if (mainFBO) + nvgDeleteFramebuffer(mainFBO); mainFBO = nvgCreateFramebuffer(nvg, scaledWidth, scaledHeight, NVG_IMAGE_PREMULTIPLIED); invalidFBO = nvgCreateFramebuffer(nvg, scaledWidth, scaledHeight, NVG_IMAGE_PREMULTIPLIED); fbWidth = scaledWidth; @@ -196,7 +198,6 @@ void NVGSurface::updateBufferSize() } } - #ifdef NANOVG_GL_IMPLEMENTATION void NVGSurface::timerCallback() { @@ -208,7 +209,7 @@ void NVGSurface::timerCallback() void NVGSurface::lookAndFeelChanged() { - if(makeContextActive()) { + if (makeContextActive()) { NVGFramebuffer::clearAll(nvg); NVGImage::clearAll(nvg); invalidateAll(); @@ -226,12 +227,12 @@ bool NVGSurface::makeContextActive() // No need to make context active with Metal, so just check if we have initialised and return that return getView() != nullptr && nvg != nullptr; #else - if(glContext) return glContext->makeActive(); + if (glContext) + return glContext->makeActive(); return false; #endif } - float NVGSurface::getRenderScale() const { #ifdef NANOVG_METAL_IMPLEMENTATION @@ -250,7 +251,7 @@ void NVGSurface::updateBounds(Rectangle bounds) setBounds(bounds.withHeight(getHeight())); else setBounds(bounds.withWidth(getWidth())); - + resizing = true; #else setBounds(bounds); @@ -260,7 +261,7 @@ void NVGSurface::updateBounds(Rectangle bounds) void NVGSurface::resized() { #ifdef NANOVG_METAL_IMPLEMENTATION - if(auto* view = getView()) { + if (auto* view = getView()) { auto renderScale = getRenderScale(); auto* topLevel = getTopLevelComponent(); auto bounds = topLevel->getLocalArea(this, getLocalBounds()).toFloat() * renderScale; @@ -284,76 +285,77 @@ void NVGSurface::render() #if ENABLE_FPS_COUNT frameTimer->addFrameTime(); #endif - + auto startTime = Time::getMillisecondCounter(); - if(!nvg) { + if (!nvg) { initialise(); return; // Render on next frame } - - if(!makeContextActive()) return; - + + if (!makeContextActive()) + return; + updateBufferSize(); - + auto pixelScale = getRenderScale(); - if(!invalidArea.isEmpty()) { + if (!invalidArea.isEmpty()) { auto invalidated = invalidArea.expanded(1); - + // First, draw only the invalidated region to a separate framebuffer // I've found that nvgScissor doesn't always clip everything, meaning that there will be graphical glitches if we don't do this nvgBindFramebuffer(invalidFBO); nvgViewport(0, 0, getWidth() * pixelScale, getHeight() * pixelScale); nvgClear(nvg); - + nvgBeginFrame(nvg, getWidth(), getHeight(), pixelScale); - nvgScissor (nvg, invalidated.getX(), invalidated.getY(), invalidated.getWidth(), invalidated.getHeight()); + nvgScissor(nvg, invalidated.getX(), invalidated.getY(), invalidated.getWidth(), invalidated.getHeight()); editor->renderArea(nvg, invalidated); nvgEndFrame(nvg); - + nvgBindFramebuffer(mainFBO); nvgViewport(0, 0, getWidth() * pixelScale, getHeight() * pixelScale); nvgBeginFrame(nvg, getWidth(), getHeight(), pixelScale); nvgBeginPath(nvg); nvgScissor(nvg, invalidated.getX(), invalidated.getY(), invalidated.getWidth(), invalidated.getHeight()); - + nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, getWidth(), getHeight(), 0, invalidFBO->image, 1)); nvgFillRect(nvg, invalidated.getX(), invalidated.getY(), invalidated.getWidth(), invalidated.getHeight()); - + #if ENABLE_FB_DEBUGGING static Random rng; nvgFillColor(nvg, nvgRGBA(rng.nextInt(255), rng.nextInt(255), rng.nextInt(255), 0x50)); nvgFillRect(nvg, 0, 0, getWidth(), getHeight()); #endif - + nvgEndFrame(nvg); - + nvgBindFramebuffer(nullptr); needsBufferSwap = true; invalidArea = Rectangle(0, 0, 0, 0); } - - if(needsBufferSwap) { + + if (needsBufferSwap) { float pixelScale = getRenderScale(); nvgViewport(0, 0, getWidth() * pixelScale, getHeight() * pixelScale); - + nvgBeginFrame(nvg, getWidth(), getHeight(), pixelScale); - + nvgSave(nvg); nvgScissor(nvg, 0, 0, getWidth(), getHeight()); nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, getWidth(), getHeight(), 0, mainFBO->image, 1)); nvgFillRect(nvg, 0, 0, getWidth(), getHeight()); nvgRestore(nvg); - + #if ENABLE_FPS_COUNT nvgSave(nvg); frameTimer->render(nvg); nvgRestore(nvg); #endif - + nvgEndFrame(nvg); - + #ifdef NANOVG_GL_IMPLEMENTATION glContext->swapBuffers(); if (resizing) { @@ -365,21 +367,20 @@ void NVGSurface::render() #endif needsBufferSwap = false; } - + auto elapsed = Time::getMillisecondCounter() - startTime; // We update frambuffers after we call swapBuffers to make sure the frame is on time - if(elapsed < 14) { - for(auto* cnv : editor->getTabComponent().getVisibleCanvases()) - { + if (elapsed < 14) { + for (auto* cnv : editor->getTabComponent().getVisibleCanvases()) { cnv->updateFramebuffers(nvg, cnv->getLocalBounds(), 14 - elapsed); } } } - NVGSurface* NVGSurface::getSurfaceForContext(NVGcontext* nvg) { - if(!surfaces.count(nvg)) return nullptr; - + if (!surfaces.count(nvg)) + return nullptr; + return surfaces[nvg]; } diff --git a/Source/NVGSurface.h b/Source/NVGSurface.h index 6c8d844d37..87dfcb0185 100644 --- a/Source/NVGSurface.h +++ b/Source/NVGSurface.h @@ -10,165 +10,164 @@ #include using namespace juce::gl; - #include "Utility/Config.h" #include "Utility/SettingsFile.h" #include #ifdef NANOVG_GL_IMPLEMENTATION -#undef NANOVG_GL_IMPLEMENTATION -#include -#define NANOVG_GL_IMPLEMENTATION 1 +# undef NANOVG_GL_IMPLEMENTATION +# include +# define NANOVG_GL_IMPLEMENTATION 1 #endif - class FrameTimer; class PluginEditor; class NVGSurface : #if NANOVG_METAL_IMPLEMENTATION && JUCE_MAC -public NSViewComponent + public NSViewComponent #elif NANOVG_METAL_IMPLEMENTATION && JUCE_IOS -public UIViewComponent + public UIViewComponent #else -public Component, public Timer + public Component + , public Timer #endif { public: NVGSurface(PluginEditor* editor); ~NVGSurface(); - + void initialise(); void updateBufferSize(); - + void render(); - + void triggerRepaint(); bool makeContextActive(); - + void detachContext(); #ifdef NANOVG_GL_IMPLEMENTATION void timerCallback() override; #endif - + void lookAndFeelChanged() override; Rectangle getInvalidArea() { return invalidArea; } - + float getRenderScale() const; void updateBounds(Rectangle bounds); - - class InvalidationListener : public CachedComponentImage - { + + class InvalidationListener : public CachedComponentImage { public: - InvalidationListener(NVGSurface& s, Component* origin, bool passRepaintEvents = false) : surface(s), originComponent(origin), passEvents(passRepaintEvents) + InvalidationListener(NVGSurface& s, Component* origin, bool passRepaintEvents = false) + : surface(s) + , originComponent(origin) + , passEvents(passRepaintEvents) { } - + void paint(Graphics& g) override {}; - - bool invalidate (const Rectangle& rect) override + + bool invalidate(Rectangle const& rect) override { - if(originComponent->isVisible()) { + if (originComponent->isVisible()) { // Translate from canvas coords to viewport coords as float to prevent rounding errors auto invalidatedBounds = surface.getLocalArea(originComponent, rect.toFloat()).getSmallestIntegerContainer(); surface.invalidateArea(invalidatedBounds); } return passEvents; } - + bool invalidateAll() override - { - if(originComponent->isVisible()) { + { + if (originComponent->isVisible()) { surface.invalidateArea(originComponent->getLocalBounds()); } return passEvents; } - + void releaseResources() override {}; - - + NVGSurface& surface; Component* originComponent; bool passEvents; }; - + void invalidateArea(Rectangle area); void invalidateAll(); - + NVGcontext* getRawContext() { return nvg; } static NVGSurface* getSurfaceForContext(NVGcontext*); - -private: +private: void resized() override; - + PluginEditor* editor; NVGcontext* nvg = nullptr; bool needsBufferSwap = false; std::unique_ptr vBlankAttachment; - + Rectangle invalidArea; NVGframebuffer* mainFBO = nullptr; NVGframebuffer* invalidFBO = nullptr; int fbWidth = 0, fbHeight = 0; - + static inline std::map surfaces; - + bool hresize = false; bool resizing = false; Rectangle newBounds; - + #if NANOVG_GL_IMPLEMENTATION std::unique_ptr glContext; #endif - + std::unique_ptr frameTimer; }; -class NVGComponent -{ +class NVGComponent { public: - NVGComponent(Component* comp) : component(*comp) {} - + NVGComponent(Component* comp) + : component(*comp) + { + } + static NVGcolor convertColour(Colour c) { return nvgRGBA(c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); } - + NVGcolor findNVGColour(int colourId) { return convertColour(component.findColour(colourId)); } - + static void setJUCEPath(NVGcontext* nvg, Path const& p) { - Path::Iterator i (p); + Path::Iterator i(p); - nvgBeginPath (nvg); + nvgBeginPath(nvg); - while (i.next()) - { - switch (i.elementType) - { - case Path::Iterator::startNewSubPath: - nvgBeginPath (nvg); - nvgMoveTo (nvg, i.x1, i.y1); - break; - case Path::Iterator::lineTo: - nvgLineTo (nvg, i.x1, i.y1); - break; - case Path::Iterator::quadraticTo: - nvgQuadTo (nvg, i.x1, i.y1, i.x2, i.y2); - break; - case Path::Iterator::cubicTo: - nvgBezierTo (nvg, i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); - break; - case Path::Iterator::closePath: - nvgClosePath (nvg); - break; + while (i.next()) { + switch (i.elementType) { + case Path::Iterator::startNewSubPath: + nvgBeginPath(nvg); + nvgMoveTo(nvg, i.x1, i.y1); + break; + case Path::Iterator::lineTo: + nvgLineTo(nvg, i.x1, i.y1); + break; + case Path::Iterator::quadraticTo: + nvgQuadTo(nvg, i.x1, i.y1, i.x2, i.y2); + break; + case Path::Iterator::cubicTo: + nvgBezierTo(nvg, i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); + break; + case Path::Iterator::closePath: + nvgClosePath(nvg); + break; default: break; } @@ -176,17 +175,15 @@ class NVGComponent } virtual void render(NVGcontext*) {}; - + private: Component& component; - + JUCE_DECLARE_WEAK_REFERENCEABLE(NVGComponent) }; -class NVGImage -{ +class NVGImage { public: - NVGImage(NVGcontext* nvg, int width, int height, std::function renderCall) { Image image = Image(Image::ARGB, width, height, true); @@ -200,7 +197,7 @@ class NVGImage { allImages.insert(this); } - + NVGImage(NVGImage& other) { // Check for self-assignment @@ -209,12 +206,12 @@ class NVGImage imageId = other.imageId; imageWidth = other.imageWidth; imageHeight = other.imageHeight; - + other.imageId = 0; allImages.insert(this); } } - + NVGImage& operator=(NVGImage&& other) noexcept { // Check for self-assignment @@ -223,22 +220,21 @@ class NVGImage imageId = other.imageId; imageWidth = other.imageWidth; imageHeight = other.imageHeight; - + other.imageId = 0; // Important, makes sure the old buffer can't delete this buffer allImages.insert(this); } - + return *this; } - + ~NVGImage() { - if(imageId && nvg) { - if(auto* surface = NVGSurface::getSurfaceForContext(nvg)) - { + if (imageId && nvg) { + if (auto* surface = NVGSurface::getSurfaceForContext(nvg)) { surface->makeContextActive(); } - + nvgDeleteImage(nvg, imageId); } allImages.erase(this); @@ -246,9 +242,8 @@ class NVGImage static void clearAll(NVGcontext* nvg) { - for(auto* image : allImages) - { - if(image->isValid() && image->nvg == nvg) { + for (auto* image : allImages) { + if (image->isValid() && image->nvg == nvg) { nvgDeleteImage(image->nvg, image->imageId); image->imageId = 0; if (image->onImageInvalidate) @@ -256,23 +251,24 @@ class NVGImage } } } - + bool isValid() { return imageId != 0; } - + void renderJUCEComponent(NVGcontext* nvg, Component& component, float scale) { Image componentImage = component.createComponentSnapshot(Rectangle(0, 0, component.getWidth(), component.getHeight()), false, scale); - if(componentImage.isNull()) return; - + if (componentImage.isNull()) + return; + loadJUCEImage(nvg, componentImage); nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, component.getWidth(), component.getHeight(), 0, imageId, 1.0f)); nvgFillRect(nvg, 0, 0, component.getWidth(), component.getHeight()); } - + void loadJUCEImage(NVGcontext* context, Image& image) { Image::BitmapData imageData(image, Image::BitmapData::readOnly); @@ -280,13 +276,11 @@ class NVGImage int width = imageData.width; int height = imageData.height; uint8* pixelData = imageData.data; - - for (int y = 0; y < height; ++y) - { - auto* scanLine = (uint32*) imageData.getLinePointer(y); - for (int x = 0; x < width; ++x) - { + for (int y = 0; y < height; ++y) { + auto* scanLine = (uint32*)imageData.getLinePointer(y); + + for (int x = 0; x < width; ++x) { uint32 argb = scanLine[x]; uint8 a = argb >> 24; @@ -298,112 +292,108 @@ class NVGImage scanLine[x] = (a << 24) | (b << 16) | (g << 8) | r; } } - - if(imageId && imageWidth == width && imageHeight == height && nvg == context) - { + + if (imageId && imageWidth == width && imageHeight == height && nvg == context) { nvgUpdateImage(nvg, imageId, pixelData); - } - else { + } else { nvg = context; - imageId = nvgCreateImageRGBA(nvg, width, height, NVG_IMAGE_PREMULTIPLIED, pixelData); + imageId = nvgCreateImageRGBA(nvg, width, height, NVG_IMAGE_PREMULTIPLIED, pixelData); imageWidth = width; imageHeight = height; } } - + void render(NVGcontext* nvg, Rectangle b) { - if(imageId) { + if (imageId) { nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, b.getWidth(), b.getHeight(), 0, imageId, 1)); - nvgFillRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight()); + nvgFillRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight()); } } - + bool needsUpdate(int width, int height) { return imageId == 0 || width != imageWidth || height != imageHeight; } - + int getImageId() { return imageId; } - + NVGcontext* nvg = nullptr; int imageId = 0; int imageWidth = 0, imageHeight = 0; std::function onImageInvalidate = nullptr; - + static inline std::set allImages; }; -class NVGFramebuffer -{ +class NVGFramebuffer { public: NVGFramebuffer() { allFramebuffers.insert(this); } - + ~NVGFramebuffer() { - if(fb) { - if(auto* surface = NVGSurface::getSurfaceForContext(nvg)) - { + if (fb) { + if (auto* surface = NVGSurface::getSurfaceForContext(nvg)) { surface->makeContextActive(); } - + nvgDeleteFramebuffer(fb); fb = nullptr; } allFramebuffers.erase(this); } - + static void clearAll(NVGcontext* nvg) { - for(auto* buffer : allFramebuffers) - { - if(buffer->nvg == nvg && buffer->fb) { + for (auto* buffer : allFramebuffers) { + if (buffer->nvg == nvg && buffer->fb) { nvgDeleteFramebuffer(buffer->fb); buffer->fb = nullptr; } } } - + bool needsUpdate(int width, int height) { return fb == nullptr || width != fbWidth || height != fbHeight || fbDirty; } - + bool isValid() { return fb != nullptr; } - + void setDirty() { fbDirty = true; } - + void bind(NVGcontext* ctx, int width, int height) { - if(!fb || fbWidth != width || fbHeight != height) { + if (!fb || fbWidth != width || fbHeight != height) { nvg = ctx; - if(fb) nvgDeleteFramebuffer(fb); + if (fb) + nvgDeleteFramebuffer(fb); fb = nvgCreateFramebuffer(nvg, width, height, NVG_IMAGE_PREMULTIPLIED); fbWidth = width; fbHeight = height; } - + nvgBindFramebuffer(fb); } - + void unbind() { nvgBindFramebuffer(nullptr); } - + void renderToFramebuffer(NVGcontext* nvg, int width, int height, std::function renderCallback) { bind(nvg, width, height); @@ -411,25 +401,26 @@ class NVGFramebuffer unbind(); fbDirty = false; } - + void render(NVGcontext* nvg, Rectangle b) { - if(fb) { + if (fb) { nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, b.getWidth(), b.getHeight(), 0, fb->image, 1)); - nvgFillRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight()); + nvgFillRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight()); } } - + int getImage() { - if(!fb) return -1; - + if (!fb) + return -1; + return fb->image; } - + private: static inline std::set allFramebuffers; - + NVGcontext* nvg; NVGframebuffer* fb = nullptr; int fbWidth, fbHeight; diff --git a/Source/Object.cpp b/Source/Object.cpp index 0a3ad86db4..faa92888f9 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -92,30 +92,30 @@ void Object::setObjectBounds(Rectangle bounds) } // CachedComponentImage that will block repaint messages to parent when scrolling/zooming, and keeps track of invaldation while scrolling/zooming -class InvalidationListener : public CachedComponentImage -{ +class InvalidationListener : public CachedComponentImage { public: - InvalidationListener(Object* parent) : object(parent) + InvalidationListener(Object* parent) + : object(parent) { } + private: - - void paint(Graphics& g) override {} - - bool invalidate(const Rectangle& rect) override + void paint(Graphics& g) override { } + + bool invalidate(Rectangle const& rect) override { object->scrollBuffer.setDirty(); return true; } - + bool invalidateAll() override { object->scrollBuffer.setDirty(); return true; } - - void releaseResources() override {} - + + void releaseResources() override { } + Object* object; }; @@ -138,9 +138,9 @@ void Object::initialise() hvccMode.addListener(this); originalBounds.setBounds(0, 0, 0, 0); - + setAccessible(false); // TODO: implement accessibility. We disable default, since it makes stuff slow on macOS - + setCachedComponentImage(new InvalidationListener(this)); } @@ -194,7 +194,7 @@ void Object::valueChanged(Value& v) } } // FIXME: any value change triggers a repaint! - //repaint(); + // repaint(); } bool Object::checkIfHvccCompatible() const @@ -345,8 +345,9 @@ void Object::applyBounds() } void Object::updateBounds() { - if(cnv->isGraph && gui && gui->hideInGraph()) return; - + if (cnv->isGraph && gui && gui->hideInGraph()) + return; + // only update if we have a gui and the object isn't been moved by the user // otherwise PD hasn't been informed of the new position 'while' we are dragging // so we don't need to update the bounds when an object is being interacted with @@ -401,10 +402,10 @@ void Object::setType(String const& newType, pd::WeakReference existingObject) jassertfalse; return; } - + // Create gui for the object gui.reset(ObjectBase::createGui(objectPtr, this)); - + isGemObject = is_gem_object(gui->getText().toRawUTF8()); if (gui) { @@ -481,8 +482,10 @@ Array> Object::getCorners() const String Object::getType(bool withOriginPrefix) const { - if(gui && withOriginPrefix) return gui->getTypeWithOriginPrefix(); - else if(gui) return gui->getType(); + if (gui && withOriginPrefix) + return gui->getTypeWithOriginPrefix(); + else if (gui) + return gui->getType(); return String(); } @@ -510,14 +513,15 @@ void Object::triggerOverlayActiveState() void Object::lookAndFeelChanged() { activityOverlayDirty = true; - if(gui) gui->updateLabel(); + if (gui) + gui->updateLabel(); } void Object::resized() { scrollBuffer.setDirty(); activityOverlayDirty = true; - + setVisible(!((cnv->isGraph || cnv->presentationMode == var(true)) && gui && gui->hideInGraph())); if (gui) { @@ -527,7 +531,7 @@ void Object::resized() if (newObjectEditor) { newObjectEditor->setBounds(getLocalBounds().reduced(margin)); } - + #if JUCE_IOS int ioletSize = 15; #else @@ -578,13 +582,12 @@ void Object::updateTooltips() { if (!gui || cnv->isGraph) return; - - + auto objectInfo = cnv->pd->objectLibrary->getObjectInfo(gui->getTypeWithOriginPrefix()); - + std::array ioletTooltips; - - if(objectInfo.isValid()) { + + if (objectInfo.isValid()) { // Set object tooltip gui->setTooltip(objectInfo.getProperty("description").toString()); // Check pd library for pddp tooltips, those have priority @@ -601,12 +604,10 @@ void Object::updateTooltips() auto* iolet = iolets[i]; auto& tooltip = ioletTooltips[!iolet->isInlet][iolet->isInlet ? i : i - numInputs]; - if(tooltip.startsWith("(gemlist)")) - { + if (tooltip.startsWith("(gemlist)")) { iolet->isGemState = true; iolet->setTooltip("(gemlist)"); - } - else { + } else { if (tooltip.isNotEmpty()) { // Don't overwrite custom documentation if there is no md documentation iolet->setTooltip(tooltip); } @@ -616,7 +617,7 @@ void Object::updateTooltips() std::vector> inletMessages; std::vector> outletMessages; - + if (auto subpatch = gui->getPatch()) { cnv->pd->lockAudioThread(); auto* subpatchPtr = subpatch->getPointer().get(); @@ -705,7 +706,7 @@ void Object::updateIolets() numInputs = pd::Interface::numInlets(ptr); numOutputs = pd::Interface::numOutlets(ptr); } - + // Looking up tooltips takes a bit of time, so we make sure we're not constantly updating them for no reason bool tooltipsNeedUpdate = gui->getPatch() != nullptr || numInputs != oldNumInputs || numOutputs != oldNumOutputs || isGemObject; @@ -749,7 +750,8 @@ void Object::updateIolets() numOut += !input; } - if(tooltipsNeedUpdate) updateTooltips(); + if (tooltipsNeedUpdate) + updateTooltips(); resized(); } @@ -760,7 +762,8 @@ void Object::mouseDown(MouseEvent const& e) if (e.mods.isRightButtonDown() && !cnv->isGraph) { PopupMenu::dismissAllActiveMenus(); if (!getValue(locked)) { - if(!e.mods.isAnyModifierKeyDown() && !e.mods.isRightButtonDown()) cnv->deselectAll(); + if (!e.mods.isAnyModifierKeyDown() && !e.mods.isRightButtonDown()) + cnv->deselectAll(); cnv->setSelected(this, true); } Dialogs::showCanvasRightClickMenu(cnv, this, e.getScreenPosition()); @@ -907,7 +910,7 @@ void Object::mouseUp(MouseEvent const& e) isInsideUndoSequence = false; cnv->patch.endUndoSequence("Drag"); } - + cnv->needsSearchUpdate = true; } @@ -918,7 +921,7 @@ void Object::mouseDrag(MouseEvent const& e) if (cnv->objectRateReducer.tooFast()) return; - + #if JUCE_MAC || JUCE_WINDOWS beginDragAutoRepeat(25); // Doing this leads to terrible performance on Linux, unfortunately #endif @@ -1046,8 +1049,8 @@ void Object::mouseDrag(MouseEvent const& e) object->setTopLeftPosition(newPosition); } - - if(cnv->viewport) { + + if (cnv->viewport) { cnv->autoscroll(e.getEventRelativeTo(cnv->viewport.get())); } } @@ -1150,7 +1153,7 @@ void Object::mouseDrag(MouseEvent const& e) if (object->numInputs && object->numOutputs && !object->iolets.isEmpty()) { bool intersected = false; for (auto* connection : cnv->connections) { - + if (connection->intersectsRectangle(object->iolets[0]->getCanvasBounds())) { object->iolets[0]->isTargeted = true; object->iolets[object->numInputs]->isTargeted = true; @@ -1178,30 +1181,30 @@ void Object::updateFramebuffer(NVGcontext* nvg) { // For very large objects, buffering is just gonna take up GPU memory, with minimal performance benefits // Also, Metal has a limitation on image size, so this will also prevent crashing - if(getWidth() * 3 * cnv->getRenderScale() > 8192 || getHeight() * 3 * cnv->getRenderScale() > 8192) return; - + if (getWidth() * 3 * cnv->getRenderScale() > 8192 || getHeight() * 3 * cnv->getRenderScale() > 8192) + return; + auto b = getLocalBounds(); auto maxScale = 3.0f; int scaledWidth = b.getWidth() * maxScale * cnv->getRenderScale(); int scaledHeight = b.getHeight() * maxScale * cnv->getRenderScale(); - - if(scrollBuffer.needsUpdate(scaledWidth, scaledHeight)) - { - scrollBuffer.renderToFramebuffer(nvg, scaledWidth, scaledHeight, [this, scaledWidth, scaledHeight, maxScale, b](NVGcontext* nvg){ + + if (scrollBuffer.needsUpdate(scaledWidth, scaledHeight)) { + scrollBuffer.renderToFramebuffer(nvg, scaledWidth, scaledHeight, [this, scaledWidth, scaledHeight, maxScale, b](NVGcontext* nvg) { nvgViewport(0, 0, scaledWidth, scaledHeight); nvgClear(nvg); nvgBeginFrame(nvg, b.getWidth() * maxScale, b.getHeight() * maxScale, cnv->getRenderScale()); nvgScale(nvg, maxScale, maxScale); - nvgScissor (nvg, 0, 0, b.getWidth(), b.getHeight()); - + nvgScissor(nvg, 0, 0, b.getWidth(), b.getHeight()); + performRender(nvg); - - #if ENABLE_OBJECT_FB_DEBUGGING + +#if ENABLE_OBJECT_FB_DEBUGGING static Random rng; nvgFillColor(nvg, nvgRGBA(rng.nextInt(255), rng.nextInt(255), rng.nextInt(255), 0x50)); nvgFillRect(nvg, 0, 0, b.getWidth(), b.getHeight()); - #endif +#endif nvgEndFrame(nvg); }); } @@ -1209,23 +1212,18 @@ void Object::updateFramebuffer(NVGcontext* nvg) void Object::render(NVGcontext* nvg) { - if(cnv->shouldShowObjectActivity() && (!activityOverlayImage.isValid() || activityOverlayDirty)) - { + if (cnv->shouldShowObjectActivity() && (!activityOverlayImage.isValid() || activityOverlayDirty)) { Path objectShadow; objectShadow.addRoundedRectangle(getLocalBounds().reduced(Object::margin - 1), Corners::objectCornerRadius); activityOverlayImage = StackShadow::createActivityDropShadowImage(nvg, getLocalBounds(), objectShadow, getLookAndFeel().findColour(PlugDataColour::dataColourId), 5.5f, { 0, 0 }, 0, gui && (gui->getCanvas() || gui->isTransparent())); activityOverlayDirty = false; } - - if(cnv->isScrolling && scrollBuffer.needsUpdate(getWidth() * 3.0f * cnv->getRenderScale(), getHeight() * 3.0f * cnv->getRenderScale())) - { + + if (cnv->isScrolling && scrollBuffer.needsUpdate(getWidth() * 3.0f * cnv->getRenderScale(), getHeight() * 3.0f * cnv->getRenderScale())) { performRender(nvg); - } - else if(cnv->isScrolling && scrollBuffer.isValid()) - { + } else if (cnv->isScrolling && scrollBuffer.isValid()) { scrollBuffer.render(nvg, Rectangle(0, 0, getWidth(), getHeight())); - } - else { + } else { performRender(nvg); } } @@ -1239,14 +1237,13 @@ void Object::performRender(NVGcontext* nvg) if (selectedFlag) { auto& resizeHandleImage = cnv->resizeHandleImage; int angle = 360; - for(auto& corner : getCorners()) - { + for (auto& corner : getCorners()) { nvgSave(nvg); // Rotate around centre nvgTranslate(nvg, corner.getCentreX(), corner.getCentreY()); nvgRotate(nvg, degreesToRadians(angle)); nvgTranslate(nvg, -4.5f, -4.5f); - + nvgBeginPath(nvg); nvgRect(nvg, 0, 0, 9, 9); nvgFillPaint(nvg, nvgImagePattern(nvg, 0, 0, 9, 9, 0, resizeHandleImage.getImageId(), 1)); @@ -1255,32 +1252,30 @@ void Object::performRender(NVGcontext* nvg) angle -= 90; } } - - if(cnv->shouldShowObjectActivity() && !approximatelyEqual(activeStateAlpha, 0.0f) && activityOverlayImage.isValid()) - { + + if (cnv->shouldShowObjectActivity() && !approximatelyEqual(activeStateAlpha, 0.0f) && activityOverlayImage.isValid()) { nvgFillPaint(nvg, nvgImagePattern(nvg, lb.getX(), lb.getY(), lb.getWidth(), lb.getHeight(), 0, activityOverlayImage.getImageId(), activeStateAlpha)); nvgFillRect(nvg, lb.getX(), lb.getY(), lb.getWidth(), lb.getHeight()); } - + if (gui && gui->isTransparent() && !getValue(locked) && !cnv->isGraph) { nvgBeginPath(nvg); nvgFillColor(nvg, convertColour(getLookAndFeel().findColour(PlugDataColour::canvasBackgroundColourId).contrasting(0.35f).withAlpha(0.1f))); nvgRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), Corners::objectCornerRadius); nvgFill(nvg); } - - if(gui) { + + if (gui) { nvgSave(nvg); nvgTranslate(nvg, margin, margin); gui->render(nvg); nvgRestore(nvg); } - - if(newObjectEditor) - { + + if (newObjectEditor) { auto backgroundColour = convertColour(getLookAndFeel().findColour(PlugDataColour::textObjectBackgroundColourId)); auto outlineColour = convertColour(getLookAndFeel().findColour(PlugDataColour::objectOutlineColourId)); - + nvgBeginPath(nvg); nvgRoundedRect(nvg, b.getX(), b.getY(), b.getWidth(), b.getHeight(), Corners::objectCornerRadius); nvgFillColor(nvg, backgroundColour); @@ -1288,20 +1283,20 @@ void Object::performRender(NVGcontext* nvg) nvgStrokeWidth(nvg, 1.f); nvgStrokeColor(nvg, isSelected() ? selectedOutlineColour : outlineColour); nvgStroke(nvg); - + nvgTranslate(nvg, margin, margin); textEditorRenderer.renderJUCEComponent(nvg, *newObjectEditor, getValue(cnv->zoomScale) * cnv->getRenderScale()); } - + // If autoconnect is about to happen, draw a fake inlet with a dotted outline if (getValue(editor->autoconnect) && isInitialEditorShown() && cnv->lastSelectedObject && cnv->lastSelectedObject != this && cnv->lastSelectedObject->numOutputs) { auto outlet = cnv->lastSelectedObject->iolets[cnv->lastSelectedObject->numInputs]; - float fakeInletBounds[4] = {16.0f, 4.0f, 8.0f, 8.0f}; + float fakeInletBounds[4] = { 16.0f, 4.0f, 8.0f, 8.0f }; nvgBeginPath(nvg); nvgFillColor(nvg, convertColour(getLookAndFeel().findColour(outlet->isSignal ? PlugDataColour::signalColourId : PlugDataColour::dataColourId).brighter())); nvgEllipse(nvg, fakeInletBounds[0] + fakeInletBounds[2] * 0.5f, fakeInletBounds[1] + fakeInletBounds[3] * 0.5f, fakeInletBounds[2] * 0.5f, fakeInletBounds[3] * 0.5f); nvgFill(nvg); - + nvgStrokeColor(nvg, convertColour(getLookAndFeel().findColour(PlugDataColour::objectOutlineColourId))); nvgStrokeWidth(nvg, 1.0f); nvgStroke(nvg); @@ -1323,11 +1318,10 @@ void Object::performRender(NVGcontext* nvg) auto text = std::to_string(cnv->objects.indexOf(this)); int textWidth = 6 + text.length() * 4; auto indexBounds = b.withSizeKeepingCentre(b.getWidth() + doubleMargin, halfHeight * 2).removeFromRight(textWidth); - + auto fillColour = convertColour(getLookAndFeel().findColour(PlugDataColour::objectSelectedOutlineColourId)); nvgDrawRoundedRect(nvg, indexBounds.getX(), indexBounds.getY(), indexBounds.getWidth(), indexBounds.getHeight(), fillColour, fillColour, 2.0f); - nvgFontSize(nvg, 8.0f); nvgFontFace(nvg, "Inter"); nvgTextAlign(nvg, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER); @@ -1340,8 +1334,9 @@ void Object::performRender(NVGcontext* nvg) void Object::renderIolets(NVGcontext* nvg) { - if(cnv->isGraph) return; - + if (cnv->isGraph) + return; + for (auto* iolet : iolets) { nvgSave(nvg); nvgTranslate(nvg, iolet->getX(), iolet->getY()); @@ -1352,18 +1347,15 @@ void Object::renderIolets(NVGcontext* nvg) void Object::renderLabel(NVGcontext* nvg) { - if(gui) - { - if(auto* label = gui->getLabel()) - { + if (gui) { + if (auto* label = gui->getLabel()) { nvgSave(nvg); auto posOnCanvas = cnv->getLocalPoint(gui->labels.get(), label->getPosition()); nvgTranslate(nvg, posOnCanvas.getX(), posOnCanvas.getY()); label->renderLabel(nvg, cnv->getRenderScale() * 2.0f); nvgRestore(nvg); } - if(auto* vu = gui->getVU()) - { + if (auto* vu = gui->getVU()) { if (vu->isVisible()) { nvgSave(nvg); auto posOnCanvas = cnv->getLocalPoint(gui->labels.get(), vu->getPosition()); @@ -1404,10 +1396,10 @@ void Object::hideEditor() // Get entered text, remove extra spaces at the end auto newText = outgoingEditor->getText().trimEnd(); - + newText = newText.replace("\n", " "); newText = newText.replace(";", " ;"); - + outgoingEditor.reset(); repaint(); @@ -1499,11 +1491,11 @@ void Object::textEditorReturnKeyPressed(TextEditor& ed) bool Object::keyPressed(KeyPress const& key, Component* component) { - if(auto* editor = newObjectEditor.get()) { + if (auto* editor = newObjectEditor.get()) { if (key.getKeyCode() == KeyPress::returnKey && editor && key.getModifiers().isShiftDown()) { int caretPosition = editor->getCaretPosition(); auto text = editor->getText(); - + if (!editor->getHighlightedRegion().isEmpty()) return false; if (text[caretPosition - 1] == ';') { @@ -1513,11 +1505,11 @@ bool Object::keyPressed(KeyPress const& key, Component* component) text = text.substring(0, caretPosition) + ";\n" + text.substring(caretPosition); caretPosition += 2; } - + editor->setText(text); editor->setCaretPosition(caretPosition); cnv->hideSuggestions(); - + return true; } } @@ -1534,11 +1526,11 @@ void Object::textEditorTextChanged(TextEditor& ed) } else { currentText = ed.getText(); } - + // For resize-while-typing behaviour auto newWidth = CachedStringWidth<15>::calculateStringWidth(currentText) + 14.0f; newWidth += Object::doubleMargin; - + auto numLines = StringArray::fromLines(currentText.trimEnd()).size(); auto newHeight = std::max((numLines * 15) + 5 + Object::doubleMargin, height); setSize(newWidth, newHeight); @@ -1566,8 +1558,8 @@ void Object::openHelpPatch() const } auto* helpCanvas = editor->getTabComponent().openPatch(URL(file)); - if(helpCanvas) { - if(auto patch = helpCanvas->patch.getPointer()) { + if (helpCanvas) { + if (auto patch = helpCanvas->patch.getPointer()) { patch->gl_edit = 0; } } diff --git a/Source/Object.h b/Source/Object.h index a9710ac55c..92a6c74130 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -15,11 +15,11 @@ #include #if NANOVG_GL_IMPLEMENTATION -#include +# include using namespace juce::gl; -#undef NANOVG_GL_IMPLEMENTATION -#include -#define NANOVG_GL_IMPLEMENTATION 1 +# undef NANOVG_GL_IMPLEMENTATION +# include +# define NANOVG_GL_IMPLEMENTATION 1 #endif #define ACTIVITY_UPDATE_RATE 30 @@ -37,8 +37,7 @@ class Object : public Component , public Timer , public KeyListener , public NVGComponent - , private TextEditor::Listener -{ + , private TextEditor::Listener { public: explicit Object(Canvas* parent, String const& name = "", Point position = { 100, 100 }); @@ -52,7 +51,7 @@ class Object : public Component void timerCallback() override; void resized() override; - + bool keyPressed(KeyPress const& key, Component* component) override; void updateIolets(); @@ -64,7 +63,7 @@ class Object : public Component void showEditor(); void hideEditor(); bool isInitialEditorShown(); - + String getType(bool withOriginPrefix = true) const; Rectangle getSelectableBounds(); @@ -80,7 +79,7 @@ class Object : public Component void mouseEnter(MouseEvent const& e) override; void mouseExit(MouseEvent const& e) override; - + void updateFramebuffer(NVGcontext* nvg); void render(NVGcontext* nvg) override; void performRender(NVGcontext* nvg); @@ -91,7 +90,7 @@ class Object : public Component void mouseDown(MouseEvent const& e) override; void mouseUp(MouseEvent const& e) override; void mouseDrag(MouseEvent const& e) override; - + void lookAndFeelChanged() override; void textEditorReturnKeyPressed(TextEditor& ed) override; @@ -143,7 +142,7 @@ class Object : public Component void openNewObjectEditor(); bool checkIfHvccCompatible() const; - + void setSelected(bool shouldBeSelected); bool selectedFlag = false; bool selectionStateChanged = false; @@ -153,12 +152,12 @@ class Object : public Component bool isGemObject = false; float activeStateAlpha = 0.0f; - + NVGImage activityOverlayImage; bool activityOverlayDirty = false; - + NVGFramebuffer scrollBuffer; - + NVGpaint glow; bool glowDirty = true; @@ -172,7 +171,7 @@ class Object : public Component RateReducer rateReducer = RateReducer(ACTIVITY_UPDATE_RATE); std::unique_ptr newObjectEditor; - + friend class InvalidationListener; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Object) JUCE_DECLARE_WEAK_REFERENCEABLE(Object) diff --git a/Source/ObjectGrid.cpp b/Source/ObjectGrid.cpp index 88f36c6614..6841ad3e12 100644 --- a/Source/ObjectGrid.cpp +++ b/Source/ObjectGrid.cpp @@ -14,7 +14,8 @@ #include "PluginEditor.h" #include "Connection.h" -ObjectGrid::ObjectGrid(Canvas* cnv) : cnv(cnv) +ObjectGrid::ObjectGrid(Canvas* cnv) + : cnv(cnv) { gridEnabled = SettingsFile::getInstance()->getProperty("grid_enabled"); @@ -25,8 +26,9 @@ ObjectGrid::ObjectGrid(Canvas* cnv) : cnv(cnv) Array ObjectGrid::getSnappableObjects(Object* draggedObject) { auto& cnv = draggedObject->cnv; - if(!cnv->viewport) return {}; - + if (!cnv->viewport) + return {}; + Array snappable; auto scaleFactor = std::sqrt(std::abs(cnv->getTransform().getDeterminant())); @@ -213,18 +215,15 @@ Point ObjectGrid::performResize(Object* toDrag, Point dragOffset, Rect auto isDraggingLeft = resizeZone.isDraggingLeftEdge(); auto isDraggingRight = resizeZone.isDraggingRightEdge(); - if(auto* constrainer = toDrag->getConstrainer()) - { + if (auto* constrainer = toDrag->getConstrainer()) { // Not great that we need to do this, but otherwise we don't really know the object bounds for sure constrainer->checkBounds(newResizeBounds, toDrag->originalBounds, limits, isDraggingTop, isDraggingLeft, isDraggingBottom, isDraggingRight); } - // Returns non-zero if the object has a fixed ratio auto ratio = 0.0; - if(auto* constrainer = toDrag->getConstrainer()) - { + if (auto* constrainer = toDrag->getConstrainer()) { ratio = constrainer->getFixedAspectRatio(); } @@ -383,10 +382,10 @@ Line ObjectGrid::getObjectIndicatorLine(Side side, Rectangle b1, Recta void ObjectGrid::clearIndicators(bool fast) { float lineFadeMs = fast ? 50 : 250; - + lineAlphaMultiplier[0] = dsp::FastMathApproximations::exp((-MathConstants::twoPi * 1000.0f / 60.0f) / lineFadeMs); lineAlphaMultiplier[1] = lineAlphaMultiplier[0]; - if(lineTargetAlpha[0] != 0.0f || lineTargetAlpha[1] != 0.0f) { + if (lineTargetAlpha[0] != 0.0f || lineTargetAlpha[1] != 0.0f) { lineTargetAlpha[0] = 0.0f; lineTargetAlpha[1] = 0.0f; startTimerHz(60); @@ -398,26 +397,24 @@ void ObjectGrid::setIndicator(int idx, Line line, float scale) auto lineIsEmpty = line.getLength() == 0; if (lineIsEmpty) { lineAlphaMultiplier[idx] = dsp::FastMathApproximations::exp((-MathConstants::twoPi * 1000.0f / 60.0f) / 50.0f); - if(lineTargetAlpha[idx] != 0.0f) { + if (lineTargetAlpha[idx] != 0.0f) { lineTargetAlpha[idx] = 0.0f; startTimerHz(60); } - } - else { + } else { auto lineArea = cnv->editor->nvgSurface.getLocalArea(cnv, Rectangle(lines[idx].getStart(), lines[idx].getEnd()).expanded(2)); cnv->editor->nvgSurface.invalidateArea(lineArea); } - + lines[idx] = line; if (!lineIsEmpty) { lineAlphaMultiplier[idx] = dsp::FastMathApproximations::exp((-MathConstants::twoPi * 1000.0f / 60.0f) / 50.0f); - if(lineTargetAlpha[idx] != 1.0f) { + if (lineTargetAlpha[idx] != 1.0f) { lineTargetAlpha[idx] = 1.0f; startTimerHz(60); } - } - else { + } else { auto lineArea = cnv->editor->nvgSurface.getLocalArea(cnv, Rectangle(lines[idx].getStart(), lines[idx].getEnd()).expanded(2)); cnv->editor->nvgSurface.invalidateArea(lineArea); } @@ -425,49 +422,48 @@ void ObjectGrid::setIndicator(int idx, Line line, float scale) void ObjectGrid::timerCallback() { - if(lines[0].getLength() != 0 && lineAlpha[0] != 0.0f) { + if (lines[0].getLength() != 0 && lineAlpha[0] != 0.0f) { auto lineArea = cnv->editor->nvgSurface.getLocalArea(cnv, Rectangle(lines[0].getStart(), lines[0].getEnd()).expanded(2)); cnv->editor->nvgSurface.invalidateArea(lineArea); } - if(lines[1].getLength() != 0 && lineAlpha[1] != 0.0f) { + if (lines[1].getLength() != 0 && lineAlpha[1] != 0.0f) { auto lineArea = cnv->editor->nvgSurface.getLocalArea(cnv, Rectangle(lines[1].getStart(), lines[1].getEnd()).expanded(2)); cnv->editor->nvgSurface.invalidateArea(lineArea); } - + bool done = true; // TODO: use multi-timer? - for(int i = 0; i < 2; i++) { + for (int i = 0; i < 2; i++) { lineAlpha[i] = jmap(lineAlphaMultiplier[i], lineTargetAlpha[i], lineAlpha[i]); - if(std::abs(lineAlpha[i] - lineTargetAlpha[i]) < 1e-5) { + if (std::abs(lineAlpha[i] - lineTargetAlpha[i]) < 1e-5) { lineAlpha[i] = lineTargetAlpha[i]; - } - else { + } else { done = false; } } - - if(done) { + + if (done) { stopTimer(); } } void ObjectGrid::render(NVGcontext* nvg) { - if(lines[0].getLength() != 0) { + if (lines[0].getLength() != 0) { auto& lnf = LookAndFeel::getDefaultLookAndFeel(); nvgStrokeColor(nvg, NVGComponent::convertColour(lnf.findColour(PlugDataColour::gridLineColourId).withAlpha(lineAlpha[0]))); nvgStrokeWidth(nvg, 1.0f); - + nvgBeginPath(nvg); nvgMoveTo(nvg, lines[0].getStartX(), lines[0].getStartY()); nvgLineTo(nvg, lines[0].getEndX(), lines[0].getEndY()); nvgStroke(nvg); } - - if(lines[1].getLength() != 0) { + + if (lines[1].getLength() != 0) { auto& lnf = LookAndFeel::getDefaultLookAndFeel(); nvgStrokeColor(nvg, NVGComponent::convertColour(lnf.findColour(PlugDataColour::gridLineColourId).withAlpha(lineAlpha[1]))); nvgStrokeWidth(nvg, 1.0f); - + nvgBeginPath(nvg); nvgMoveTo(nvg, lines[1].getStartX(), lines[1].getStartY()); nvgLineTo(nvg, lines[1].getEndX(), lines[1].getEndY()); diff --git a/Source/ObjectGrid.h b/Source/ObjectGrid.h index fe6c5ea6f0..945b5568bd 100644 --- a/Source/ObjectGrid.h +++ b/Source/ObjectGrid.h @@ -10,7 +10,8 @@ class Object; class Canvas; -struct ObjectGrid : public SettingsFileListener, public Timer { +struct ObjectGrid : public SettingsFileListener + , public Timer { int gridSize = 20; @@ -20,7 +21,7 @@ struct ObjectGrid : public SettingsFileListener, public Timer { Point performMove(Object* toDrag, Point dragOffset); void clearIndicators(bool fast); - + void render(NVGcontext* nvg); private: @@ -32,7 +33,7 @@ struct ObjectGrid : public SettingsFileListener, public Timer { VerticalCentre, HorizontalCentre, }; - + void timerCallback() override; void propertyChanged(String const& name, var const& value) override; diff --git a/Source/Objects/AllGuis.h b/Source/Objects/AllGuis.h index fdad877b97..5d03c7b298 100644 --- a/Source/Objects/AllGuis.h +++ b/Source/Objects/AllGuis.h @@ -230,59 +230,59 @@ struct t_fake_keyboard { // [else/knob] struct t_fake_knob { - t_object x_obj; - void *x_proxy; - t_glist *x_glist; - int x_ctrl; - int x_size; - double x_pos; // 0-1 normalized position - t_float x_exp; - int x_expmode; - int x_log; - t_float x_load; // value when loading patch - t_float x_start; // arc start value - int x_start_angle; - int x_end_angle; - int x_range; - int x_offset; - int x_ticks; - int x_outline; - double x_min; - double x_max; - int x_clicked; - int x_sel; - int x_shift; - int x_edit; - int x_jump; - double x_fval; - t_symbol *x_fg; - t_symbol *x_mg; - t_symbol *x_bg; - t_symbol *x_snd; - t_symbol *x_snd_raw; - int x_flag; - int x_r_flag; - int x_s_flag; - int x_rcv_set; - int x_snd_set; - t_symbol *x_rcv; - t_symbol *x_rcv_raw; - int x_circular; - int x_arc; - int x_zoom; - int x_discrete; - char x_tag_obj[128]; - char x_tag_circle[128]; - char x_tag_bg_arc[128]; - char x_tag_arc[128]; - char x_tag_center[128]; - char x_tag_wiper[128]; - char x_tag_wpr_c[128]; - char x_tag_ticks[128]; - char x_tag_outline[128]; - char x_tag_in[128]; - char x_tag_out[128]; - char x_tag_sel[128]; + t_object x_obj; + void* x_proxy; + t_glist* x_glist; + int x_ctrl; + int x_size; + double x_pos; // 0-1 normalized position + t_float x_exp; + int x_expmode; + int x_log; + t_float x_load; // value when loading patch + t_float x_start; // arc start value + int x_start_angle; + int x_end_angle; + int x_range; + int x_offset; + int x_ticks; + int x_outline; + double x_min; + double x_max; + int x_clicked; + int x_sel; + int x_shift; + int x_edit; + int x_jump; + double x_fval; + t_symbol* x_fg; + t_symbol* x_mg; + t_symbol* x_bg; + t_symbol* x_snd; + t_symbol* x_snd_raw; + int x_flag; + int x_r_flag; + int x_s_flag; + int x_rcv_set; + int x_snd_set; + t_symbol* x_rcv; + t_symbol* x_rcv_raw; + int x_circular; + int x_arc; + int x_zoom; + int x_discrete; + char x_tag_obj[128]; + char x_tag_circle[128]; + char x_tag_bg_arc[128]; + char x_tag_arc[128]; + char x_tag_center[128]; + char x_tag_wiper[128]; + char x_tag_wpr_c[128]; + char x_tag_ticks[128]; + char x_tag_outline[128]; + char x_tag_in[128]; + char x_tag_out[128]; + char x_tag_sel[128]; }; // [else/messbox] diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index 405cb2af4f..dd6a7d7057 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -11,8 +11,10 @@ extern "C" { void garray_arraydialog(t_fake_garray* x, t_symbol* name, t_floatarg fsize, t_floatarg fflags, t_floatarg deleteit); } - -class GraphicalArray : public Component, public Value::Listener, public pd::MessageListener, public NVGComponent { +class GraphicalArray : public Component + , public Value::Listener + , public pd::MessageListener + , public NVGComponent { public: Object* object; @@ -21,15 +23,15 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess Polygon, Curve }; - + Value name = SynchronousValue(); Value size = SynchronousValue(); Value drawMode = SynchronousValue(); Value saveContents = SynchronousValue(); Value range = SynchronousValue(); bool visible = true; - - std::function reloadGraphs = [](){}; + + std::function reloadGraphs = []() {}; GraphicalArray(PluginProcessor* instance, void* ptr, Object* parent) : NVGComponent(this) @@ -45,21 +47,20 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess } catch (...) { error = true; } - + updateParameters(); - - for(auto* value : std::vector{&name, &size, &drawMode, &saveContents, &range}) - { + + for (auto* value : std::vector { &name, &size, &drawMode, &saveContents, &range }) { // TODO: implement undo/redo for these values! value->addListener(this); } - + pd->registerMessageListener(arr.getRawUnchecked(), this); setInterceptsMouseClicks(true, false); setOpaque(false); } - + ~GraphicalArray() { pd->unregisterMessageListener(arr.getRawUnchecked(), this); @@ -86,7 +87,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess } std::vector result(newSize); - const std::size_t oldSize = v.size(); + std::size_t const oldSize = v.size(); for (unsigned i = 0; i < newSize; i++) { auto const idx = i * (oldSize - 1) / newSize; auto const mod = i * (oldSize - 1) % newSize; @@ -124,21 +125,20 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess float const dh = h / (scale[1] - scale[0]); float const dw = w / static_cast(points.size() - 1); - + switch (getDrawType()) { case DrawType::Curve: { Array> splinePoints; - for(int i = 0; i < points.size(); i++) - { + for (int i = 0; i < points.size(); i++) { splinePoints.add(Point(i, points[i])); } Spline spline(splinePoints); - + Path p; p.startNewSubPath(0, h - (std::clamp(points[0], scale[0], scale[1]) - scale[0]) * dh); for (int i = 0; i < w; i++) { - auto pos = jmap(i, 0, w, 0, points.size()-1); + auto pos = jmap(i, 0, w, 0, points.size() - 1); p.lineTo(i, h - (std::clamp(spline[pos], scale[0], scale[1]) - scale[0]) * dh); } @@ -188,7 +188,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess } } } - + void paintGraph(NVGcontext* nvg) { auto const h = static_cast(getHeight()); @@ -198,7 +198,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess if (!points.empty()) { nvgSave(nvg); - const auto arrB = Rectangle(0,0,w,h).reduced(1); + auto const arrB = Rectangle(0, 0, w, h).reduced(1); nvgRoundedScissor(nvg, arrB.getX(), arrB.getY(), arrB.getWidth(), arrB.getHeight(), Corners::objectCornerRadius); std::array scale = getScale(); @@ -220,20 +220,19 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess switch (getDrawType()) { case DrawType::Curve: { Array> splinePoints; - for(int i = 0; i < points.size(); i++) - { + for (int i = 0; i < points.size(); i++) { splinePoints.add(Point(i, points[i])); } Spline spline(splinePoints); - + nvgBeginPath(nvg); nvgMoveTo(nvg, 0, h - (std::clamp(points[0], scale[0], scale[1]) - scale[0]) * dh); for (int i = 0; i < w; i++) { - auto pos = jmap(i, 0, w, 0, points.size()-1); + auto pos = jmap(i, 0, w, 0, points.size() - 1); nvgLineTo(nvg, i, h - (std::clamp(spline[pos], scale[0], scale[1]) - scale[0]) * dh); } - + if (invert) { nvgScale(nvg, 1.0f, -1.0f); nvgTranslate(nvg, 0.0f, -getHeight()); @@ -252,12 +251,12 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess int startY = h - (std::clamp(points[0], scale[0], scale[1]) - scale[0]) * dh; nvgMoveTo(nvg, 0, startY); float const dw = w / (static_cast(points.size()) - 1); - + for (int i = 1; i < static_cast(points.size()); i++) { float const y = h - (std::clamp(points[i], scale[0], scale[1]) - scale[0]) * dh; nvgLineTo(nvg, static_cast(i) * dw, y); } - + if (invert) { nvgScale(nvg, 1.0f, -1.0f); nvgTranslate(nvg, 0.0f, -getHeight()); @@ -294,91 +293,93 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess } } - - void receiveMessage(t_symbol* symbol, const pd::Atom atoms[8], int numAtoms) override + void receiveMessage(t_symbol* symbol, pd::Atom const atoms[8], int numAtoms) override { - switch(hash(symbol->s_name)) { - case hash("edit"): { - if (numAtoms <= 0) break; - MessageManager::callAsync([_this = SafePointer(this), shouldBeEditable = static_cast(atoms[0].getFloat())]() { - _this->editable = shouldBeEditable; - _this->setInterceptsMouseClicks(shouldBeEditable, false); - }); - break; - } - case hash("rename"): { - if (numAtoms <= 0) break; - MessageManager::callAsync([_this = SafePointer(this), newName = atoms[0].toString()]() { - if (!_this) - return; - - _this->object->cnv->setSelected(_this->object, false); - _this->object->editor->sidebar->hideParameters(); - _this->name = newName; - }); - - break; - } - case hash("color"): { - MessageManager::callAsync([_this = SafePointer(this)] { - if(_this) _this->repaint(); - }); + switch (hash(symbol->s_name)) { + case hash("edit"): { + if (numAtoms <= 0) break; - } - case hash("width"): { - MessageManager::callAsync([_this = SafePointer(this)] { - if(_this) _this->repaint(); - }); - break; - } - case hash("style"): { - MessageManager::callAsync([_this = SafePointer(this), newDrawMode = static_cast(atoms[0].getFloat())] { - if(_this) - { - _this->drawMode = newDrawMode + 1; - _this->updateSettings(); - _this->repaint(); - } - }); - break; - } - case hash("xticks"): { - MessageManager::callAsync([_this = SafePointer(this)] { - if(_this) _this->repaint(); - }); - break; - } - case hash("yticks"): { - MessageManager::callAsync([_this = SafePointer(this)] { - if(_this) _this->repaint(); - }); - break; - } - case hash("vis"): { - MessageManager::callAsync([_this = SafePointer(this), visible = atoms[0].getFloat()] { - if(_this) - { - _this->visible = static_cast(visible); - _this->repaint(); - } - }); - - break; - } - case hash("resize"): { - MessageManager::callAsync([_this = SafePointer(this), newSize = atoms[0].getFloat()] { - if(_this) - { - _this->size = static_cast(newSize); - _this->updateSettings(); - _this->repaint(); - } - }); + MessageManager::callAsync([_this = SafePointer(this), shouldBeEditable = static_cast(atoms[0].getFloat())]() { + _this->editable = shouldBeEditable; + _this->setInterceptsMouseClicks(shouldBeEditable, false); + }); + break; + } + case hash("rename"): { + if (numAtoms <= 0) break; - } + MessageManager::callAsync([_this = SafePointer(this), newName = atoms[0].toString()]() { + if (!_this) + return; + + _this->object->cnv->setSelected(_this->object, false); + _this->object->editor->sidebar->hideParameters(); + _this->name = newName; + }); + + break; + } + case hash("color"): { + MessageManager::callAsync([_this = SafePointer(this)] { + if (_this) + _this->repaint(); + }); + break; + } + case hash("width"): { + MessageManager::callAsync([_this = SafePointer(this)] { + if (_this) + _this->repaint(); + }); + break; + } + case hash("style"): { + MessageManager::callAsync([_this = SafePointer(this), newDrawMode = static_cast(atoms[0].getFloat())] { + if (_this) { + _this->drawMode = newDrawMode + 1; + _this->updateSettings(); + _this->repaint(); + } + }); + break; + } + case hash("xticks"): { + MessageManager::callAsync([_this = SafePointer(this)] { + if (_this) + _this->repaint(); + }); + break; + } + case hash("yticks"): { + MessageManager::callAsync([_this = SafePointer(this)] { + if (_this) + _this->repaint(); + }); + break; + } + case hash("vis"): { + MessageManager::callAsync([_this = SafePointer(this), visible = atoms[0].getFloat()] { + if (_this) { + _this->visible = static_cast(visible); + _this->repaint(); + } + }); + + break; + } + case hash("resize"): { + MessageManager::callAsync([_this = SafePointer(this), newSize = atoms[0].getFloat()] { + if (_this) { + _this->size = static_cast(newSize); + _this->updateSettings(); + _this->repaint(); + } + }); + break; + } } } - + void render(NVGcontext* nvg) override { if (error) { @@ -390,7 +391,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess nvgFillColor(nvg, convertColour(LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId))); nvgText(nvg, position.x, position.y, errorText.toRawUTF8(), nullptr); error = false; - } else if(visible) { + } else if (visible) { paintGraph(nvg); } } @@ -401,7 +402,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess // TODO: error colour Fonts::drawText(g, "array " + getUnexpandedName() + " is invalid", 0, 0, getWidth(), getHeight(), LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::canvasTextColourId), 15, Justification::centred); error = false; - } else if(visible) { + } else if (visible) { paintGraph(g); } } @@ -472,11 +473,11 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess { if (error || !getEditMode()) return; - + if (auto ptr = arr.get()) { plugdata_forward_message(ptr->x_glist, gensym("redraw"), 0, NULL); } - + edited = false; } @@ -487,7 +488,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess if (vec.size() != currentSize) { vec.resize(currentSize); } - + size = currentSize; if (!edited) { @@ -609,7 +610,6 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess return 0; } - Colour getContentColour() { if (auto garray = arr.get()) { @@ -640,7 +640,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess return LookAndFeel::getDefaultLookAndFeel().findColour(PlugDataColour::guiObjectInternalOutlineColour); } - + void valueChanged(Value& value) override { if (value.refersToSameSourceAs(name) || value.refersToSameSourceAs(size) || value.refersToSameSourceAs(drawMode) || value.refersToSameSourceAs(saveContents)) { @@ -653,7 +653,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess repaint(); } } - + void updateSettings() { auto arrName = name.getValue().toString(); @@ -682,16 +682,16 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess object->gui->updateLabel(); } - + void deleteArray() { if (auto garray = arr.get()) { glist_delete(garray->x_glist, &garray->x_gobj); } - + reloadGraphs(); } - + void updateParameters() { auto scale = getScale(); @@ -730,7 +730,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess } // Writes a value to the array. - void write(const size_t pos, float const input) + void write(size_t const pos, float const input) { if (auto ptr = arr.get()) { t_word* vec = ((t_word*)garray_vec(ptr.get())); @@ -744,7 +744,7 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess std::vector temp; std::atomic edited; bool error = false; - const String stringArray = "array"; + String const stringArray = "array"; int lastIndex = 0; @@ -752,8 +752,8 @@ class GraphicalArray : public Component, public Value::Listener, public pd::Mess bool editable = true; }; -struct ArrayPropertiesPanel : public PropertiesPanelProperty, public Value::Listener -{ +struct ArrayPropertiesPanel : public PropertiesPanelProperty + , public Value::Listener { class AddArrayButton : public Component { bool mouseIsOver = false; @@ -802,10 +802,10 @@ struct ArrayPropertiesPanel : public PropertiesPanelProperty, public Value::List onClick(); } }; - + OwnedArray properties; Array> graphs; - + AddArrayButton addButton; OwnedArray deleteButtons; Array nameValues; @@ -813,53 +813,51 @@ struct ArrayPropertiesPanel : public PropertiesPanelProperty, public Value::List std::function syncCanvas = []() {}; ArrayPropertiesPanel(std::function addArrayCallback, std::function syncCanvasFunc) - : PropertiesPanelProperty("array") + : PropertiesPanelProperty("array") { setHideLabel(true); - + addAndMakeVisible(addButton); addButton.onClick = addArrayCallback; syncCanvas = syncCanvasFunc; } - - void reloadGraphs(const Array>& safeGraphs) + + void reloadGraphs(Array> const& safeGraphs) { properties.clear(); nameValues.clear(); deleteButtons.clear(); - + graphs = safeGraphs; - - for(auto graph : graphs) - { + + for (auto graph : graphs) { addAndMakeVisible(properties.add(new PropertiesPanel::EditableComponent("Name", graph->name))); addAndMakeVisible(properties.add(new PropertiesPanel::EditableComponent("Size", graph->size))); addAndMakeVisible(properties.add(new PropertiesPanel::RangeComponent("Range", graph->range, false))); - addAndMakeVisible(properties.add(new PropertiesPanel::BoolComponent("Save contents", graph->saveContents, {"No", "Yes"}))); - addAndMakeVisible(properties.add(new PropertiesPanel::ComboComponent("Draw Style", graph->drawMode, {"Points", "Polygon", "Bezier curve"}))); - + addAndMakeVisible(properties.add(new PropertiesPanel::BoolComponent("Save contents", graph->saveContents, { "No", "Yes" }))); + addAndMakeVisible(properties.add(new PropertiesPanel::ComboComponent("Draw Style", graph->drawMode, { "Points", "Polygon", "Bezier curve" }))); + // To detect name changes, so we can redraw the array title nameValues.add(Value()); auto& nameValue = nameValues.getReference(nameValues.size() - 1); nameValue.referTo(graph->name); nameValue.addListener(this); auto* deleteButton = deleteButtons.add(new SmallIconButton(Icons::Clear)); - deleteButton->onClick = [graph](){ + deleteButton->onClick = [graph]() { graph->deleteArray(); }; addAndMakeVisible(deleteButton); } - + auto newHeight = (156 * graphs.size()) + 34; setPreferredHeight(newHeight); - if(auto* propertiesPanel = findParentComponentOfClass()) - { + if (auto* propertiesPanel = findParentComponentOfClass()) { propertiesPanel->updatePropHolderLayout(); } - + repaint(); } - + void valueChanged(Value& v) override { // when array parameters are changed we need to resync the canavs to PD @@ -867,26 +865,27 @@ struct ArrayPropertiesPanel : public PropertiesPanelProperty, public Value::List syncCanvas(); repaint(); } - + void paint(Graphics& g) override { g.fillAll(findColour(PlugDataColour::sidebarBackgroundColourId)); - + auto numGraphs = properties.size() / 5; - for(int i = 0; i < numGraphs; i++) - { - if(!graphs[i]) continue; - + for (int i = 0; i < numGraphs; i++) { + if (!graphs[i]) + continue; + auto start = (i * 156) - 6; g.setColour(findColour(PlugDataColour::sidebarActiveBackgroundColourId)); g.fillRoundedRectangle(0.0f, start + 25, getWidth(), 130, Corners::largeCornerRadius); - - Fonts::drawStyledText(g, graphs[i]->name.toString(), 8, start - 2, getWidth() - 16, 25, findColour(PlugDataColour::sidebarTextColourId), Semibold, 14.5f); + + Fonts::drawStyledText(g, graphs[i]->name.toString(), 8, start - 2, getWidth() - 16, 25, findColour(PlugDataColour::sidebarTextColourId), Semibold, 14.5f); } - + g.setColour(findColour(PlugDataColour::sidebarBackgroundColourId)); for (int i = 0; i < properties.size(); i++) { - if((i % 5) == 4) continue; + if ((i % 5) == 4) + continue; auto y = properties[i]->getBottom(); g.drawHorizontalLine(y, 0, getWidth()); } @@ -895,48 +894,47 @@ struct ArrayPropertiesPanel : public PropertiesPanelProperty, public Value::List void resized() override { auto b = getLocalBounds().translated(0, -6); - for(int i = 0; i < properties.size(); i++) - { - if((i % 5) == 0) { + for (int i = 0; i < properties.size(); i++) { + if ((i % 5) == 0) { auto deleteButtonBounds = b.removeFromTop(26).removeFromRight(28); deleteButtons[i / 5]->setBounds(deleteButtonBounds); } properties[i]->setBounds(b.removeFromTop(26)); } - + addButton.setBounds(getLocalBounds().removeFromBottom(36).reduced(0, 8)); } }; -class ArrayListView : public PropertiesPanel, public Value::Listener -{ +class ArrayListView : public PropertiesPanel + , public Value::Listener { public: - ArrayListView(pd::Instance* instance, void* arr) : array(arr, instance) + ArrayListView(pd::Instance* instance, void* arr) + : array(arr, instance) { update(); } - + void parentSizeChanged() override { setContentWidth(getWidth() - 100); } - + void update() { clear(); arrayValues.clear(); - + Array properties; - + if (auto ptr = array.get()) { auto* arr = garray_getarray(ptr.cast()); auto* vec = ((t_word*)garray_vec(ptr.cast())); - + auto numProperties = arr->a_n; properties.resize(numProperties); - - for(int i = 0; i < numProperties; i++) - { + + for (int i = 0; i < numProperties; i++) { auto& value = *arrayValues.add(new Value(vec[i].w_float)); value.addListener(this); auto* property = new EditableComponent(String(i), value); @@ -944,9 +942,9 @@ class ArrayListView : public PropertiesPanel, public Value::Listener property->setRangeMin(ptr->x_glist->gl_y2); property->setRangeMax(ptr->x_glist->gl_y1); - + // Only send this after drag end so it doesn't interrupt the drag action - label->dragEnd = [this](){ + label->dragEnd = [this]() { if (auto ptr = array.get()) { plugdata_forward_message(ptr->x_glist, gensym("redraw"), 0, NULL); } @@ -954,28 +952,26 @@ class ArrayListView : public PropertiesPanel, public Value::Listener properties.set(i, property); } } - + addSection("", properties); } - + private: void valueChanged(Value& v) override { if (auto ptr = array.get()) { auto* vec = ((t_word*)garray_vec(ptr.cast())); - - for(int i = 0; i < arrayValues.size(); i++) - { + + for (int i = 0; i < arrayValues.size(); i++) { auto& value = *arrayValues[i]; - if(v.refersToSameSourceAs(value)) - { + if (v.refersToSameSourceAs(value)) { vec[i].w_float = getValue(value); break; } } } } - + OwnedArray arrayValues; pd::WeakReference array; }; @@ -985,7 +981,7 @@ class ArrayEditorDialog : public Component { std::unique_ptr