Hello,
I am a new JUCE member.
Tried to implement an efficient full screen crosshair cursor.
Unfortunatelly the cursor repaint seems to be more and more laggy as the window size increases (e.g. 800x600 30-40ms, 1920x1080 150ms, 4096x2160 600ms). Somehow the user experience is even worse.
As the code was mostly generated by the ChatGPT I tried to optimize a bit the paint method. A store the previous mouse position and try to restore from the original image if dirty. Unfortunatelly the screen gets blank.
Would you please have a look to the code and advise a solution?
Thank you and a Happy New Year 2025!
#include <JuceHeader.h>
class CrosshairImageComponent : public juce::Component
{
public:
CrosshairImageComponent()
{
// Load a sample image for demonstration purposes.
auto imagePath = juce::File::getSpecialLocation(juce::File::SpecialLocationType::userDesktopDirectory)
.getChildFile("example.jpg"); // Adjust the path to a valid image file.
imagePath = "/home/alpr/src/JUCE_Test/JUCE_Test/vlcsnap.png";
if (imagePath.existsAsFile())
{
cachedImage = juce::ImageFileFormat::loadFrom(imagePath);
}
// Set default mouse position.
mousePosition.setXY(-1, -1);
mousePositionOld.setXY(-1, -1);
setBufferedToImage(true);
}
void paint(juce::Graphics& g) override
{
auto startTime = std::chrono::high_resolution_clock::now();
// Clear the background.
//g.fillAll(juce::Colours::white);
// Draw the cached image if it's valid.
if (cachedImage.isValid())
{
// Attempt to optimize
if (mousePositionOld.x >= 0 and mousePositionOld.y >= 0)
{
g.setOpacity (1.0f);
g.drawImage(cachedImage, 0, mousePositionOld.y, getWidth(), 2, 0, mousePositionOld.y, getWidth(), 2);
g.drawImage(cachedImage, mousePositionOld.x, 0, 2, getHeight(), mousePositionOld.x, 0, 2, getHeight());
}
else
{
g.drawImage(cachedImage, getLocalBounds().toFloat());
}
}
else
{
// Display a placeholder if no image is loaded.
g.setColour(juce::Colours::darkgrey);
g.drawText("No Image Loaded", getLocalBounds(), juce::Justification::centred, true);
}
// Draw crosshair if the mouse is inside the component bounds.
if (mousePosition.x >= 0 && mousePosition.y >= 0)
{
g.setColour(juce::Colours::red);
g.drawLine(0.0f, mousePosition.y, (float)getWidth(), mousePosition.y, 2.0f);
g.drawLine(mousePosition.x, 0.0f, mousePosition.x, (float)getHeight(), 2.0f);
}
auto endTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(endTime - startTime);
juce::Logger::outputDebugString("Paint Execution Time: " + juce::String(duration.count()) + " microseconds");
}
void resized() override
{
// Component resized logic can go here if needed.
}
void mouseMove(const juce::MouseEvent& event) override
{
// Update mouse position and repaint the component.
mousePositionOld = mousePosition;
mousePosition = event.position;
repaint();
}
void mouseExit(const juce::MouseEvent& event) override
{
// Reset mouse position when it exits the component.
mousePosition.setXY(-1, -1);
repaint();
}
private:
juce::Image cachedImage; // Cached image to be displayed.
juce::Point<float> mousePosition; // Current mouse position for crosshair.
juce::Point<float> mousePositionOld; // Previous mouse position for crosshair.
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CrosshairImageComponent)
};
//==============================================================================
class MainComponent : public juce::Component
{
public:
MainComponent()
{
addAndMakeVisible(crosshairImageComponent);
setSize(800, 600); // Set a default size.
}
void resized() override
{
crosshairImageComponent.setBounds(getLocalBounds());
}
private:
CrosshairImageComponent crosshairImageComponent;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainComponent)
};
//==============================================================================
class Application : public juce::JUCEApplication
{
public:
const juce::String getApplicationName() override { return "Crosshair Image Application"; }
const juce::String getApplicationVersion() override { return "1.0"; }
void initialise(const juce::String&) override
{
mainWindow.reset(new MainWindow(getApplicationName()));
}
void shutdown() override
{
mainWindow = nullptr;
}
private:
class MainWindow : public juce::DocumentWindow
{
public:
MainWindow(const juce::String& name)
: DocumentWindow(name, juce::Colours::lightgrey, DocumentWindow::allButtons)
{
setUsingNativeTitleBar(false);
setContentOwned(new MainComponent(), true);
setResizable(true, true);
centreWithSize(getWidth(), getHeight());
setVisible(true);
}
void closeButtonPressed() override
{
juce::JUCEApplication::getInstance()->systemRequestedQuit();
}
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainWindow)
};
std::unique_ptr<MainWindow> mainWindow;
};
//==============================================================================
START_JUCE_APPLICATION(Application)
