diff --git a/CMakeLists.txt b/CMakeLists.txt index db4a68b73..7989ba3f3 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ option(RUN_CLANG_TIDY "" OFF) option(ENABLE_TESTING "" OFF) option(ENABLE_SFONT "" ON) option(ENABLE_SFIZZ "" ON) +option(ENABLE_ASAN "" OFF) option(VERBOSE "" OFF) set (CMAKE_CXX_STANDARD 20) @@ -16,16 +17,6 @@ set (CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_C_VISIBILITY_PRESET hidden) -# Sfizz doesn't want to compile on aarch64 linux here... -if(CMAKE_SYSTEM_NAME MATCHES "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") - set(ENABLE_SFIZZ OFF) -endif() - -# Sfizz doesn't want to compile on aarch64 linux here... -if(CMAKE_SYSTEM_NAME MATCHES "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") - set(ENABLE_SFIZZ OFF) -endif() - function(message) if (NOT MESSAGE_QUIET) _message(${ARGN}) @@ -59,6 +50,13 @@ set_property(GLOBAL PROPERTY USE_FOLDERS YES) project(plugdata VERSION 0.8.0 LANGUAGES C CXX) +# Sfizz doesn't want to compile on aarch64 linux... +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux" AND ${CMAKE_SYSTEM_PROCESSOR} MATCHES "aarch64") + message(STATUS "Disabled sfizz") + set(ENABLE_SFIZZ OFF) +endif() + + add_subdirectory(Libraries/ EXCLUDE_FROM_ALL) cmake_policy(SET CMP0091 NEW) @@ -69,6 +67,11 @@ if (CMAKE_C_COMPILER_ID MATCHES "Clang") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=return-type") endif() +if(ENABLE_ASAN) + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() + message(STATUS "Preparing documentation") execute_process(COMMAND python3 parse_documentation.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Scripts) diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 1219f463b..d703220bd 100755 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -191,11 +191,20 @@ file(GLOB ELSE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/pd-else/Code_source/shared/*.c ) +file(GLOB_RECURSE CIRCUIT_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/pd-else/Code_source/Compiled/audio/circuit~/Source/circuit~.c + ${CMAKE_CURRENT_SOURCE_DIR}/pd-else/Code_source/Compiled/audio/circuit~/Source/Simulator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pd-else/Code_source/Compiled/audio/circuit~/Libraries/**/*.c +) + file(GLOB_RECURSE PLAITS_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/pd-else/Code_source/Compiled/audio/plaits~/plaits~.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pd-else/Code_source/Compiled/audio/plaits~/*.cc ) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/pd-else/Code_source/Compiled/audio/circuit~/Libraries) + +list(APPEND ELSE_SOURCES ${CIRCUIT_SOURCES}) list(APPEND ELSE_SOURCES ${PLAITS_SOURCES}) if(ENABLE_SFIZZ) diff --git a/Libraries/libpd/x_libpd_multi.c b/Libraries/libpd/x_libpd_multi.c index 2977c2ab0..dde3f29f5 100644 --- a/Libraries/libpd/x_libpd_multi.c +++ b/Libraries/libpd/x_libpd_multi.c @@ -767,6 +767,12 @@ void setup_ptouch0x2eout(); void setup_spread0x2emc_tilde(); void setup_rotate0x2emc_tilde(); void pipe2_setup(); +void circuit_tilde_setup(); + +void op2_tilde_setup(); +void op4_tilde_setup(); +void op6_tilde_setup(); + #if ENABLE_SFIZZ void sfz_tilde_setup(); @@ -1085,6 +1091,12 @@ void libpd_init_else(void) setup_spread0x2emc_tilde(); setup_rotate0x2emc_tilde(); pipe2_setup(); + circuit_tilde_setup(); + + /* Not yet! + op2_tilde_setup(); + op4_tilde_setup(); + op6_tilde_setup(); */ } void libpd_init_cyclone(void) diff --git a/Libraries/pd-else b/Libraries/pd-else index d8306d84e..ab507c690 160000 --- a/Libraries/pd-else +++ b/Libraries/pd-else @@ -1 +1 @@ -Subproject commit d8306d84e1741f95bb2b8a9ea36cdffefa29a125 +Subproject commit ab507c69071168f0fc89d7711518beea67bda8d6 diff --git a/Resources/Documentation/vanilla/clone.md b/Resources/Documentation/vanilla/clone.md index 15b3a6e16..4396bbf23 100644 --- a/Resources/Documentation/vanilla/clone.md +++ b/Resources/Documentation/vanilla/clone.md @@ -6,7 +6,7 @@ categories: see_also: - poly pdcategory: vanilla, UI, Data Management -last_update: '0.47' +last_update: '0.54' inlets: nth: - type: list @@ -18,7 +18,7 @@ outlets: - type: data outlets description: output with a prepended instance number - type: signal outlets - description: output the sum of all instances' outputs + description: output from all instances either as multichannel signal or summed into mono signal flags: - name: -x @@ -26,6 +26,12 @@ flags: - name: -s description: sets starting voice number default: 0 + - name: -di + description: distribute multichannel input signals across cloned patches + - name: -do + description: combine signal outputs to make a multichannel signal + - name: -d + description: set both -di and -do flags arguments: - type: symbol @@ -36,6 +42,10 @@ arguments: description: optional arguments to the abstraction methods: + - type: resize + description: resizes the number of copies + - type: vis + description: opens a copy, takes copy number and visualization status (1 to open, 0 to close) - type: next description: forwards a message to the next instance's inlet (incrementing and repeating circularly) - type: this diff --git a/Resources/Documentation/vanilla/snake~-in.md b/Resources/Documentation/vanilla/snake~-in.md new file mode 100644 index 000000000..63a58fad8 --- /dev/null +++ b/Resources/Documentation/vanilla/snake~-in.md @@ -0,0 +1,28 @@ +--- +title: snake~, snake_in~ +description: combine mono signals into multichannel signals +categories: +- object +pdcategory: vanilla, Mixing and Routing, Audio I/O +last_update: '0.54' +see_also: +- snake~ out +- inlet~ +- clone +- throw~ +- send~ +arguments: +- description: number of channels + default: 2 + type: float +inlets: + nth: + - type: float/signal + description: mono input to merge into a multichannel signal +outlets: + 1st: + - type: signals + description: multichannel signal +draft: false +--- +ssssssssss diff --git a/Resources/Documentation/vanilla/snake~-out.md b/Resources/Documentation/vanilla/snake~-out.md new file mode 100644 index 000000000..1fb79bf8f --- /dev/null +++ b/Resources/Documentation/vanilla/snake~-out.md @@ -0,0 +1,28 @@ +--- +title: snake_out~ +description: split multichannel signals into mono signals +categories: +- object +pdcategory: vanilla, Mixing and Routing, Audio I/O +last_update: '0.54' +see_also: +- snake~ out +- inlet~ +- clone +- throw~ +- send~ +arguments: +- description: number of channels + default: 2 + type: float +inlets: + 1st: + - type: signals + description: a multichannel signal to break into mono tracks +outlets: + nth: + - type: signal + description: mono outputs +draft: false +--- +ssssssssss diff --git a/Resources/Fonts/IconFont.ttf b/Resources/Fonts/IconFont.ttf index 2e2419cde..1340e38b8 100644 Binary files a/Resources/Fonts/IconFont.ttf and b/Resources/Fonts/IconFont.ttf differ diff --git a/Resources/Scripts/package_resources.py b/Resources/Scripts/package_resources.py index f35d62a27..7f016f7c7 100644 --- a/Resources/Scripts/package_resources.py +++ b/Resources/Scripts/package_resources.py @@ -81,6 +81,7 @@ def splitFile(file, num_files): globMove("./Abstractions/*-help.pd", "./Documentation/5.reference") copyDir("../../Libraries/pd-else/Documentation/Help-files/", "./Documentation/9.else") +copyFile("../../Libraries/pd-else/Documentation/extra_files/f2s~-help.pd", "./Documentation/9.else") #copyFile("../Patches/beat-help.pd", "./Documentation/5.reference") copyFile("../Patches/param-help.pd", "./Documentation/5.reference") @@ -99,6 +100,7 @@ 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("../Patches/Presets", "./Extra/Presets") copyDir("../Patches/Palettes", "./Extra/palette") diff --git a/Source/Canvas.cpp b/Source/Canvas.cpp index 561a1b0d9..0c8f6228d 100644 --- a/Source/Canvas.cpp +++ b/Source/Canvas.cpp @@ -108,8 +108,9 @@ Canvas::Canvas(PluginEditor* parent, pd::Patch::Ptr p, Component* parentGraph) setSize(infiniteCanvasSize, infiniteCanvasSize); - // initialize per canvas zoom to 100% when first creating canvas - zoomScale.setValue(1.0f); + // initialize to default zoom + auto defaultZoom = SettingsFile::getInstance()->getPropertyAsValue("default_zoom"); + zoomScale.setValue(getValue(defaultZoom)/100.0f); zoomScale.addListener(this); // Add lasso component @@ -275,7 +276,9 @@ void Canvas::zoomToFitAll() void Canvas::lookAndFeelChanged() { - lasso.setColour(LassoComponent::lassoFillColourId, findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.3f)); + lasso.setColour(LassoComponent::lassoFillColourId, findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.125f)); + + lasso.setColour(LassoComponent::lassoOutlineColourId, findColour(PlugDataColour::objectSelectedOutlineColourId).withAlpha(0.8f)); } void Canvas::paint(Graphics& g) @@ -461,7 +464,9 @@ void Canvas::performSynchronise() // Remove deleted objects for (int n = objects.size() - 1; n >= 0; n--) { auto* object = objects[n]; - if (!object->getPointer() || patch.objectWasDeleted(object->getPointer())) { + + // If the object is showing it's initial editor, meaning no object was assigned yet, allow it to exist without pointing to an object + if ((!object->getPointer()|| patch.objectWasDeleted(object->getPointer())) && !object->isInitialEditorShown()) { setSelected(object, false, false); objects.remove(n); } diff --git a/Source/CanvasViewport.h b/Source/CanvasViewport.h index 95aff5c68..80212e376 100644 --- a/Source/CanvasViewport.h +++ b/Source/CanvasViewport.h @@ -170,12 +170,13 @@ class CanvasViewport : public Viewport { { isMouseDragging = true; viewPosition = viewport->getViewPosition(); + repaint(); } void mouseUp(MouseEvent const& e) override { + isMouseDragging = false; if (e.mouseWasDraggedSinceMouseDown()) { - isMouseDragging = false; if (!isMouseOver) animator.shrink(); } @@ -222,12 +223,22 @@ class CanvasViewport : public Viewport { auto growingBounds = thumbBounds.reduced(1).withTop(thumbBounds.getY() + growPosition); auto roundedCorner = growingBounds.getHeight() * 0.5f; + auto fullBounds = growingBounds.withX(2).withWidth(getWidth() - 4); + if (isVertical) { growingBounds = thumbBounds.reduced(1).withLeft(thumbBounds.getX() + growPosition); roundedCorner = growingBounds.getWidth() * 0.5f; + fullBounds = growingBounds.withY(2).withHeight(getHeight() - 4); } - g.setColour(findColour(ScrollBar::ColourIds::thumbColourId)); + auto canvasColour = findColour(PlugDataColour::canvasBackgroundColourId); + auto scrollbarColour = findColour(ScrollBar::ColourIds::thumbColourId); + auto activeScrollbarColour = scrollbarColour.interpolatedWith(canvasColour.contrasting(0.6f), 0.7f); + + g.setColour(scrollbarColour.interpolatedWith(canvasColour, 0.7f).withAlpha(std::clamp(1.0f - growAnimation, 0.0f, 1.0f))); + g.fillRoundedRectangle(fullBounds, roundedCorner); + + g.setColour(isMouseDragging ? activeScrollbarColour : scrollbarColour); g.fillRoundedRectangle(growingBounds, roundedCorner); } @@ -373,6 +384,11 @@ class CanvasViewport : public Viewport { adjustScrollbarBounds(); + if (!SettingsFile::getInstance()->getProperty("center_resized_canvas")) { + Viewport::resized(); + return; + } + float scale = std::sqrt(std::abs(cnv->getTransform().getDeterminant())); // centre canvas when resizing viewport @@ -384,12 +400,12 @@ class CanvasViewport : public Viewport { return getViewArea().withZeroOrigin().getCentre(); }; - auto currentCenter = getCentre(previousBounds); + auto currentCentre = getCentre(previousBounds); previousBounds = getBounds(); Viewport::resized(); - auto newCenter = getCentre(getBounds()); + auto newCentre = getCentre(getBounds()); - auto offset = currentCenter - newCenter; + auto offset = currentCentre - newCentre; setViewPosition(getViewPosition() + offset); } diff --git a/Source/Connection.cpp b/Source/Connection.cpp index f28868609..d3259bed8 100644 --- a/Source/Connection.cpp +++ b/Source/Connection.cpp @@ -210,7 +210,7 @@ bool Connection::hitTest(int x, int y) if (Canvas::panningModifierDown()) return false; - if (locked == var(true) || !cnv->connectionsBeingCreated.isEmpty()) + if (cnv->commandLocked == var(true) || locked == var(true) || !cnv->connectionsBeingCreated.isEmpty()) return false; Point position = Point(static_cast(x), static_cast(y)); @@ -444,10 +444,10 @@ bool Connection::isSegmented() const void Connection::setSegmented(bool isSegmented) { segmented = isSegmented; - pushPathState(); updatePath(); resizeToFit(); repaint(); + pushPathState(); } void Connection::setSelected(bool shouldBeSelected) diff --git a/Source/Constants.h b/Source/Constants.h index 6aebcc5c9..e34d7387f 100644 --- a/Source/Constants.h +++ b/Source/Constants.h @@ -63,6 +63,7 @@ struct Icons { inline static const String History = "X"; inline static const String Protection = "Y"; inline static const String DevTools = "{"; + inline static const String Help = "\\"; inline static const String SavePatch = "Z"; inline static const String ClosePatch = "["; @@ -381,7 +382,7 @@ const std::map objectNames { }; struct Corners { - inline static float const windowCornerRadius = 12.5f; + inline static float const windowCornerRadius = 12.0f; inline static float const largeCornerRadius = 8.0f; inline static float const defaultCornerRadius = 5.0f; inline static float objectCornerRadius = 2.75f; diff --git a/Source/Dialogs/AdvancedSettingsPanel.h b/Source/Dialogs/AdvancedSettingsPanel.h index 2a54a20be..1fcb4ab50 100644 --- a/Source/Dialogs/AdvancedSettingsPanel.h +++ b/Source/Dialogs/AdvancedSettingsPanel.h @@ -59,6 +59,14 @@ class AdvancedSettingsPanel : public Component scaleValue.addListener(this); otherProperties.add(new PropertiesPanel::EditableComponent("Global scale factor", scaleValue)); + defaultZoom = settingsFile->getProperty("default_zoom"); + defaultZoom.addListener(this); + otherProperties.add(new PropertiesPanel::EditableComponent("Default zoom %", defaultZoom)); + + centerResized = settingsFile->getPropertyAsValue("center_resized_canvas"); + centerResized.addListener(this); + otherProperties.add(new PropertiesPanel::BoolComponent("Center canvas when resized", centerResized, { "No", "Yes" })); + propertiesPanel.addSection("Other", otherProperties); addAndMakeVisible(propertiesPanel); @@ -85,6 +93,11 @@ class AdvancedSettingsPanel : public Component SettingsFile::getInstance()->setGlobalScale(scale); scaleValue = scale; } + if (v.refersToSameSourceAs(defaultZoom)) { + auto zoom = std::clamp(getValue(defaultZoom), 20.0f, 300.0f); + SettingsFile::getInstance()->setProperty("default_zoom", zoom); + defaultZoom = zoom; + } } Component* editor; @@ -94,6 +107,8 @@ class AdvancedSettingsPanel : public Component Value macTitlebarButtons; Value reloadPatch; Value scaleValue; + Value defaultZoom; + Value centerResized; Value showPalettesValue; Value autoPatchingValue; diff --git a/Source/Dialogs/AudioSettingsPanel.h b/Source/Dialogs/AudioSettingsPanel.h index ed67cf4e4..d6e7f9953 100644 --- a/Source/Dialogs/AudioSettingsPanel.h +++ b/Source/Dialogs/AudioSettingsPanel.h @@ -84,6 +84,7 @@ struct CallbackComboPropertyWithTestButton : public CallbackComboProperty { : CallbackComboProperty(propertyName, options, currentOption, onChange) { testButton.setColour(ComboBox::outlineColourId, Colours::transparentBlack); + testButton.setColour(TextButton::textColourOnId, findColour(TextButton::textColourOffId)); testButton.onClick = [&deviceManager]() mutable { deviceManager.playTestSound(); }; diff --git a/Source/Dialogs/Deken.h b/Source/Dialogs/Deken.h index b28560337..ac420160f 100644 --- a/Source/Dialogs/Deken.h +++ b/Source/Dialogs/Deken.h @@ -527,10 +527,10 @@ class Deken : public Component g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); g.fillRoundedRectangle(getLocalBounds().toFloat(), Corners::windowCornerRadius); - auto bounds = getLocalBounds().removeFromTop(40).toFloat(); + auto titlebarBounds = getLocalBounds().removeFromTop(40).toFloat(); Path p; - p.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), Corners::largeCornerRadius, Corners::largeCornerRadius, true, true, false, false); + p.addRoundedRectangle(titlebarBounds.getX(), titlebarBounds.getY(), titlebarBounds.getWidth(), titlebarBounds.getHeight(), Corners::largeCornerRadius, Corners::largeCornerRadius, true, true, false, false); g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); g.fillPath(p); @@ -550,7 +550,7 @@ class Deken : public Component Fonts::drawFittedText(g, "Type to search for objects or libraries", 30, 40, getWidth() - 60, 30, findColour(PlugDataColour::panelTextColourId).withAlpha(0.5f), 1, 0.9f, 14); } - g.setColour(findColour(PlugDataColour::outlineColourId)); + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.drawLine(0, 40, getWidth(), 40); g.drawLine(0, 70, getWidth(), 70); } @@ -681,7 +681,7 @@ class Deken : public Component listBox.setBounds(getLocalBounds().withHeight(listBox.getHeight())); listBox.getViewport()->setBounds(bounds); - refreshButton.setBounds(getLocalBounds().removeFromTop(40).removeFromLeft(40)); + refreshButton.setBounds(getLocalBounds().removeFromTop(40).removeFromLeft(40).translated(2, 0)); } // Show error message in statusbar diff --git a/Source/Dialogs/Dialogs.cpp b/Source/Dialogs/Dialogs.cpp index 58e96025a..5d67ecf16 100644 --- a/Source/Dialogs/Dialogs.cpp +++ b/Source/Dialogs/Dialogs.cpp @@ -166,6 +166,9 @@ void Dialogs::showOkayCancelDialog(std::unique_ptr* target, Component* p cancel.setColour(TextButton::buttonColourId, Colours::transparentBlack); okay.setColour(TextButton::buttonColourId, Colours::transparentBlack); + + cancel.setColour(TextButton::textColourOnId, findColour(TextButton::textColourOffId)); + okay.setColour(TextButton::textColourOnId, findColour(TextButton::textColourOffId)); cancel.onClick = [dialog, callback] { callback(false); @@ -215,7 +218,7 @@ void Dialogs::showHeavyExportDialog(std::unique_ptr* target, Component* void Dialogs::showObjectBrowserDialog(std::unique_ptr* target, Component* parent) { - auto* dialog = new Dialog(target, parent, 750, 450, true); + auto* dialog = new Dialog(target, parent, 750, 480, true); auto* dialogContent = new ObjectBrowserDialog(parent, dialog); dialog->setViewedComponent(dialogContent); @@ -224,7 +227,7 @@ void Dialogs::showObjectBrowserDialog(std::unique_ptr* target, Component void Dialogs::showObjectReferenceDialog(std::unique_ptr* target, Component* parent, String const& objectName) { - auto* dialog = new Dialog(target, parent, 750, 450, true); + auto* dialog = new Dialog(target, parent, 750, 480, true); auto* dialogContent = new ObjectReferenceDialog(dynamic_cast(parent), false); dialogContent->showObject(objectName); diff --git a/Source/Dialogs/ObjectBrowserDialog.h b/Source/Dialogs/ObjectBrowserDialog.h index fbd12a52a..c98129f6d 100644 --- a/Source/Dialogs/ObjectBrowserDialog.h +++ b/Source/Dialogs/ObjectBrowserDialog.h @@ -221,6 +221,9 @@ class ObjectViewer : public Component { addChildComponent(openHelp); addChildComponent(openReference); addChildComponent(objectDragArea); + + openReference.setColour(TextButton::textColourOnId, findColour(TextButton::textColourOffId)); + openHelp.setColour(TextButton::textColourOnId, findColour(TextButton::textColourOffId)); openReference.onClick = [this]() { reference.showObject(objectName); @@ -250,9 +253,10 @@ class ObjectViewer : public Component { buttonBounds.removeFromTop(5); openHelp.setBounds(buttonBounds.removeFromTop(25)); - objectDragArea.setBounds(getLocalBounds().reduced(20).withTrimmedTop(16).withTrimmedBottom(100)); + objectDragArea.setBounds(getLocalBounds().withTrimmedTop(48).withTrimmedBottom(120).withTrimmedLeft(12).withTrimmedRight(6)); } + void paintOverChildren(Graphics& g) override { g.setColour(findColour(PlugDataColour::outlineColourId)); @@ -265,7 +269,7 @@ class ObjectViewer : public Component { auto objectDisplayBounds = infoBounds.removeFromTop(100).reduced(60); auto colour = findColour(PlugDataColour::panelTextColourId); - Fonts::drawStyledText(g, objectName, getLocalBounds().removeFromTop(35).translated(0, 4), colour, Bold, 16.0f, Justification::centred); + Fonts::drawStyledText(g, objectName, getLocalBounds().removeFromTop(24).translated(0, 4), colour, Bold, 16.0f, Justification::centred); auto numInlets = unknownInletLayout ? "Unknown" : String(inlets.size()); auto numOutlets = unknownOutletLayout ? "Unknown" : String(outlets.size()); @@ -685,7 +689,7 @@ class ObjectSearchComponent : public Component input.setBounds(inputBounds); - clearButton.setBounds(inputBounds.removeFromRight(32)); + clearButton.setBounds(inputBounds.removeFromRight(30).translated(4, 0)); listBox.setBounds(tableBounds); } @@ -809,7 +813,7 @@ class ObjectBrowserDialog : public Component { void resized() override { - auto b = getLocalBounds().reduced(1); + auto b = getLocalBounds().withTrimmedTop(40).reduced(1); objectViewer.setBounds(b.removeFromRight(260)); objectSearch.setBounds(b); b.removeFromTop(35); @@ -824,6 +828,23 @@ class ObjectBrowserDialog : public Component { { g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); g.fillRoundedRectangle(getLocalBounds().reduced(1).toFloat(), Corners::windowCornerRadius); + + g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); + g.fillRoundedRectangle(getLocalBounds().reduced(1).toFloat(), Corners::windowCornerRadius); + + auto titlebarBounds = getLocalBounds().removeFromTop(40); + + Path p; + p.addRoundedRectangle(titlebarBounds.getX(), titlebarBounds.getY(), titlebarBounds.getWidth(), titlebarBounds.getHeight(), Corners::largeCornerRadius, Corners::largeCornerRadius, true, true, false, false); + + g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); + g.fillPath(p); + + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); + g.drawHorizontalLine(40, 0.0f, getWidth()); + g.drawHorizontalLine(70, 0.0f, getWidth()); + + Fonts::drawStyledText(g, "Object Browser", Rectangle(0.0f, 4.0f, getWidth(), 32.0f), findColour(PlugDataColour::panelTextColourId), Semibold, 15, Justification::centred); } private: diff --git a/Source/Dialogs/ObjectReferenceDialog.h b/Source/Dialogs/ObjectReferenceDialog.h index ee0987512..4cc474ab1 100644 --- a/Source/Dialogs/ObjectReferenceDialog.h +++ b/Source/Dialogs/ObjectReferenceDialog.h @@ -23,19 +23,21 @@ class ObjectInfoPanel : public Component { g.setColour(findColour(PlugDataColour::outlineColourId)); g.drawLine(0, 1, getWidth(), 0); - Fonts::drawStyledText(g, categoryName, getLocalBounds().toFloat().removeFromTop(24), findColour(PlugDataColour::panelTextColourId), FontStyle::Bold, 14.0f); + Fonts::drawStyledText(g, categoryName, getLocalBounds().toFloat().removeFromTop(24).translated(2, 0), findColour(PlugDataColour::panelTextColourId), FontStyle::Bold, 14.0f); float totalHeight = 24; for (int i = 0; i < panelContent.size(); i++) { auto textHeight = layouts[i].getHeight(); - auto bounds = Rectangle(24.0f, totalHeight + 6.0f, getWidth() - 48.0f, textHeight); - - Fonts::drawStyledText(g, panelContent[i].first, bounds.removeFromLeft(128), findColour(PlugDataColour::panelTextColourId), FontStyle::Semibold, 13.5f); + + auto bounds = Rectangle(36.0f, totalHeight + 6.0f, getWidth() - 48.0f, textHeight); + auto nameWidth = std::max(Fonts::getSemiBoldFont().getStringWidth(panelContent[i].first), 64); + + Fonts::drawStyledText(g, panelContent[i].first, bounds.removeFromLeft(nameWidth), findColour(PlugDataColour::panelTextColourId), FontStyle::Semibold, 13.5f); layouts[i].draw(g, bounds); g.setColour(findColour(PlugDataColour::outlineColourId)); - g.drawLine(24.0f, totalHeight, getWidth() - 24.0f, totalHeight); + g.drawLine(36.0f, totalHeight, getWidth() - 24.0f, totalHeight); totalHeight += textHeight + 12; } @@ -47,10 +49,27 @@ class ObjectInfoPanel : public Component { int totalHeight = 24; for (auto const& [name, description] : panelContent) { + auto nameWidth = std::max(Fonts::getSemiBoldFont().getStringWidth(name), 64); + AttributedString str; - str.append(description, Font(13.5f), findColour(PlugDataColour::panelTextColourId)); + + auto lines = StringArray::fromLines(description); + + // Draw anything between () as bold + for (auto const& line : lines) { + if (line.contains("(") && line.contains(")")) { + auto type = line.fromFirstOccurrenceOf("(", false, false).upToFirstOccurrenceOf(")", false, false); + auto description = line.fromFirstOccurrenceOf(")", false, false); + str.append(type + ":", Fonts::getSemiBoldFont().withHeight(13.5f), findColour(PlugDataColour::panelTextColourId)); + + str.append(description + "\n", Font(13.5f), findColour(PlugDataColour::panelTextColourId)); + } else { + str.append(line, Font(13.5f), findColour(PlugDataColour::panelTextColourId)); + } + } + TextLayout layout; - layout.createLayout(str, width - 192.0f); + layout.createLayout(str, width - (nameWidth + 64.0f)); layouts.add(layout); totalHeight += layout.getHeight() + 12; } @@ -162,7 +181,7 @@ class ObjectInfoPanel : public Component { { categoriesViewport.setBounds(getLocalBounds()); - int totalHeight = 0; + int totalHeight = 24; for (auto* category : categories) { category->recalculateLayout(getWidth()); category->setTopLeftPosition(0, totalHeight); @@ -194,8 +213,6 @@ class ObjectReferenceDialog : public Component { setVisible(false); }; - backButton.setColour(TextButton::buttonColourId, Colours::transparentBlack); - backButton.setColour(TextButton::buttonOnColourId, Colours::transparentBlack); backButton.getProperties().set("Style", "LargeIcon"); addAndMakeVisible(objectInfoPanel); @@ -203,12 +220,9 @@ class ObjectReferenceDialog : public Component { void resized() override { - backButton.setBounds(8, 6, 42, 48); - - auto buttonBounds = getLocalBounds().removeFromBottom(80).reduced(30, 0).translated(0, -30); - buttonBounds.removeFromTop(10); + backButton.setBounds(2, 0, 40, 40); - auto rightPanelBounds = getLocalBounds().removeFromRight(getLocalBounds().proportionOfWidth(0.7f)).reduced(20, 40); + auto rightPanelBounds = getLocalBounds().withTrimmedTop(40).removeFromRight(getLocalBounds().proportionOfWidth(0.65f)).reduced(20, 0); objectInfoPanel.setBounds(rightPanelBounds); } @@ -217,16 +231,32 @@ class ObjectReferenceDialog : public Component { { g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); g.fillRoundedRectangle(getLocalBounds().reduced(1).toFloat(), Corners::windowCornerRadius); + + g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); + g.fillRoundedRectangle(getLocalBounds().reduced(1).toFloat(), Corners::windowCornerRadius); + + auto titlebarBounds = getLocalBounds().removeFromTop(40).toFloat(); + + Path p; + p.addRoundedRectangle(titlebarBounds.getX(), titlebarBounds.getY(), titlebarBounds.getWidth(), titlebarBounds.getHeight(), Corners::largeCornerRadius, Corners::largeCornerRadius, true, true, false, false); + + g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); + g.fillPath(p); + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); + g.drawHorizontalLine(40, 0.0f, getWidth()); + if (objectName.isEmpty()) return; - auto leftPanelBounds = getLocalBounds().withTrimmedRight(getLocalBounds().proportionOfWidth(0.7f)); + auto leftPanelBounds = getLocalBounds().withTrimmedRight(getLocalBounds().proportionOfWidth(0.65f)); - auto infoBounds = leftPanelBounds.withTrimmedBottom(100).withTrimmedTop(100).withTrimmedLeft(5).reduced(10); + g.drawVerticalLine(leftPanelBounds.getRight(), 40.0f, getHeight() - 40.0f); + + auto infoBounds = leftPanelBounds.withTrimmedBottom(100).withTrimmedTop(140).withTrimmedLeft(5).reduced(10); auto objectDisplayBounds = leftPanelBounds.removeFromTop(140); - Fonts::drawStyledText(g, "Reference: " + objectName, getLocalBounds().removeFromTop(35).translated(0, 4), findColour(PlugDataColour::panelTextColourId), Bold, 16, Justification::centred); + Fonts::drawStyledText(g, "Object Reference: " + objectName, getLocalBounds().removeFromTop(35).translated(0, 4), findColour(PlugDataColour::panelTextColourId), Bold, 16, Justification::centred); auto colour = findColour(PlugDataColour::panelTextColourId); @@ -405,6 +435,7 @@ class ObjectReferenceDialog : public Component { setVisible(true); objectInfoPanel.showObject(objectInfo); + objectInfoPanel.resized(); } bool unknownInletLayout = false; diff --git a/Source/Dialogs/PatchStorage.h b/Source/Dialogs/PatchStorage.h index a6a25be90..dd454cd95 100644 --- a/Source/Dialogs/PatchStorage.h +++ b/Source/Dialogs/PatchStorage.h @@ -664,11 +664,11 @@ struct PatchStorage : public Component if (input.getText().isEmpty()) { Fonts::drawFittedText(g, "Type to search for patches", 30, 40, getWidth() - 60, 30, findColour(PlugDataColour::panelTextColourId).withAlpha(0.5f), 1, 0.9f, 14); } - g.setColour(findColour(PlugDataColour::outlineColourId)); + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.drawLine(0, 70, getWidth(), 70); } - g.setColour(findColour(PlugDataColour::outlineColourId)); + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.drawLine(0, 40, getWidth(), 40); } diff --git a/Source/Dialogs/SaveDialog.h b/Source/Dialogs/SaveDialog.h index 53a2fc609..aee16c5ea 100644 --- a/Source/Dialogs/SaveDialog.h +++ b/Source/Dialogs/SaveDialog.h @@ -20,7 +20,7 @@ class SaveDialogButton : public TextButton { auto activeColour = findColour(PlugDataColour::toolbarActiveColourId); if (isMouseOver() || isMouseButtonDown()) { - backgroundColour = backgroundColour.contrasting(0.1f); + backgroundColour = backgroundColour.contrasting(0.05f); } g.setColour(backgroundColour); diff --git a/Source/Dialogs/SettingsDialog.h b/Source/Dialogs/SettingsDialog.h index f1c0acd42..37b17177c 100644 --- a/Source/Dialogs/SettingsDialog.h +++ b/Source/Dialogs/SettingsDialog.h @@ -39,7 +39,7 @@ class SettingsToolbarButton : public TextButton { if (isMouseOver() || getToggleState()) { auto background = findColour(PlugDataColour::toolbarHoverColourId); if (getToggleState()) - background = background.darker(0.05f); + background = background.darker(0.025f); g.setColour(background); PlugDataLook::fillSmoothedRectangle(g, b.toFloat(), Corners::defaultCornerRadius); @@ -147,11 +147,13 @@ class SettingsDialog : public Component { g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); g.fillRoundedRectangle(getLocalBounds().reduced(1).toFloat(), Corners::windowCornerRadius); - g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); + auto titlebarBounds = getLocalBounds().removeFromTop(toolbarHeight).toFloat(); + + Path p; + p.addRoundedRectangle(titlebarBounds.getX(), titlebarBounds.getY(), titlebarBounds.getWidth(), titlebarBounds.getHeight(), Corners::largeCornerRadius, Corners::largeCornerRadius, true, true, false, false); - auto toolbarBounds = Rectangle(1, 1, getWidth() - 2, toolbarHeight); - g.fillRoundedRectangle(toolbarBounds, Corners::windowCornerRadius); - g.fillRect(toolbarBounds.withTrimmedTop(15.0f)); + g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); + g.fillPath(p); g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.drawHorizontalLine(toolbarHeight, 0.0f, getWidth()); @@ -168,7 +170,7 @@ class SettingsDialog : public Component { AudioProcessor* processor; ComponentBoundsConstrainer constrainer; - static constexpr int toolbarHeight = 42; + static constexpr int toolbarHeight = 40; static inline std::atomic lastPanel = 0; int currentPanel; diff --git a/Source/Heavy/DaisyExporter.h b/Source/Heavy/DaisyExporter.h index e325a36cc..789f6a77e 100644 --- a/Source/Heavy/DaisyExporter.h +++ b/Source/Heavy/DaisyExporter.h @@ -37,6 +37,7 @@ class DaisyExporter : public ExporterBase { exportButton.setVisible(false); addAndMakeVisible(flashButton); + flashButton.setColour(TextButton::buttonColourId, findColour(PlugDataColour::panelBackgroundColourId)); flashButton.setColour(TextButton::textColourOnId, findColour(TextButton::textColourOffId)); exportTypeValue.addListener(this); diff --git a/Source/Heavy/ExporterBase.h b/Source/Heavy/ExporterBase.h index dc10dd8eb..218686e72 100644 --- a/Source/Heavy/ExporterBase.h +++ b/Source/Heavy/ExporterBase.h @@ -51,6 +51,8 @@ struct ExporterBase : public Component , editor(pluginEditor) { addAndMakeVisible(exportButton); + + exportButton.setColour(TextButton::buttonColourId, findColour(PlugDataColour::panelBackgroundColourId)); exportButton.setColour(TextButton::textColourOnId, findColour(TextButton::textColourOffId)); Array properties; diff --git a/Source/Heavy/HeavyExportDialog.cpp b/Source/Heavy/HeavyExportDialog.cpp index a43ab5202..23f70de7a 100644 --- a/Source/Heavy/HeavyExportDialog.cpp +++ b/Source/Heavy/HeavyExportDialog.cpp @@ -76,7 +76,7 @@ class ExporterSettingsPanel : public Component { auto listboxBounds = getLocalBounds().removeFromLeft(listBoxWidth); - g.setColour(findColour(PlugDataColour::outlineColourId)); + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); g.drawLine(Line(listboxBounds.getTopRight().toFloat(), listboxBounds.getBottomRight().toFloat())); } @@ -145,18 +145,31 @@ HeavyExportDialog::HeavyExportDialog(Dialog* dialog) : exportingView(new ExportingProgressView()) , exporterPanel(new ExporterSettingsPanel(dynamic_cast(dialog->parentComponent), exportingView.get())) , installer(new ToolchainInstaller(dynamic_cast(dialog->parentComponent))) + , infoButton(new TextButton(Icons::Help)) { - hasToolchain = Toolchain::dir.exists(); - - // Create integer versions by removing the dots - // Compare latest version on github to the currently installed version - auto const latestVersion = URL("https://raw.githubusercontent.com/plugdata-team/plugdata-heavy-toolchain/main/VERSION").readEntireTextStream().trim().removeCharacters(".").getIntValue(); + // Don't do this relative to toolchain variable, that won't work on Windows auto const versionFile = ProjectInfo::appDataDir.getChildFile("Toolchain").getChildFile("VERSION"); auto const installedVersion = versionFile.loadFileAsString().trim().removeCharacters(".").getIntValue(); + + // Create integer versions by removing the dots + // Compare latest version on github to the currently installed version + int latestVersion; + try { + auto compatTable = JSON::parse(URL("https://raw.githubusercontent.com/plugdata-team/plugdata-heavy-toolchain/main/COMPATIBILITY").readEntireTextStream()); + // Get latest version + + latestVersion = compatTable.getDynamicObject()->getProperty(String(ProjectInfo::versionString).upToFirstOccurrenceOf("-", false, false)).toString().removeCharacters(".").getIntValue(); + } + // Network error, JSON error or empty version string somehow + catch (...) { + latestVersion = installedVersion; + return; + } + if (hasToolchain && latestVersion > installedVersion) { installer->needsUpdate = true; hasToolchain = false; @@ -168,6 +181,12 @@ HeavyExportDialog::HeavyExportDialog(Dialog* dialog) exportingView->setAlwaysOnTop(true); + infoButton->getProperties().set("Style", "LargeIcon"); + infoButton->onClick = [](){ + URL("https://wasted-audio.github.io/hvcc/docs/01.introduction.html#what-is-heavy").launchInDefaultBrowser(); + }; + addAndMakeVisible(*infoButton); + installer->toolchainInstalledCallback = [this]() { hasToolchain = true; exporterPanel->setVisible(true); @@ -191,12 +210,29 @@ void HeavyExportDialog::paint(Graphics& g) { g.setColour(findColour(PlugDataColour::panelBackgroundColourId)); g.fillRoundedRectangle(getLocalBounds().toFloat(), Corners::windowCornerRadius); + + auto titlebarBounds = getLocalBounds().removeFromTop(40); + + Path p; + p.addRoundedRectangle(titlebarBounds.getX(), titlebarBounds.getY(), titlebarBounds.getWidth(), titlebarBounds.getHeight(), Corners::largeCornerRadius, Corners::largeCornerRadius, true, true, false, false); + + g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); + g.fillPath(p); + + Fonts::drawStyledText(g, "Compiler", Rectangle(0.0f, 4.0f, getWidth(), 32.0f), findColour(PlugDataColour::panelTextColourId), Semibold, 15, Justification::centred); +} + +void HeavyExportDialog::paintOverChildren(Graphics& g) +{ + g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); + g.drawHorizontalLine(40, 0.0f, getWidth()); } void HeavyExportDialog::resized() { - auto b = getLocalBounds(); + auto b = getLocalBounds().withTrimmedTop(40); exporterPanel->setBounds(b); installer->setBounds(b); exportingView->setBounds(b); + infoButton->setBounds(Rectangle(40, 40)); } diff --git a/Source/Heavy/HeavyExportDialog.h b/Source/Heavy/HeavyExportDialog.h index e20fe69c6..30bf0edd1 100644 --- a/Source/Heavy/HeavyExportDialog.h +++ b/Source/Heavy/HeavyExportDialog.h @@ -15,13 +15,15 @@ class HeavyExportDialog : public Component { std::unique_ptr exportingView; std::unique_ptr installer; std::unique_ptr exporterPanel; - + std::unique_ptr infoButton; + public: explicit HeavyExportDialog(Dialog* dialog); ~HeavyExportDialog() override; void paint(Graphics& g) override; + void paintOverChildren(Graphics& g) override; void resized() override; }; diff --git a/Source/Heavy/Toolchain.h b/Source/Heavy/Toolchain.h index 740323584..9efa0e2ae 100644 --- a/Source/Heavy/Toolchain.h +++ b/Source/Heavy/Toolchain.h @@ -215,8 +215,8 @@ struct ToolchainInstaller : public Component float downloadBarBgHeight = 11.0f; float downloadBarHeight = downloadBarBgHeight - 3.0f; - auto downloadBarBg = Rectangle(90.0f, 300.0f - (downloadBarBgHeight * 0.5), width, downloadBarBgHeight); - auto downloadBar = Rectangle(91.5f, 300.0f - (downloadBarHeight * 0.5), progress, downloadBarHeight); + auto downloadBarBg = Rectangle(90.0f, 250.0f - (downloadBarBgHeight * 0.5), width, downloadBarBgHeight); + auto downloadBar = Rectangle(91.5f, 250.0f - (downloadBarHeight * 0.5), progress, downloadBarHeight); g.setColour(findColour(PlugDataColour::panelTextColourId)); PlugDataLook::fillSmoothedRectangle(g, downloadBarBg, Corners::defaultCornerRadius); @@ -308,7 +308,7 @@ struct ToolchainInstaller : public Component + "\nchmod +x " + tcPath + "/bin/*" + "\nchmod +x " + tcPath + "/lib/dpf/utils/generate-ttl.sh" + "\nchmod +x " + tcPath + "/arm-none-eabi/bin/*" - + "\nchmod +x " + tcPath + "/libexec/gcc/arm-none-eabi/*/*" + + "\nchmod +x " + tcPath + "/lib/gcc/arm-none-eabi/*/*" # if JUCE_LINUX + "\nchmod +x " + tcPath + "/x86_64-anywhere-linux-gnu/bin/*" + "\nchmod +x " + tcPath + "/x86_64-anywhere-linux-gnu/sysroot/sbin/*" diff --git a/Source/Iolet.cpp b/Source/Iolet.cpp index 2add40560..8b093de95 100644 --- a/Source/Iolet.cpp +++ b/Source/Iolet.cpp @@ -343,14 +343,10 @@ void Iolet::createConnection() // Check type for input and output bool sameDirection = isInlet == c->getIolet()->isInlet; - bool connectionAllowed = c->getIolet()->object != object && !sameDirection; + bool connectionAllowed = c->getIolet() != this && c->getIolet()->object != object && !sameDirection; - // Don't create if this is the same iolet - if (c->getIolet() == this) { - object->cnv->connectionsBeingCreated.removeObject(c); - } // Create new connection if allowed - else if (connectionAllowed) { + if (connectionAllowed) { auto outlet = isInlet ? c->getIolet() : this; auto inlet = isInlet ? this : c->getIolet(); diff --git a/Source/LookAndFeel.h b/Source/LookAndFeel.h index 8b41ed3ad..f2b4a72e9 100644 --- a/Source/LookAndFeel.h +++ b/Source/LookAndFeel.h @@ -20,7 +20,6 @@ inline const std::map> PlugDa { toolbarTextColourId, { "Toolbar text", "toolbar_text", "Toolbar" } }, { toolbarHoverColourId, { "Toolbar hover", "toolbar_hover", "Toolbar" } }, { toolbarActiveColourId, { "Toolbar active text", "toolbar_active", "Toolbar" } }, - { toolbarOutlineColourId, { "Toolbar outline", "toolbar_outline_colour", "Toolbar" } }, { tabBackgroundColourId, { "Tab background", "tabbar_background", "Tabbar" } }, @@ -31,7 +30,6 @@ inline const std::map> PlugDa { canvasBackgroundColourId, { "Canvas background", "canvas_background", "Canvas" } }, { canvasTextColourId, { "Canvas text", "canvas_text", "Canvas" } }, { canvasDotsColourId, { "Canvas dots colour", "canvas_dots", "Canvas" } }, - { outlineColourId, { "Outline", "outline_colour", "Canvas" } }, { guiObjectBackgroundColourId, { "GUI object background", "default_object_background", "Object" } }, { guiObjectInternalOutlineColour, { "GUI Object internal outline colour", "gui_internal_outline_colour", "Object" } }, @@ -53,9 +51,11 @@ inline const std::map> PlugDa { 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" } }, { caretColourId, { "Text editor caret", "caret_colour", "Other" } }, + { toolbarOutlineColourId, { "Outline", "toolbar_outline_colour", "Other" } }, { levelMeterActiveColourId, { "Level meter active", "levelmeter_active", "Level Meter" } }, { levelMeterBackgroundColourId, { "Level meter track", "levelmeter_background", "Level Meter" } }, @@ -263,7 +263,7 @@ struct PlugDataLook : public LookAndFeel_V4 { : Button("") , buttonType(buttonType) { - auto crossThickness = 0.15f; + auto crossThickness = 0.2f; String name; switch (buttonType) { @@ -301,17 +301,18 @@ struct PlugDataLook : public LookAndFeel_V4 { void paintButton(Graphics& g, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override { - auto colour = findColour(TextButton::textColourOffId); + auto circleColour = findColour(PlugDataColour::toolbarHoverColourId); + if(shouldDrawButtonAsHighlighted) circleColour = circleColour.contrasting(0.04f); - g.setColour((!isEnabled() || shouldDrawButtonAsDown) ? colour.withAlpha(0.6f) : colour); + g.setColour(circleColour); + g.fillEllipse(getLocalBounds().withSizeKeepingCentre(getWidth() - 8, getWidth() - 8).toFloat()); - if (shouldDrawButtonAsHighlighted) { - g.setColour(findColour(Slider::thumbColourId)); - } + auto colour = findColour(TextButton::textColourOffId); + g.setColour((!isEnabled() || shouldDrawButtonAsDown) ? colour.withAlpha(0.6f) : colour); auto& p = getToggleState() ? toggledShape : shape; - auto reducedRect = Justification(Justification::centred).appliedToRectangle(Rectangle(getHeight(), getHeight()), getLocalBounds()).toFloat().reduced(getHeight() * 0.3f); + auto reducedRect = Justification(Justification::centred).appliedToRectangle(Rectangle(getHeight(), getHeight()), getLocalBounds()).toFloat().reduced(getHeight() * 0.35f); g.fillPath(p, p.getTransformToScaleToFit(reducedRect, true)); } @@ -919,6 +920,14 @@ struct PlugDataLook : public LookAndFeel_V4 { idealHeight = standardMenuItemHeight > 0 ? standardMenuItemHeight : roundToInt(font.getHeight() * 1.3f); idealWidth = font.getStringWidth(text) + idealHeight; + + #if JUCE_LINUX || JUCE_WINDOWS + // 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(" ")) { + idealWidth -= 25; + } + #endif } } @@ -1575,24 +1584,24 @@ struct PlugDataLook : public LookAndFeel_V4 { " searchbar_colour=\"ff232323\" dashed_signal_connections=\"1\" straight_connections=\"0\"\n" " thin_connections=\"0\" square_iolets=\"0\" square_object_corners=\"0\"/>\n" " \n" + " straight_connections=\"0\" thin_connections=\"0\" square_iolets=\"0\"/>" " \n" " "; + // clang-format on static void resetColours(ValueTree themesTree) @@ -1714,3 +1724,4 @@ struct PlugDataLook : public LookAndFeel_V4 { static inline String currentTheme = "light"; static inline StringArray selectedThemes = { "light", "dark" }; }; + diff --git a/Source/Object.cpp b/Source/Object.cpp index 7b51b9f56..134495bb1 100644 --- a/Source/Object.cpp +++ b/Source/Object.cpp @@ -271,9 +271,30 @@ void Object::mouseMove(MouseEvent const& e) updateMouseCursor(); return; } - - resizeZone = ResizableBorderComponent::Zone::fromPositionOnBorder(getLocalBounds().reduced(margin - 2), BorderSize(5), Point(e.x, e.y)); - + + int zone = 0; + auto b = getLocalBounds().toFloat().reduced(margin - 2); + if (b.contains (e.position) + && !b.reduced(7).contains(e.position)) + { + auto corners = getCorners(); + bool done = false; + + auto minW = jmax(b.getWidth() / 10.0f, jmin (10.0f, b.getWidth() / 3.0f)); + auto minH = jmax(b.getHeight() / 10.0f, jmin (10.0f, b.getHeight() / 3.0f)); + + if (corners[0].contains(e.position) || corners[1].contains(e.position) || (e.position.x < jmax (7.0f, minW) && b.getX() > 0.0f)) + zone |= ResizableBorderComponent::Zone::left; + else if (corners[2].contains(e.position) || corners[3].contains(e.position) || (e.position.x >= b.getWidth() - jmax (7.0f, minW))) + zone |= ResizableBorderComponent::Zone::right; + + if (corners[0].contains(e.position) || corners[3].contains(e.position) || (e.position.y < jmax (7.0f, minH))) + zone |= ResizableBorderComponent::Zone::top; + else if (corners[1].contains(e.position) || corners[2].contains(e.position) || (e.position.y >= b.getHeight() - jmax (7.0f, minH))) + zone |= ResizableBorderComponent::Zone::bottom; + } + + resizeZone = static_cast(zone); validResizeZone = resizeZone.getZoneFlags() != ResizableBorderComponent::Zone::centre && e.originalComponent == this; setMouseCursor(validResizeZone ? resizeZone.getMouseCursor() : MouseCursor::NormalCursor); @@ -380,7 +401,7 @@ void Object::setType(String const& newType, void* existingObject) resized(); // If bounds haven't changed, we'll still want to update gui and iolets bounds // Auto patching - if (!attachedToMouse && getValue(cnv->editor->autoconnect) && numInputs && cnv->lastSelectedObject && cnv->lastSelectedObject->numOutputs) { + if (!attachedToMouse && getValue(cnv->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) { @@ -408,6 +429,7 @@ void Object::setType(String const& newType, void* existingObject) } } cnv->lastSelectedObject = nullptr; + cnv->lastSelectedConnection = nullptr; cnv->editor->updateCommandStatus(); @@ -429,6 +451,18 @@ Array> Object::getCorners() const void Object::paintOverChildren(Graphics& g) { + // 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) + { + auto outlet = cnv->lastSelectedObject->iolets[cnv->lastSelectedObject->numInputs]; + auto fakeInletBounds = Rectangle(16, 4, 8, 8); + g.setColour(findColour(outlet->isSignal ? PlugDataColour::signalColourId : PlugDataColour::dataColourId).brighter()); + g.fillEllipse(fakeInletBounds); + + g.setColour(findColour(PlugDataColour::objectOutlineColourId)); + g.drawEllipse(fakeInletBounds, 1.0f); + } + if (consoleTarget == this) { g.saveState(); @@ -1180,6 +1214,12 @@ void Object::mouseDrag(MouseEvent const& e) } } +// Returns true is the object is showing its initial editor, and doesn't have a GUI yet +bool Object::isInitialEditorShown() +{ + return newObjectEditor != nullptr; +} + void Object::showEditor() { if (!gui) { @@ -1189,12 +1229,14 @@ void Object::showEditor() } } + + void Object::hideEditor() { if (gui) { gui->hideEditor(); } else if (newObjectEditor) { - std::unique_ptr outgoingEditor; + std::unique_ptr outgoingEditor = nullptr; std::swap(outgoingEditor, newObjectEditor); cnv->hideSuggestions(); @@ -1258,6 +1300,7 @@ void Object::openNewObjectEditor() cnv->hideSuggestions(); cnv->objects.removeObject(_this.getComponent()); cnv->lastSelectedObject = nullptr; + cnv->lastSelectedConnection = nullptr; }); }; diff --git a/Source/Object.h b/Source/Object.h index 159a35b54..22dfa1f19 100644 --- a/Source/Object.h +++ b/Source/Object.h @@ -49,6 +49,7 @@ class Object : public Component void showEditor(); void hideEditor(); + bool isInitialEditorShown(); Rectangle getSelectableBounds(); Rectangle getObjectBounds(); diff --git a/Source/ObjectGrid.cpp b/Source/ObjectGrid.cpp index 5aee8698b..21a6f9f00 100644 --- a/Source/ObjectGrid.cpp +++ b/Source/ObjectGrid.cpp @@ -172,11 +172,11 @@ Point ObjectGrid::performFixedResize(Object* toDrag, Point dragOffset, if (draggingWidth && !horizontal) { delta *= ratio; } - if (!draggingWidth && horizontal) - + if (!draggingWidth && horizontal) { if (horizontal ^ draggingWidth) { delta /= ratio; } + } snapTarget += delta; diff --git a/Source/Objects/ArrayObject.h b/Source/Objects/ArrayObject.h index 845958bd8..2bef853c1 100644 --- a/Source/Objects/ArrayObject.h +++ b/Source/Objects/ArrayObject.h @@ -274,7 +274,7 @@ class GraphicalArray : public Component return false; } - // Gets the name of the array + // Gets the name of the array. Currently not used anywhere String getExpandedName() const { if (auto ptr = arr.get()) { @@ -453,7 +453,7 @@ class ArrayEditorDialog : public Component addAndMakeVisible(graph); } - title = graphs[0]->getExpandedName(); + title = graphs[0]->getUnexpandedName(); closeButton.reset(LookAndFeel::getDefaultLookAndFeel().createDocumentWindowButton(DocumentWindow::closeButton)); addAndMakeVisible(closeButton.get()); @@ -590,7 +590,7 @@ class ArrayObject final : public ObjectBase { int fontHeight = 14.0f; - const String text = graphs[0]->getExpandedName(); + const String text = graphs[0]->getUnexpandedName(); if (text.isNotEmpty()) { if (!label) { diff --git a/Source/Objects/FunctionObject.h b/Source/Objects/FunctionObject.h index aa734f29b..c4f039743 100644 --- a/Source/Objects/FunctionObject.h +++ b/Source/Objects/FunctionObject.h @@ -43,9 +43,9 @@ class FunctionObject final : public ObjectBase { Array arr = { function->x_min, function->x_max }; range = var(arr); - - auto sndSym = String::fromUTF8(function->x_send->s_name); - auto rcvSym = String::fromUTF8(function->x_receive->s_name); + + auto sndSym = function->x_snd_set ? String::fromUTF8(function->x_snd_raw->s_name) : getBinbufSymbol(3); + auto rcvSym = function->x_rcv_set ? String::fromUTF8(function->x_rcv_raw->s_name) : getBinbufSymbol(4); sendSymbol = sndSym != "empty" ? sndSym : ""; receiveSymbol = rcvSym != "empty" ? rcvSym : ""; @@ -416,6 +416,7 @@ class FunctionObject final : public ObjectBase { hash("fgcolor"), hash("bgcolor"), hash("init"), + hash("set"), }; } @@ -449,7 +450,8 @@ class FunctionObject final : public ObjectBase { } case hash("init"): case hash("fgcolor"): - case hash("bgcolor"): { + case hash("bgcolor"): + case hash("set"): { update(); break; } diff --git a/Source/Objects/KeyboardObject.h b/Source/Objects/KeyboardObject.h index 66da2b788..1ca1d73d7 100644 --- a/Source/Objects/KeyboardObject.h +++ b/Source/Objects/KeyboardObject.h @@ -262,8 +262,8 @@ class KeyboardObject final : public ObjectBase toggleMode.setValue(obj->x_toggle_mode); sizeProperty.setValue(obj->x_height); - auto sndSym = String::fromUTF8(obj->x_send->s_name); - auto rcvSym = String::fromUTF8(obj->x_receive->s_name); + 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); sendSymbol = sndSym != "empty" ? sndSym : ""; receiveSymbol = rcvSym != "empty" ? rcvSym : ""; diff --git a/Source/Objects/KnobObject.h b/Source/Objects/KnobObject.h index 287bce9ef..d6fd4783e 100644 --- a/Source/Objects/KnobObject.h +++ b/Source/Objects/KnobObject.h @@ -7,6 +7,12 @@ #include #include "TclColours.h" +extern "C" +{ +void knob_get_snd(void* x); +void knob_get_rcv(void* x); +} + class Knob : public Slider { Colour fgColour; @@ -474,11 +480,12 @@ class KnobObject : public ObjectBase { String getSendSymbol() { if (auto knb = ptr.get()) { - - if (!knb->x_snd || !knb->x_snd->s_name) + knob_get_snd(knb.get()); // get unexpanded send symbol from binbuf + + if (!knb->x_snd_raw || !knb->x_snd_raw->s_name) return ""; - auto sym = String::fromUTF8(knb->x_snd->s_name); + auto sym = String::fromUTF8(knb->x_snd_raw->s_name); if (sym != "empty") { return sym; } @@ -490,10 +497,12 @@ class KnobObject : public ObjectBase { String getReceiveSymbol() { if (auto knb = ptr.get()) { - if (!knb->x_rcv || !knb->x_rcv->s_name) + knob_get_rcv(knb.get()); // get unexpanded receive symbol from binbuf + + if (!knb->x_rcv_raw || !knb->x_rcv_raw->s_name) return ""; - auto sym = String::fromUTF8(knb->x_rcv->s_name); + auto sym = String::fromUTF8(knb->x_rcv_raw->s_name); if (sym != "empty") { return sym; } diff --git a/Source/Objects/ListObject.h b/Source/Objects/ListObject.h index 1beed3197..8f0d22b7d 100644 --- a/Source/Objects/ListObject.h +++ b/Source/Objects/ListObject.h @@ -50,9 +50,6 @@ class ListObject final : public ObjectBase { listLabel.addMouseListener(this, false); - listLabel.setText("0 0", dontSendNotification); - updateFromGui(); - objectParameters.addParamInt("Width (chars)", cDimensions, &sizeProperty); objectParameters.addParamFloat("Minimum", cGeneral, &min); objectParameters.addParamFloat("Maximum", cGeneral, &max); diff --git a/Source/Objects/ObjectBase.cpp b/Source/Objects/ObjectBase.cpp index 19f143b63..60ebf071f 100644 --- a/Source/Objects/ObjectBase.cpp +++ b/Source/Objects/ObjectBase.cpp @@ -71,6 +71,7 @@ void canvas_click(t_canvas* x, t_floatarg xpos, t_floatarg ypos, t_floatarg shif #include "NoteObject.h" #include "ColourPickerObject.h" #include "MidiObjects.h" +#include "OpenFileObject.h" #include "PdTildeObject.h" // Class for non-patchable objects @@ -564,7 +565,20 @@ ObjectBase* ObjectBase::createGui(void* ptr, Object* parent) return new NoteObject(ptr, parent); case hash("knob"): return new KnobObject(ptr, parent); - + case hash("openfile"): { + char* text; + int size; + libpd_get_object_text(ptr, &text, &size); + auto objText = String::fromUTF8(text, size); + bool hyperlink = objText.contains("openfile -h"); + if(hyperlink) + { + return new OpenFileObject(ptr, parent); + } + else { + return new TextObject(ptr, parent); + } + } case hash("noteout"): case hash("pgmout"): case hash("bendout"): { @@ -614,6 +628,22 @@ void ObjectBase::lock(bool isLocked) setInterceptsMouseClicks(isLocked, isLocked); } +String ObjectBase::getBinbufSymbol(int argIndex) +{ + if(auto obj = ptr.get()) + { + auto* binbuf = obj->te_binbuf; + int numAtoms = binbuf_getnatom(binbuf); + if(argIndex < numAtoms) { + char buf[80]; + atom_string(binbuf_getvec(binbuf) + argIndex, buf, 80); + return String::fromUTF8(buf); + } + } + + return {}; +} + Canvas* ObjectBase::getCanvas() { return nullptr; diff --git a/Source/Objects/ObjectBase.h b/Source/Objects/ObjectBase.h index a47abb11f..fbf305d6c 100644 --- a/Source/Objects/ObjectBase.h +++ b/Source/Objects/ObjectBase.h @@ -180,6 +180,8 @@ class ObjectBase : public Component // Call when you start/stop editing a gui object void startEdition(); void stopEdition(); + + String getBinbufSymbol(int argIndex); // Called whenever one of the inspector parameters changes void valueChanged(Value& value) override {}; diff --git a/Source/Objects/ObjectImplementations.h b/Source/Objects/ObjectImplementations.h index 5e46cebc0..caa41c543 100644 --- a/Source/Objects/ObjectImplementations.h +++ b/Source/Objects/ObjectImplementations.h @@ -678,7 +678,7 @@ class CanvasEditObject final : public ImplementationBase , public Value::Listener { bool lastEditMode; - Component::Component::SafePointer cnv; + Component::SafePointer cnv; public: using ImplementationBase::ImplementationBase; @@ -692,10 +692,13 @@ class CanvasEditObject final : public ImplementationBase cnv->locked.removeListener(this); } - cnv = getMainCanvas(ptr.get()->x_canvas); - if (!cnv) - return; - + if(auto edit = ptr.get()) + { + cnv = getMainCanvas(edit->x_canvas); + } + + if (!cnv) return; + // Don't use lock method, because that also responds to temporary lock lastEditMode = getValue(cnv->locked); cnv->locked.addListener(this); diff --git a/Source/Objects/OpenFileObject.h b/Source/Objects/OpenFileObject.h new file mode 100644 index 000000000..b1273bb10 --- /dev/null +++ b/Source/Objects/OpenFileObject.h @@ -0,0 +1,67 @@ +/* + // Copyright (c) 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 "Utility/MidiDeviceManager.h" + +class OpenFileObject final : public TextBase { +public: + + OpenFileObject(void* ptr, Object* object) + : TextBase(ptr, object) + { + } + + void paint(Graphics& g) override + { + auto backgroundColour = object->findColour(PlugDataColour::textObjectBackgroundColourId); + g.setColour(backgroundColour); + g.fillRoundedRectangle(getLocalBounds().toFloat().reduced(0.5f), Corners::objectCornerRadius); + + auto ioletAreaColour = object->findColour(PlugDataColour::ioletAreaColourId); + + if (ioletAreaColour != backgroundColour) { + g.setColour(ioletAreaColour); + g.fillRect(getLocalBounds().removeFromTop(3)); + g.fillRect(getLocalBounds().removeFromBottom(3)); + } + + if (!editor) { + auto textArea = border.subtractedFrom(getLocalBounds()); + + auto scale = getWidth() < 40 ? 0.9f : 1.0f; + + bool locked = getValue(object->locked) || getValue(object->commandLocked); + + auto colour = object->findColour((locked && isMouseOver()) ? PlugDataColour::objectSelectedOutlineColourId : PlugDataColour::canvasTextColourId); + + Fonts::drawFittedText(g, objectText.fromFirstOccurrenceOf("-h", false, false), textArea, colour, numLines, scale); + } + } + + void mouseEnter(const MouseEvent& e) override + { + repaint(); + } + + void mouseExit(const MouseEvent& e) override + { + repaint(); + } + + void mouseDown(const MouseEvent& e) override + { + if(!getValue(object->locked) && !getValue(object->commandLocked)) return; + + if(auto openfile = ptr.get()) + { + pd->sendDirectMessage(openfile.get(), "bang", std::vector{}); + } + } + + void paintOverChildren(Graphics& g) override + { + } +}; diff --git a/Source/Objects/PictureObject.h b/Source/Objects/PictureObject.h index fc6ce41e4..4b23f3744 100644 --- a/Source/Objects/PictureObject.h +++ b/Source/Objects/PictureObject.h @@ -80,9 +80,13 @@ class PictureObject final : public ObjectBase { latch = pic->x_latch; outline = pic->x_outline; reportSize = pic->x_size; - sendSymbol = pic->x_snd_raw == pd->generateSymbol("empty") ? "" : String::fromUTF8(pic->x_snd_raw->s_name); - receiveSymbol = pic->x_rcv_raw == pd->generateSymbol("empty") ? "" : String::fromUTF8(pic->x_rcv_raw->s_name); + + auto sndSym = pic->x_snd_set ? String::fromUTF8(pic->x_snd_raw->s_name) : getBinbufSymbol(3); + auto rcvSym = pic->x_rcv_set ? String::fromUTF8(pic->x_rcv_raw->s_name) : getBinbufSymbol(4); + sendSymbol = sndSym != "empty" ? sndSym : ""; + receiveSymbol = rcvSym != "empty" ? rcvSym : ""; + sizeProperty = Array { var(pic->x_width), var(pic->x_height) }; } diff --git a/Source/Objects/ScopeObject.h b/Source/Objects/ScopeObject.h index 131dab3ce..ffbca497a 100644 --- a/Source/Objects/ScopeObject.h +++ b/Source/Objects/ScopeObject.h @@ -20,7 +20,6 @@ class ScopeBase : public ObjectBase Value signalRange = SynchronousValue(); Value primaryColour = SynchronousValue(); Value secondaryColour = SynchronousValue(); - Value sendSymbol = SynchronousValue(); Value receiveSymbol = SynchronousValue(); Value sizeProperty = SynchronousValue(); @@ -39,7 +38,6 @@ class ScopeBase : public ObjectBase objectParameters.addParamInt("Buffer size", cGeneral, &bufferSize, 128); objectParameters.addParamInt("Delay", cGeneral, &delay, 0); objectParameters.addParamReceiveSymbol(&receiveSymbol); - objectParameters.addParamSendSymbol(&sendSymbol); startTimerHz(25); } @@ -66,10 +64,8 @@ class ScopeBase : public ObjectBase gridColour = colourFromHexArray(scope->x_gg).toString(); sizeProperty = Array { var(scope->x_width), var(scope->x_height) }; - auto rcv = String::fromUTF8(scope->x_rcv_raw->s_name); - if (rcv == "empty") - rcv = ""; - receiveSymbol = rcv; + auto rcvSym = scope->x_rcv_set ? String::fromUTF8(scope->x_rcv_raw->s_name) : getBinbufSymbol(22); + receiveSymbol = rcvSym != "empty" ? rcvSym : ""; Array arr = { scope->x_min, scope->x_max }; signalRange = var(arr); @@ -276,22 +272,15 @@ class ScopeBase : public ObjectBase scope->x_triglevel = getValue(triggerValue); } else if (v.refersToSameSourceAs(receiveSymbol)) { auto* rcv = pd->generateSymbol(receiveSymbol.toString()); - if (auto scope = ptr.get()) { - scope->x_receive = canvas_realizedollar(scope->x_glist, scope->x_rcv_raw = rcv); - - if (scope->x_receive != gensym("")) { - pd_bind(&scope->x_obj.ob_pd, scope->x_receive); - } else { - scope->x_rcv_raw = pd->generateSymbol("empty"); - } - } + auto symbol = receiveSymbol.toString(); + if (auto scope = ptr.get()) + pd->sendDirectMessage(scope.get(), "receive", { symbol }); } } std::vector getAllMessages() override { return { - hash("send"), hash("receive"), hash("fgcolor"), hash("bgcolor"), @@ -302,11 +291,6 @@ class ScopeBase : public ObjectBase void receiveObjectMessage(String const& symbol, std::vector& atoms) override { switch (hash(symbol)) { - case hash("send"): { - if (atoms.size() >= 1) - setParameterExcludingListener(sendSymbol, atoms[0].getSymbol()); - break; - } case hash("receive"): { if (atoms.size() >= 1) setParameterExcludingListener(receiveSymbol, atoms[0].getSymbol()); diff --git a/Source/Objects/TextObject.h b/Source/Objects/TextObject.h index fd0dc3894..3750e267a 100644 --- a/Source/Objects/TextObject.h +++ b/Source/Objects/TextObject.h @@ -31,9 +31,9 @@ struct TextObjectHelper { w = std::max(w, maxIolets * 18); numLines = getNumLines(currentText, w, fontHeight); + // Calculate height so that height with 1 line is 21px, after that scale along with fontheight - auto pixPerLine = fontHeight - 1; - h = numLines * pixPerLine + (21 - pixPerLine); + h = numLines * fontHeight + (21.f - fontHeight); return { x, y, w, h }; } @@ -151,7 +151,7 @@ struct TextObjectHelper { Array xOffsets; auto font = Font(fontSize); - font.getGlyphPositions(text, glyphs, xOffsets); + font.getGlyphPositions(text.trimCharactersAtEnd(";\n"), glyphs, xOffsets); wchar_t lastChar; for (int i = 0; i < xOffsets.size(); i++) { diff --git a/Source/Palettes.h b/Source/Palettes.h index 60afee3e9..2a6fa5fc5 100644 --- a/Source/Palettes.h +++ b/Source/Palettes.h @@ -327,7 +327,13 @@ class PaletteComponent : public Component { void paint(Graphics& g) override { // toolbar bar - g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); + auto backgroundColour = findColour(PlugDataColour::toolbarBackgroundColourId); + if(ProjectInfo::isStandalone && !getTopLevelComponent()->hasKeyboardFocus(true)) + { + backgroundColour = backgroundColour.brighter(backgroundColour.getBrightness() / 2.5f); + } + + g.setColour(backgroundColour); g.fillRect(getLocalBounds().toFloat().removeFromTop(30).withTrimmedTop(0.5f)); g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); @@ -391,7 +397,13 @@ class PaletteSelector : public TextButton { void paint(Graphics& g) override { - g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); + auto backgroundColour = findColour(PlugDataColour::toolbarBackgroundColourId); + if(ProjectInfo::isStandalone && !getTopLevelComponent()->hasKeyboardFocus(true)) + { + backgroundColour = backgroundColour.brighter(backgroundColour.getBrightness() / 2.5f); + } + + g.setColour(backgroundColour); g.fillRect(getLocalBounds().toFloat().withTrimmedTop(0.5f)); if (getToggleState()) { @@ -689,17 +701,22 @@ class Palettes : public Component g.fillRect(getLocalBounds().toFloat().withTrimmedTop(0.5f)); } - g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); + auto backgroundColour = findColour(PlugDataColour::toolbarBackgroundColourId); + if(ProjectInfo::isStandalone && !getTopLevelComponent()->hasKeyboardFocus(true)) + { + backgroundColour = backgroundColour.brighter(backgroundColour.getBrightness() / 2.5f); + } + g.setColour(backgroundColour); g.fillRect(getLocalBounds().toFloat().removeFromLeft(26).withTrimmedTop(0.5f)); } void paintOverChildren(Graphics& g) override { g.setColour(findColour(PlugDataColour::toolbarOutlineColourId)); - g.drawVerticalLine(25, 0, getHeight()); + g.drawLine(25.5f, 0.0f, 25.5f, getHeight()); if (view) { - g.drawLine(getWidth(), 0, getWidth(), getHeight()); + g.drawLine(getWidth() - 0.5f, 0.0f, getWidth() - 0.5f, getHeight()); } } diff --git a/Source/Pd/Instance.cpp b/Source/Pd/Instance.cpp index 101ab27c2..eb1c86254 100644 --- a/Source/Pd/Instance.cpp +++ b/Source/Pd/Instance.cpp @@ -553,7 +553,7 @@ void Instance::unregisterMessageListener(void* object, MessageListener* messageL { ScopedLock lock(messageListenerLock); - if (messageListeners.count(object)) + if (!messageListeners.count(object)) return; auto& listeners = messageListeners[object]; @@ -561,6 +561,9 @@ void Instance::unregisterMessageListener(void* object, MessageListener* messageL if (it != listeners.end()) listeners.erase(it); + + if(listeners.empty()) + messageListeners.erase(object); } void Instance::registerWeakReference(void* ptr, pd_weak_reference* ref) diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index be835b67c..316fd885e 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -287,8 +287,7 @@ PluginEditor::PluginEditor(PluginProcessor& p) } // This is necessary on Linux to make PluginEditor grab keyboard focus on startup - // Otherwise, keyboard shortcuts won't work directly after starting plugdata -#if JUCE_LINUX + // It also appears to be necessary for some DAWs, like Logic ::Timer::callAfterDelay(100, [_this = SafePointer(this)]() { if (!_this) return; @@ -298,7 +297,6 @@ PluginEditor::PluginEditor(PluginProcessor& p) } _this->grabKeyboardFocus(); }); -#endif } PluginEditor::~PluginEditor() @@ -328,6 +326,11 @@ void PluginEditor::paint(Graphics& g) auto baseColour = findColour(PlugDataColour::toolbarBackgroundColourId); + if(ProjectInfo::isStandalone && !getTopLevelComponent()->hasKeyboardFocus(true)) + { + baseColour = baseColour.brighter(baseColour.getBrightness() / 2.5f); + } + bool rounded = wantsRoundedCorners(); if (rounded) { @@ -492,7 +495,7 @@ void PluginEditor::mouseDown(MouseEvent const& e) if (e.getPosition().getY() < toolbarHeight) { if (auto* window = findParentComponentOfClass()) { if (!window->isUsingNativeTitleBar()) - windowDragger.startDraggingComponent(window, e.getEventRelativeTo(window)); + windowDragger.startDraggingWindow(window, e.getEventRelativeTo(window)); } } } @@ -505,7 +508,7 @@ void PluginEditor::mouseDrag(MouseEvent const& e) if (!isMaximised) { if (auto* window = findParentComponentOfClass()) { if (!window->isUsingNativeTitleBar()) - windowDragger.dragComponent(window, e.getEventRelativeTo(window), nullptr); + windowDragger.dragWindow(window, e.getEventRelativeTo(window), nullptr); } } } @@ -1508,7 +1511,9 @@ bool PluginEditor::perform(InvocationInfo const& info) if (objectNames.count(ID)) { if (cnv->getSelectionOfType().size() == 1) { // if 1 object is selected, create new object beneath selected - auto obj = cnv->lastSelectedObject = cnv->getSelectionOfType()[0]; + auto obj = cnv->getSelectionOfType()[0]; + obj->hideEditor(); // If it's still open, it might overwrite lastSelectedObject + cnv->lastSelectedObject = obj; if (obj) { cnv->objects.add(new Object(cnv, objectNames.at(ID), Point( diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index f77b40c23..11deb7b5d 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -15,6 +15,8 @@ #include "Utility/StackShadow.h" // TODO: move to impl #include "Utility/ZoomableDragAndDropContainer.h" #include "Utility/OfflineObjectRenderer.h" +#include "Utility/WindowDragger.h" + #include "SplitView.h" // TODO: move to impl #include "Dialogs/OverlayDisplaySettings.h" #include "Dialogs/SnapSettings.h" @@ -140,7 +142,7 @@ class PluginEditor : public AudioProcessorEditor private: // Used by standalone to handle dragging the window - ComponentDragger windowDragger; + WindowDragger windowDragger; std::unique_ptr saveChooser; std::unique_ptr openChooser; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index f29aabf9a..e9b7563b6 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -88,7 +88,7 @@ PluginProcessor::PluginProcessor() statusbarSource = std::make_unique(); - auto* volumeParameter = new PlugDataParameter(this, "volume", 1.0f, true, 0, 0.0f, 1.0f); + auto* volumeParameter = new PlugDataParameter(this, "volume", 0.8f, true, 0, 0.0f, 1.0f); addParameter(volumeParameter); volume = volumeParameter->getValuePointer(); @@ -220,6 +220,15 @@ void PluginProcessor::initialiseFilesystem() if (!patches.exists()) { patches.createDirectory(); } + + auto testTonePatch = homeDir.getChildFile("testtone.pd"); + auto cpuTestPatch = homeDir.getChildFile("load-meter.pd"); + + if(testTonePatch.exists()) testTonePatch.deleteFile(); + if(cpuTestPatch.exists()) cpuTestPatch.deleteFile(); + + File(versionDataDir.getChildFile("./Documentation/7.stuff/tools/testtone.pd")).copyFileTo(testTonePatch); + File(versionDataDir.getChildFile("./Documentation/7.stuff/tools/load-meter.pd")).copyFileTo(cpuTestPatch); // We want to recreate these symlinks so that they link to the abstractions/docs for the current plugdata version homeDir.getChildFile("Abstractions").deleteFile(); @@ -547,9 +556,10 @@ void PluginProcessor::processBlock(AudioBuffer& buffer, MidiBuffer& midiM int device; auto message = MidiDeviceManager::convertFromSysExFormat(bufferIterator.getMessage(), device); - if (device > midiDeviceManager->getOutputDevices().size()) { + if (enableInternalSynth && (device > midiDeviceManager->getOutputDevices().size() || device == 0)) { midiBufferInternalSynth.addEvent(message, 0); - } else { + } + if(isPositiveAndBelow(device, midiDeviceManager->getOutputDevices().size())) { midiDeviceManager->sendMidiOutputMessage(device, message); } } @@ -578,13 +588,12 @@ void PluginProcessor::processBlock(AudioBuffer& buffer, MidiBuffer& midiM } auto block = dsp::AudioBlock(buffer); - limiter.process(dsp::ProcessContextReplacing(block)); + limiter.process(block); } } void PluginProcessor::process(dsp::AudioBlock buffer, MidiBuffer& midiMessages) { - ScopedNoDenormals noDenormals; int const blockSize = Instance::getBlockSize(); int const numSamples = static_cast(buffer.getNumSamples()); int const adv = audioAdvancement >= 64 ? 0 : audioAdvancement; @@ -1343,11 +1352,20 @@ void PluginProcessor::receiveMidiByte(int const port, int const byte) } else if (midiByteIndex == 0 && byte == 0xf0) { midiByteIsSysex = true; } else { - midiByteBuffer[midiByteIndex++] = static_cast(byte); - if (midiByteIndex >= 3) { - midiBufferOut.addEvent(MidiDeviceManager::convertToSysExFormat(MidiMessage(midiByteBuffer, 3), port), audioAdvancement); - midiByteIndex = 0; + // Handle single-byte messages + if(midiByteIndex == 0 && byte >= 0xf8 && byte <= 0xff) + { + midiBufferOut.addEvent(MidiDeviceManager::convertToSysExFormat(MidiMessage(static_cast(byte)), port), audioAdvancement); } + // Handle 3-byte messages + else { + midiByteBuffer[midiByteIndex++] = static_cast(byte); + if (midiByteIndex >= 3) { + midiBufferOut.addEvent(MidiDeviceManager::convertToSysExFormat(MidiMessage(midiByteBuffer, 3), port), audioAdvancement); + midiByteIndex = 0; + } + } + } } diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 18936ad75..33b5c5368 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -9,6 +9,7 @@ #include #include #include "Utility/Config.h" +#include "Utility/Limiter.h" #include "Pd/Instance.h" #include "Pd/Patch.h" @@ -186,7 +187,7 @@ class PluginProcessor : public AudioProcessor int lastSplitIndex = -1; int lastSetProgram = 0; - dsp::Limiter limiter; + Limiter limiter; std::unique_ptr> oversampler; std::map> textEditorDialogs; diff --git a/Source/Sidebar/DocumentBrowser.h b/Source/Sidebar/DocumentBrowser.h index a76fc83ae..e922db09b 100644 --- a/Source/Sidebar/DocumentBrowser.h +++ b/Source/Sidebar/DocumentBrowser.h @@ -223,13 +223,13 @@ class DocumentBrowserItem : public TreeViewItem int idx = 0; // Sort by folders first for (int i = 0; i < subContentsList->getNumFiles(); ++i) { - if (subContentsList->getFile(i).isDirectory()) { + if (subContentsList->getFile(i).isDirectory() && !subContentsList->getFile(i).getFileName().startsWithChar('.')) { addSubItem(new DocumentBrowserItem(owner, subContentsList, i, idx, subContentsList->getFile(i))); idx++; } } for (int i = 0; i < subContentsList->getNumFiles(); ++i) { - if (subContentsList->getFile(i).existsAsFile()) { + if (subContentsList->getFile(i).existsAsFile() && !subContentsList->getFile(i).getFileName().startsWithChar('.')) { addSubItem(new DocumentBrowserItem(owner, subContentsList, i, idx, subContentsList->getFile(i))); idx++; } @@ -416,8 +416,6 @@ class DocumentBrowserView : public DocumentBrowserViewBase root->setSubContentsList(&directoryContentsList, false); setRootItem(root); - - setRootItemVisible(true); setInterceptsMouseClicks(true, true); setEnabled(true); diff --git a/Source/Sidebar/Sidebar.cpp b/Source/Sidebar/Sidebar.cpp index 61335940b..e8afbca82 100644 --- a/Source/Sidebar/Sidebar.cpp +++ b/Source/Sidebar/Sidebar.cpp @@ -112,8 +112,13 @@ void Sidebar::paint(Graphics& g) g.setColour(findColour(PlugDataColour::sidebarBackgroundColourId)); g.fillRect(0, 0, getWidth(), getHeight()); + auto toolbarColour = findColour(PlugDataColour::toolbarBackgroundColourId); + if(ProjectInfo::isStandalone && !getTopLevelComponent()->hasKeyboardFocus(true)) + { + toolbarColour = toolbarColour.brighter(toolbarColour.getBrightness() / 2.5f); + } // Background for buttons - g.setColour(findColour(PlugDataColour::toolbarBackgroundColourId)); + g.setColour(toolbarColour); g.fillRect(0, 0, getWidth(), 30); if (inspector->isVisible()) { diff --git a/Source/Standalone/PlugDataWindow.h b/Source/Standalone/PlugDataWindow.h index 004c5fa96..4d583a02b 100644 --- a/Source/Standalone/PlugDataWindow.h +++ b/Source/Standalone/PlugDataWindow.h @@ -511,6 +511,7 @@ class PlugDataWindow : public DocumentWindow void propertyChanged(String const& name, var const& value) override { + if (name == "native_window") { bool nativeWindow = static_cast(value); @@ -526,10 +527,9 @@ class PlugDataWindow : public DocumentWindow setResizable(false, false); if (drawWindowShadow) { - #if JUCE_MAC setDropShadowEnabled(true); -#else +#elif JUCE_WINDOWS setDropShadowEnabled(false); #endif @@ -572,12 +572,6 @@ class PlugDataWindow : public DocumentWindow return pluginHolder->processor.get(); } - /* - AudioDeviceManager& getDeviceManager() const noexcept - { - return pluginHolder->deviceManager; - } */ - /** Deletes and re-creates the plugin, resetting it to its default state. */ void resetToDefaultState() { @@ -649,17 +643,17 @@ class PlugDataWindow : public DocumentWindow StackShadow::renderDropShadow(g, localPath, Colour(0, 0, 0).withAlpha(0.6f), radius, { 0, 3 }); } } +#endif + void activeWindowStatusChanged() override { repaint(); - } -#elif JUCE_WINDOWS - void activeWindowStatusChanged() override - { + +#if JUCE_WINDOWS if (drawWindowShadow && !isUsingNativeTitleBar() && dropShadower) dropShadower->repaint(); - } #endif + } void resized() override { @@ -726,30 +720,6 @@ class PlugDataWindow : public DocumentWindow } } - void paintOverChildren(Graphics& g) override - { -#if JUCE_LINUX || JUCE_BSD - if (!owner.isUsingNativeTitleBar() && !owner.hasOpenedDialog()) { - g.setColour(findColour(PlugDataColour::outlineColourId)); - - if (!Desktop::canUseSemiTransparentWindows()) { - g.drawRect(getLocalBounds().toFloat().reduced(getMargin()), 1.0f); - } else { - g.drawRoundedRectangle(getLocalBounds().toFloat().reduced(getMargin()), Corners::windowCornerRadius, 1.0f); - } - } -#elif JUCE_WINDOWS - - g.setColour(findColour(PlugDataColour::outlineColourId)); - if (owner.isUsingNativeTitleBar()) { - g.drawRect(getLocalBounds(), 1.0f); - } else { - g.drawRoundedRectangle(getLocalBounds().toFloat(), Corners::windowCornerRadius, 1.0f); - } - -#endif - } - AudioProcessorEditor* getEditor() { return editor.get(); @@ -891,3 +861,4 @@ inline StandalonePluginHolder* StandalonePluginHolder::getInstance() return nullptr; } + diff --git a/Source/SuggestionComponent.h b/Source/SuggestionComponent.h index 92184cfd6..17ab01d77 100644 --- a/Source/SuggestionComponent.h +++ b/Source/SuggestionComponent.h @@ -626,10 +626,12 @@ class SuggestionComponent : public Component buttons[currentidx]->setToggleState(true, dontSendNotification); } - auto filterNonHvccObjectsIfNeeded = [_this = SafePointer(this)](StringArray& toFilter) { + auto filterObjects = [_this = SafePointer(this)](StringArray& toFilter) { if (!_this || !_this->currentObject) return; + + // When hvcc mode is enabled, show only hvcc compatible objects if (getValue(_this->currentObject->cnv->editor->hvccMode)) { StringArray hvccObjectsFound; @@ -642,6 +644,16 @@ class SuggestionComponent : public Component toFilter = hvccObjectsFound; } + + // Remove unhelpful objects + for(int i = toFilter.size() - 1; i >= 0; i--) + { + if(_this->excludeList.contains(toFilter[i])) + { + toFilter.remove(i); + } + } + }; auto patchDir = currentObject->cnv->patch.getPatchFile().getParentDirectory(); if (!patchDir.isDirectory() || patchDir == File::getSpecialLocation(File::tempDirectory)) @@ -650,8 +662,7 @@ class SuggestionComponent : public Component // Update suggestions auto found = library->autocomplete(currentText, patchDir); - // When hvcc mode is enabled, show only hvcc compatible objects - filterNonHvccObjectsIfNeeded(found); + filterObjects(found); if (found.isEmpty()) { autoCompleteComponent->enableAutocomplete(false); @@ -722,8 +733,8 @@ class SuggestionComponent : public Component applySuggestionsToButtons(found, currentText); } - library->getExtraSuggestions(found.size(), currentText, [this, filterNonHvccObjectsIfNeeded, applySuggestionsToButtons, found, currentText](StringArray s) mutable { - filterNonHvccObjectsIfNeeded(s); + library->getExtraSuggestions(found.size(), currentText, [this, filterObjects, applySuggestionsToButtons, found, currentText](StringArray s) mutable { + filterObjects(s); // This means the extra suggestions have returned too late to still be relevant if (!openedEditor || currentText != openedEditor->getText()) @@ -764,6 +775,33 @@ class SuggestionComponent : public Component SafePointer openedEditor = nullptr; SafePointer currentObject = nullptr; + + StringArray excludeList = { + "number~", // appears before numbox~ alphabetically, but is worse in every way + "allpass_unit", + "echo_unit", + "multi.vsl.unit", + "float2sig.unit", + "imp.mc-unit", + "multi.vsl.unit", + "multi.vsl.clone.ex", + "score-ex1", + "voice", + "args-example", + "dollsym-example", + "fontsize-example", + "oscbank.unit", + "oscbank2.unit", + "osc.mc-unit", + "All_objects", + "All_about_else", + "README.deken", + "else-meta", + "resonbank.unit", + "resonbank2.unit", + "stepnoise.mc-unit", + "rampnoise.mc-unit" + }; int windowMargin; }; diff --git a/Source/Tabbar.cpp b/Source/Tabbar.cpp index 01a6383d5..e7290f53c 100644 --- a/Source/Tabbar.cpp +++ b/Source/Tabbar.cpp @@ -363,6 +363,8 @@ void TabComponent::addTab(String const& tabName, Component* contentComponent, in contentComponents.insert(insertIndex, WeakReference(contentComponent)); tabs->addTab(tabName, findColour(ResizableWindow::backgroundColourId), insertIndex); + + setTabBarDepth(30); // Make sure tabbar isn't invisible resized(); } @@ -495,16 +497,21 @@ Component* TabComponent::getTabContentComponent(int tabIndex) const noexcept void TabComponent::paint(Graphics& g) { - g.fillAll(findColour(PlugDataColour::tabBackgroundColourId)); + auto backgroundColour = findColour(PlugDataColour::tabBackgroundColourId); + + if(ProjectInfo::isStandalone && !getTopLevelComponent()->hasKeyboardFocus(true)) + { + 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); - g.drawLine(0, 0, getWidth(), 0); - g.drawLine(0, 0, 0, getBottom()); } int TabComponent::getIndexOfCanvas(Canvas* cnv) diff --git a/Source/Utility/Config.h b/Source/Utility/Config.h index 425b2d835..36acad808 100644 --- a/Source/Utility/Config.h +++ b/Source/Utility/Config.h @@ -26,7 +26,7 @@ struct ProjectInfo { static inline const File appDataDir = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory).getChildFile("plugdata"); - static inline const String versionSuffix = "-test"; + static inline const String versionSuffix = ""; static inline const File versionDataDir = appDataDir.getChildFile("Versions").getChildFile(ProjectInfo::versionString + versionSuffix); }; diff --git a/Source/Utility/DraggableNumber.h b/Source/Utility/DraggableNumber.h index 8f792d7d0..52dc03030 100644 --- a/Source/Utility/DraggableNumber.h +++ b/Source/Utility/DraggableNumber.h @@ -284,8 +284,6 @@ class DraggableNumber : public Label if (!isBeingEdited()) { auto textArea = getBorderSize().subtractedFrom(getLocalBounds()).toFloat(); - // g.drawText(formatNumber(getText().getDoubleValue(), decimalDrag), textArea, Justification::centredLeft); - auto numberText = formatNumber(getText().getDoubleValue(), decimalDrag); auto extraNumberText = String(); auto numDecimals = numberText.fromFirstOccurrenceOf(".", false, false).length(); diff --git a/Source/Utility/FileSystemWatcher.cxx b/Source/Utility/FileSystemWatcher.cxx index f32928cfa..2d71dc3fb 100644 --- a/Source/Utility/FileSystemWatcher.cxx +++ b/Source/Utility/FileSystemWatcher.cxx @@ -136,6 +136,7 @@ class FileSystemWatcher::Impl : public Thread, ~Impl() { + shouldQuit = true; signalThreadShouldExit(); inotify_rm_watch (fd, wd); close (fd); @@ -151,7 +152,7 @@ class FileSystemWatcher::Impl : public Thread, const struct inotify_event* iNotifyEvent; char* ptr; - while (true) + while (!shouldQuit) { int numRead = read (fd, buf, BUF_LEN); @@ -171,7 +172,8 @@ class FileSystemWatcher::Impl : public Thread, else if (iNotifyEvent->mask & IN_MOVED_TO) e.fsEvent = FileSystemEvent::fileRenamedNewName; else if (iNotifyEvent->mask & IN_DELETE) e.fsEvent = FileSystemEvent::fileDeleted; - + ScopedLock sl(lock); + bool duplicateEvent = false; for (auto existing : events) { @@ -186,6 +188,7 @@ class FileSystemWatcher::Impl : public Thread, events.add (std::move (e)); } + ScopedLock sl (lock); if (events.size() > 0) triggerAsyncUpdate(); } @@ -193,6 +196,8 @@ class FileSystemWatcher::Impl : public Thread, void handleAsyncUpdate() override { + if(shouldQuit) return; + ScopedLock sl (lock); owner.folderChanged (folder); @@ -203,6 +208,7 @@ class FileSystemWatcher::Impl : public Thread, events.clear(); } + std::atomic shouldQuit = false; FileSystemWatcher& owner; File folder; @@ -234,7 +240,7 @@ class FileSystemWatcher::Impl : private AsyncUpdater, : Thread ("FileSystemWatcher::Impl"), owner (o), folder (f) { WCHAR path[_MAX_PATH] = {0}; - wcsncpy (path, folder.getFullPathName().toWideCharPointer(), _MAX_PATH - 1); + wcsncpy_s (path, folder.getFullPathName().toWideCharPointer(), _MAX_PATH - 1); folderHandle = CreateFileW (path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); diff --git a/Source/Utility/Limiter.h b/Source/Utility/Limiter.h new file mode 100644 index 000000000..f866c724b --- /dev/null +++ b/Source/Utility/Limiter.h @@ -0,0 +1,66 @@ +/* + // 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 + +class Limiter +{ +public: + + Limiter() = default; + + void process (dsp::AudioBlock& block) noexcept + { + firstStageCompressor.process(dsp::ProcessContextReplacing(block)); + 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()); + } + } + + void prepare (const dsp::ProcessSpec& spec) + { + jassert (spec.sampleRate > 0); + jassert (spec.numChannels > 0); + + sampleRate = spec.sampleRate; + + firstStageCompressor.prepare (spec); + secondStageCompressor.prepare (spec); + + update(); + reset(); + } + + void reset() + { + firstStageCompressor.reset(); + secondStageCompressor.reset(); + } + +private: + + void update() + { + firstStageCompressor.setThreshold (-8.0f); + firstStageCompressor.setRatio (4.0f); + firstStageCompressor.setAttack (2.0f); + firstStageCompressor.setRelease (200.0f); + + secondStageCompressor.setThreshold (-6.0f); + secondStageCompressor.setRatio (1000.0f); + secondStageCompressor.setAttack (0.001f); + secondStageCompressor.setRelease (releaseTime); + } + + //============================================================================== + dsp::Compressor firstStageCompressor, secondStageCompressor; + + double sampleRate = 44100.0; + float thresholddB = -10.0, releaseTime = 100.0; +}; diff --git a/Source/Utility/MidiDeviceManager.h b/Source/Utility/MidiDeviceManager.h index 671f2e1e2..87901f2b9 100644 --- a/Source/Utility/MidiDeviceManager.h +++ b/Source/Utility/MidiDeviceManager.h @@ -15,34 +15,34 @@ struct MidiDeviceManager : public ChangeListener // We still want to be able to use handy JUCE stuff for MIDI timing, so we treat every MIDI event as sysex static std::vector encodeSysExData(std::vector const& data) { - std::vector encoded_data; + std::vector encodedData; for (auto& value : data) { if (value == 0xF0 || value == 0xF7) { - // If the value is 0xF0 or 0xF7, encode them in the higher 8 bits - encoded_data.push_back(static_cast(value) << 8); + // If the value is 0xF0 or 0xF7, encode them in the higher 8 bits. 0xF0 and 0xF8 are already at the top end, so we only need to shift them by 1 position to put it outside of MIDI range. We can't shift by 8, the sysex bytes could still be recognised as sysex bytes! + encodedData.push_back(static_cast(value) << 1); } else { // Otherwise, just cast the 8-bit value to a 16-bit value - encoded_data.push_back(static_cast(value)); + encodedData.push_back(static_cast(value)); } } - return encoded_data; + return encodedData; } - static std::vector decodeSysExData(std::vector const& encoded_data) + static std::vector decodeSysExData(std::vector const& encodedData) { - std::vector decoded_data; - for (auto& value : encoded_data) { - auto upperByte = value >> 8; + std::vector decodeData; + for (auto& value : encodedData) { + auto upperByte = value >> 1; if (upperByte == 0xF0 || upperByte == 0xF7) { - decoded_data.push_back(upperByte); + decodeData.push_back(upperByte); } else { // Extract the lower 8 bits to obtain the original 8-bit data - decoded_data.push_back(static_cast(value)); + decodeData.push_back(static_cast(value)); } } - return decoded_data; + return decodeData; } - + static MidiMessage convertToSysExFormat(MidiMessage m, int device) { if (ProjectInfo::isStandalone) { @@ -68,6 +68,7 @@ struct MidiDeviceManager : public ChangeListener device = midiMessage.back(); midiMessage.pop_back(); + return MidiMessage(midiMessage.data(), midiMessage.size()); } @@ -348,13 +349,14 @@ struct MidiDeviceManager : public ChangeListener for (auto* midiOutput : midiOutputs) { midiOutput->sendMessageNow(message); } + if(fromPlugdata) fromPlugdata->sendMessageNow(message); return; } auto idToFind = getOutputDevices()[device - 1].identifier; // The order of midiOutputs is not necessarily the same as that of lastMidiOutputs, that's why we need to check - if (idToFind == fromPlugdata->getIdentifier()) { + if (fromPlugdata && idToFind == fromPlugdata->getIdentifier()) { fromPlugdata->sendMessageNow(message); return; } diff --git a/Source/Utility/OSUtils.cpp b/Source/Utility/OSUtils.cpp index eadfa209b..3f23d8dad 100644 --- a/Source/Utility/OSUtils.cpp +++ b/Source/Utility/OSUtils.cpp @@ -162,6 +162,44 @@ OSUtils::KeyboardLayout OSUtils::getKeyboardLayout() // Selects Linux and BSD #if defined(__unix__) && !defined(__APPLE__) + +void OSUtils::hostManagedX11WindowMove(juce::Component* component, const juce::Rectangle& newBounds) +{ + auto* display = juce::XWindowSystem::getInstance()->getDisplay(); + auto* peer = component->getPeer(); + auto window = (Window)peer->getNativeHandle(); + + XUngrabPointer (display, CurrentTime); + + const auto root = XRootWindow(display, XDefaultScreen(display)); + + XEvent ev = {0}; + ev.xclient.type = ClientMessage; + ev.xclient.send_event = True; + ev.xclient.message_type = XInternAtom(display, "_NET_WM_MOVERESIZE", False); + ev.xclient.window = window; + ev.xclient.display = display; + ev.xclient.format = 32; + + auto pos = component->localPointToGlobal(newBounds.getPosition()); + ev.xclient.data.l[0] = pos.getX(); + ev.xclient.data.l[1] = pos.getY(); + ev.xclient.data.l[2] = 8; + ev.xclient.data.l[3] = 0; + ev.xclient.data.l[4] = 1; + + XSendEvent(display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &ev); + XSync(display, 0); + + /* This is a simpler method that will be available once we update to JUCE 7.0.7 + auto* peer = component->getPeer(); + auto pos = component->localPointToGlobal(newBounds.getPosition()); + auto window = (Window)peer->getNativeHandle(); + + juce::XWindowSystem::getInstance()->startHostManagedResize(window, pos, (juce::ResizableBorderComponent::Zone)juce::ResizableBorderComponent::Zone::centre); + */ +} + bool OSUtils::isX11WindowMaximised(void* handle) { enum window_state_t { @@ -333,7 +371,7 @@ static juce::Array iterateDirectoryRecurse(cpath::Dir&& dir, bool re result.addArray(iterateDirectoryRecurse(std::move(file->ToDir().GetRaw()), recursive, onlyFiles, maximum)); } if ((isDir && !onlyFiles) || !isDir) { - result.add(juce::File(juce::String(file->GetPath().GetRawPath()->buf))); + result.add(juce::File(juce::String::fromUTF8(file->GetPath().GetRawPath()->buf))); } if (maximum > 0 && result.size() >= maximum) diff --git a/Source/Utility/OSUtils.h b/Source/Utility/OSUtils.h index e6f0589c4..f56998c65 100644 --- a/Source/Utility/OSUtils.h +++ b/Source/Utility/OSUtils.h @@ -22,6 +22,7 @@ struct OSUtils { #elif defined(__unix__) && !defined(__APPLE__) static void maximiseX11Window(void* handle, bool shouldBeMaximised); static bool isX11WindowMaximised(void* handle); + static void hostManagedX11WindowMove(juce::Component* handle, const juce::Rectangle& bounds); #elif JUCE_MAC static void enableInsetTitlebarButtons(void* nativeHandle, bool enabled); static void HideTitlebarButtons(void* view, bool hideMinimiseButton, bool hideMaximiseButton, bool hideCloseButton); diff --git a/Source/Utility/PluginParameter.h b/Source/Utility/PluginParameter.h index 479a8e707..0034ca0ff 100644 --- a/Source/Utility/PluginParameter.h +++ b/Source/Utility/PluginParameter.h @@ -50,7 +50,7 @@ class PlugDataParameter : public RangedAudioParameter { range.end = max; } - void setMode(Mode newMode) + void setMode(Mode newMode, bool notify = true) { mode = newMode; if (newMode == Logarithmic) { @@ -69,7 +69,7 @@ class PlugDataParameter : public RangedAudioParameter { setValue(std::floor(getValue())); } - notifyDAW(); + if(notify) notifyDAW(); } // Reports whether the current DAW/format can deal with dynamic @@ -100,11 +100,6 @@ class PlugDataParameter : public RangedAudioParameter { void setEnabled(bool shouldBeEnabled) { - if (!enabled && shouldBeEnabled) { - range = NormalisableRange(0.0f, 1.0f, 0.000001f); - mode = Float; - } - enabled = shouldBeEnabled; } @@ -161,7 +156,7 @@ class PlugDataParameter : public RangedAudioParameter { bool isDiscrete() const override { - return mode.load() == Integer; + return mode == Integer; } bool isOrientationInverted() const override @@ -263,14 +258,13 @@ class PlugDataParameter : public RangedAudioParameter { if (xmlParam->hasAttribute("mode")) { mode = static_cast(xmlParam->getIntAttribute("mode")); } - - param->setEnabled(enabled); + param->setRange(min, max); param->setName(name); - param->setValueNotifyingHost(navalue); param->setIndex(index); - param->setMode(mode); - param->notifyDAW(); + param->setMode(mode, false); + param->setValue(navalue); + param->setEnabled(enabled); } } @@ -321,7 +315,7 @@ class PlugDataParameter : public RangedAudioParameter { String name; std::atomic enabled = false; - std::atomic mode; + Mode mode; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PlugDataParameter) }; diff --git a/Source/Utility/PropertiesPanel.h b/Source/Utility/PropertiesPanel.h index b7b1fb98e..126c9b84b 100644 --- a/Source/Utility/PropertiesPanel.h +++ b/Source/Utility/PropertiesPanel.h @@ -705,7 +705,8 @@ class PropertiesPanel : public Component { draggableNumber->onEditorShow = [this, draggableNumber]() { auto* editor = draggableNumber->getCurrentTextEditor(); - + editor->setBorder(BorderSize(2, 1, 4, 1)); + editor->setJustification(Justification::centredLeft); if constexpr (std::is_floating_point::value) { editor->setInputRestrictions(0, "0123456789.-"); } else if constexpr (std::is_integral::value) { diff --git a/Source/Utility/SettingsFile.h b/Source/Utility/SettingsFile.h index bad3ce1f0..d97bb9632 100644 --- a/Source/Utility/SettingsFile.h +++ b/Source/Utility/SettingsFile.h @@ -109,7 +109,9 @@ class SettingsFile : public ValueTree::Listener { "order", var(0) }, { "direction", var(0) }, { "global_scale", var(1.0f) }, + { "default_zoom", var(100.0f) }, { "show_palettes", var(true) }, + { "center_resized_canvas", var(true) }, { "show_all_audio_device_rates", var(false) }, { "add_object_menu_pinned", var(false) }, { "macos_buttons", diff --git a/Source/Utility/WindowDragger.h b/Source/Utility/WindowDragger.h new file mode 100644 index 000000000..457b04d2c --- /dev/null +++ b/Source/Utility/WindowDragger.h @@ -0,0 +1,63 @@ +/* + // 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 + +class WindowDragger +{ +public: + + WindowDragger() = default; + ~WindowDragger() = default; + + void startDraggingWindow (Component* componentToDrag, + const MouseEvent& e) + { + jassert (componentToDrag != nullptr); + jassert (e.mods.isAnyMouseButtonDown()); // The event has to be a drag event! + + if (componentToDrag != nullptr) + mouseDownWithinTarget = e.getEventRelativeTo (componentToDrag).getMouseDownPosition(); + +#if JUCE_LINUX + // This will tell X11 to act as if the titlebar is being dragged, and can make window dragging behave better. + // This will sometimes also work on XWayland, but not always + OSUtils::hostManagedX11WindowMove(componentToDrag, {mouseDownWithinTarget.x, mouseDownWithinTarget.y, 0, 0}); +#endif +} + + void dragWindow (Component* componentToDrag, + const MouseEvent& e, + ComponentBoundsConstrainer* constrainer) + { + jassert (componentToDrag != nullptr); + jassert (e.mods.isAnyMouseButtonDown()); // The event has to be a drag event! + + if (componentToDrag != nullptr) + { + auto bounds = componentToDrag->getBounds(); + + // If the component is a window, multiple mouse events can get queued while it's in the same position, + // so their coordinates become wrong after the first one moves the window, so in that case, we'll use + // the current mouse position instead of the one that the event contains... + if (componentToDrag->isOnDesktop()) + bounds += componentToDrag->getLocalPoint (nullptr, e.source.getScreenPosition()).roundToInt() - mouseDownWithinTarget; + else + bounds += e.getEventRelativeTo (componentToDrag).getPosition() - mouseDownWithinTarget; + + componentToDrag->getPeer()->setBounds (bounds, false); + } + + } + +private: + //============================================================================== + Point mouseDownWithinTarget; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowDragger) +}; + +