diff --git a/melatonin/components/fps_meter.h b/melatonin/components/fps_meter.h index 113193b..f54eec9 100644 --- a/melatonin/components/fps_meter.h +++ b/melatonin/components/fps_meter.h @@ -14,10 +14,8 @@ namespace melatonin class FPSMeter : public juce::Component, private juce::Timer { public: - explicit FPSMeter (juce::Component& o) : overlay (o) + FPSMeter () { - overlay.addChildComponent (this); - // don't repaint the parent // unfortunately, on macOS, this no longer works // See FAQ in README for more info @@ -26,6 +24,19 @@ namespace melatonin setInterceptsMouseClicks (false, false); } + void setRoot (juce::Component& o) + { + overlay = &o; + + overlay->addChildComponent (this); + } + + void clearRoot() + { + if (overlay) + overlay->removeChildComponent (this); + } + void timerCallback() override { TRACE_EVENT ("component", "fps timer callback"); @@ -108,7 +119,7 @@ namespace melatonin } private: - juce::Component& overlay; + juce::Component* overlay = nullptr; juce::Rectangle bounds; juce::Font font = juce::Font (juce::Font::getDefaultMonospacedFontName(), 16.0f, juce::Font::plain); double lastTime = juce::Time::getMillisecondCounterHiRes(); diff --git a/melatonin/helpers/overlay_mouse_listener.h b/melatonin/helpers/overlay_mouse_listener.h index 1f28161..fc2815b 100644 --- a/melatonin/helpers/overlay_mouse_listener.h +++ b/melatonin/helpers/overlay_mouse_listener.h @@ -8,32 +8,42 @@ namespace melatonin class OverlayMouseListener : public juce::MouseListener { public: - explicit OverlayMouseListener (juce::Component& c, bool startEnabled = true) : root (c) + OverlayMouseListener() { - // Listen to all mouse movements for all children of the root - if (startEnabled) - { - enabled = true; - root.addMouseListener (this, true); - } } ~OverlayMouseListener() override { + if (enabled && root) + root->removeMouseListener (this); + } + + void setRoot (juce::Component& c) + { + root = &c; + if (enabled) - root.removeMouseListener (this); + root->addMouseListener (this, true); + } + + void clearRoot() + { + if (enabled && root) + root->removeMouseListener (this); + + root = nullptr; } void enable() { enabled = true; - root.addMouseListener (this, true); + root->addMouseListener (this, true); } void disable() { enabled = false; - root.removeMouseListener (this); + root->removeMouseListener (this); } void mouseEnter (const juce::MouseEvent& event) override @@ -78,7 +88,7 @@ namespace melatonin void mouseExit (const juce::MouseEvent& event) override { - if (event.originalComponent == &root) + if (event.originalComponent == root) { mouseExitCallback(); } @@ -94,7 +104,7 @@ namespace melatonin private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OverlayMouseListener) - juce::Component& root; + juce::Component* root = nullptr; bool enabled = false; bool isDragging { false }; }; diff --git a/melatonin/inspector_component.h b/melatonin/inspector_component.h index 33c8b86..509cb8d 100644 --- a/melatonin/inspector_component.h +++ b/melatonin/inspector_component.h @@ -19,7 +19,7 @@ namespace melatonin class InspectorComponent : public juce::Component { public: - explicit InspectorComponent (juce::Component& rootComponent, bool enabledAtStart = true) : root (rootComponent), inspectorEnabled (enabledAtStart) + explicit InspectorComponent() { TRACE_COMPONENT(); @@ -48,7 +48,6 @@ namespace melatonin addAndMakeVisible (searchIcon); addChildComponent (clearButton); - colorPicker.setRootComponent (&root); colorPicker.togglePickerCallback = [this] (bool value) { if (toggleOverlayCallback) { @@ -153,6 +152,24 @@ namespace melatonin tree.setRootItem (nullptr); } + void setRoot (juce::Component& r) + { + root = &r; + colorPicker.setRootComponent (root); + + tree.setRootItem (nullptr); + rootItem = nullptr; + + if (inspectorEnabled) + ensureTreeIsConstructed(); + } + + void clearRoot() + { + root = nullptr; + colorPicker.setRootComponent (nullptr); + } + void paint (juce::Graphics& g) override { auto mainPanelGradient = juce::ColourGradient::horizontal (colors::panelBackgroundDarker, (float) mainColumnBounds.getX(), colors::panelBackgroundLighter, (float) mainColumnBounds.getWidth()); @@ -185,7 +202,7 @@ namespace melatonin tree.setRootItem (nullptr); // construct the root item - rootItem = std::make_unique (&root, outlineComponentCallback, selectComponentCallback); + rootItem = std::make_unique (root, outlineComponentCallback, selectComponentCallback); tree.setRootItem (rootItem.get()); getRoot()->setOpenness (ComponentTreeViewItem::Openness::opennessOpen); @@ -354,10 +371,10 @@ namespace melatonin private: Component::SafePointer selectedComponent; - Component& root; + Component* root = nullptr; juce::SharedResourcePointer settings; ComponentModel model; - bool inspectorEnabled; + bool inspectorEnabled = false; juce::Rectangle mainColumnBounds, topArea, searchBoxBounds, treeViewBounds; InspectorImageButton logo { "logo" }; diff --git a/melatonin_inspector.h b/melatonin_inspector.h index 7d2a2cf..1b46b19 100644 --- a/melatonin_inspector.h +++ b/melatonin_inspector.h @@ -30,7 +30,7 @@ END_JUCE_MODULE_DECLARATION namespace melatonin { - class Inspector : public juce::ComponentListener, public juce::DocumentWindow + class Inspector : public juce::ComponentListener, public juce::DocumentWindow, private juce::Timer { public: class InspectorKeyCommands : public juce::KeyListener @@ -65,21 +65,13 @@ namespace melatonin } }; explicit Inspector (juce::Component& rootComponent, bool inspectorEnabledAtStart = true) - : juce::DocumentWindow ("Melatonin Inspector", colors::background, 7, true), - inspectorComponent (rootComponent), - root (rootComponent) + : juce::DocumentWindow ("Melatonin Inspector", colors::background, 7, true) { TRACE_COMPONENT(); - root.addChildComponent (overlay); - overlay.setBounds (root.getLocalBounds()); - root.addComponentListener (this); - - // allow us to open/close the inspector by key command - // bit sketchy because we're modifying the source app to accept key focus - root.addKeyListener (&keyListener); - root.setWantsKeyboardFocus (true); this->addKeyListener (&keyListener); + setRoot (rootComponent); + // needs to come before the LNF restoreBoundsIfNeeded(); @@ -98,15 +90,48 @@ namespace melatonin ~Inspector() override { - root.removeKeyListener (&keyListener); + clearRoot(); + this->removeKeyListener (&keyListener); - root.removeComponentListener (this); // needed, otherwise removing look and feel will save bounds settings->props.reset(); setLookAndFeel (nullptr); } + void setRoot (juce::Component& rootComponent) + { + clearRoot(); + + root = &rootComponent; + + root->addChildComponent (overlay); + overlay.setBounds (root->getLocalBounds()); + root->addComponentListener (this); + + // allow us to open/close the inspector by key command + // bit sketchy because we're modifying the source app to accept key focus + root->addKeyListener (&keyListener); + root->setWantsKeyboardFocus (true); + + fpsMeter.setRoot (*root); + overlayMouseListener.setRoot (*root); + inspectorComponent.setRoot (*root); + } + + void clearRoot() + { + if (root == nullptr) + return; + + root->removeKeyListener (&keyListener); + root->removeComponentListener (this); + + fpsMeter.clearRoot(); + overlayMouseListener.clearRoot(); + inspectorComponent.clearRoot(); + } + void moved() override { TRACE_COMPONENT(); @@ -288,18 +313,29 @@ namespace melatonin toggle (!inspectorEnabled); } + void setRootFollowsComponentUnderMouse (bool follow) + { + rootFollowsComponentUnderMouse = follow; + + if (rootFollowsComponentUnderMouse) + startTimerHz (25); + else + stopTimer(); + } + std::function onClose; private: juce::SharedResourcePointer settings; melatonin::InspectorLookAndFeel inspectorLookAndFeel; melatonin::InspectorComponent inspectorComponent; - juce::Component& root; + juce::Component::SafePointer root; bool inspectorEnabled = false; melatonin::Overlay overlay; - melatonin::FPSMeter fpsMeter { root }; - melatonin::OverlayMouseListener overlayMouseListener { root, false }; + melatonin::FPSMeter fpsMeter; + melatonin::OverlayMouseListener overlayMouseListener; InspectorKeyCommands keyListener { *this }; + bool rootFollowsComponentUnderMouse = false; // Resize our overlay when the root component changes void componentMovedOrResized (Component& rootComponent, bool wasMoved, bool wasResized) override @@ -310,6 +346,32 @@ namespace melatonin } } + void componentBeingDeleted (juce::Component& component) override + { + if (&component == root) + { + clearRoot(); + + auto& d = juce::Desktop::getInstance(); + for (int i = 0; i < d.getNumComponents(); i++) + { + if (auto c = d.getComponent (i); c != nullptr && c != this) + { + setRoot (*c); + return; + } + } + } + } + + void timerCallback() override + { + for (auto ms : juce::Desktop::getInstance().getMouseSources()) + if (auto c = ms.getComponentUnderMouse()) + if (auto top = c->getTopLevelComponent(); top != nullptr && top != root && top != this) + setRoot (*top); + } + void setupCallbacks() { overlayMouseListener.outlineComponentCallback = [this] (Component* c) { this->outlineComponent (c); }; @@ -328,7 +390,7 @@ namespace melatonin }; inspectorComponent.toggleFPSCallback = [this] (bool enable) { if (enable) - this->fpsMeter.setBounds (root.getLocalBounds().removeFromRight (60).removeFromTop (40)); + this->fpsMeter.setBounds (root->getLocalBounds().removeFromRight (60).removeFromTop (40)); this->fpsMeter.setVisible (enable); }; }