This NodeBase is currently the Timer module implementation.
Source/Node/NodeBase.cpp
/*
==============================================================================
NodeBase.cpp
Created: 16 Aug 2024 3:26:59am
Author: Dirk Schiller
==============================================================================
*/
#include "NodeBase.h"
#include "Manager/NodeManager.h"
NodeBase::NodeBase(int x, int y, NodeManager* manager)
: isSelected(false), manager(manager)
{
setBounds(x, y, 275 + 12, 135);
inputSockets.add(new Socket("Input", true));
inputSockets.add(new Socket("Input"));
outputSockets.add(new Socket("Output", true));
outputSockets.add(new Socket("Output"));
addAndMakeVisible(inputSockets[0]);
addAndMakeVisible(inputSockets[1]);
addAndMakeVisible(outputSockets[0]);
addAndMakeVisible(outputSockets[1]);
auto socketSizeX = inputSockets[0]->getBounds().getWidth();
auto socketSizeY = inputSockets[0]->getBounds().getHeight();
if (inputSockets.size() > 0)
{
juce::Rectangle<int> inputSocketBounds (0, 0, socketSizeX, socketSizeY);
inputSockets[0]->setBounds(inputSocketBounds.withX(45).withY(9));
inputSockets[1]->setBounds(inputSocketBounds.withX(0).withY(95));
}
if (outputSockets.size() > 0)
{
juce::Rectangle<int> outputSocketBounds (0, 0, socketSizeX, socketSizeY);
outputSockets[0]->setBounds(outputSocketBounds.withX(getWidth() - socketSizeX - 15).withY(9));
outputSockets[1]->setBounds(outputSocketBounds.withX(getWidth() - socketSizeX).withY(95));
}
intervalProperty = std::make_unique<PropertyTextEditor>("Hello");
intervalProperty->setBounds(162, 90, intervalProperty->getWidth(), intervalProperty->getHeight());
addAndMakeVisible(intervalProperty.get());
}
void NodeBase::connectSockets(Socket* source, Socket* destination)
{
connections.add({source, destination});
repaint();
}
void NodeBase::deselect()
{
isSelected = false;
repaint();
}
void NodeBase::drawConnections(juce::Graphics& g)
{
g.setColour(juce::Colours::blue);
for (const auto& connection : connections)
{
auto* source = connection.first;
auto* destination = connection.second;
if (source && destination)
{
juce::Rectangle<int> sourceBounds = source->getBounds();
juce::Rectangle<int> destinationBounds = destination->getBounds();
juce::Point<int> sourceCentre = sourceBounds.getCentre();
juce::Point<int> destinationCentre = destinationBounds.getCentre();
g.drawLine(sourceCentre.getX(), sourceCentre.getY(), destinationCentre.getX(), destinationCentre.getY(), 2.0f);
}
}
}
void NodeBase::handleSocketMouseDown(Socket* socket, const juce::MouseEvent& event)
{
// Get the mouse position in the NodeBase component's coordinate space
juce::Point<int> mousePosInComponentSpace = event.getPosition();
// Get the bounds of the socket in the NodeBase coordinate space
juce::Rectangle<int> socketBounds = socket->getBounds();
// Check if the mouse position is within the socket bounds
bool isMouseWithinBounds = socketBounds.contains(mousePosInComponentSpace);
if (isMouseWithinBounds)
{
draggingSocket = socket;
return;
}
for (auto* inputSocket : inputSockets)
{
juce::Rectangle<int> inputSocketBounds = inputSocket->getBounds();
if (inputSocketBounds.contains(mousePosInComponentSpace))
{
draggingSocket = inputSocket;
return;
}
}
for (auto* outputSocket : outputSockets)
{
juce::Rectangle<int> outputSocketBounds = outputSocket->getBounds();
if (outputSocketBounds.contains(mousePosInComponentSpace))
{
draggingSocket = outputSocket;
return;
}
}
}
void NodeBase::handleSocketMouseUp(Socket* socket, const juce::MouseEvent& event)
{
// Detect if a socket was released over another socket
if (draggingSocket != nullptr)
{
for (auto* socket : inputSockets)
{
if (socket != draggingSocket && socket->getBounds().contains(event.getPosition()))
{
connectSockets(draggingSocket, socket);
draggingSocket = nullptr;
return;
}
}
for (auto* socket : outputSockets)
{
if (socket != draggingSocket && socket->getBounds().contains(event.getPosition()))
{
connectSockets(draggingSocket, socket);
draggingSocket = nullptr;
return;
}
}
draggingSocket = nullptr;
}
else
{
// End dragging if no socket was involved
}
}
void NodeBase::mouseDown(const juce::MouseEvent &event)
{
giveAwayKeyboardFocus();
intervalProperty->setHighlightedRegion(juce::Range<int>(0,0));
bool multiSelect = event.mods.isShiftDown() || event.mods.isCommandDown();
manager->deselectAllNodesExcept(this, multiSelect);
isSelected = true;
dragger.startDraggingComponent (this, event);
repaint();
}
void NodeBase::mouseDrag (const juce::MouseEvent& event)
{
if (draggingSocket != nullptr)
{
// Update the position of the dragging indicator or logic here if needed
}
else
{
// Drag the Node
dragger.dragComponent(this, event, nullptr);
}
}
void NodeBase::mouseEnter(const juce::MouseEvent& event)
{
isPropertyTextEditorHovered = true;
repaint();
}
void NodeBase::mouseExit(const juce::MouseEvent& event)
{
isPropertyTextEditorHovered = false;
repaint();
}
void NodeBase::mouseMove (const juce::MouseEvent& event) { }
void NodeBase::mouseUp(const juce::MouseEvent& event) { }
void NodeBase::paint(juce::Graphics& g)
{
// Selection
// if (isSelected) { g.setColour(juce::Colour(0xffffff11)); }
// else if (isHovered) { g.setColour(juce::Colour(0xffffffff)); }
// else { g.setColour(juce::Colour(0x88111111)); }
// g.fillRoundedRectangle(getLocalBounds().toFloat(), 14.0f);
// Body
g.setColour(juce::Colour(0xff111111));
g.fillRoundedRectangle(getLocalBounds().withX(0 + 6).withY(0).withWidth(275).withHeight(135).toFloat(), 12.f);
// Base
g.setColour(juce::Colour(0xff555555));
g.fillRoundedRectangle(getLocalBounds().withX(3 + 6).withY(39).withWidth(269).withHeight(93).toFloat(), 9.f);
// BG Title
g.setColour(juce::Colour(0xff444444));
juce::Path path;
auto bounds = getLocalBounds().withX(3 + 6).withY(39).withWidth(269).withHeight(39);
path.addRoundedRectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), 9.f, 9.f, true, true, false, false);
g.fillPath(path);
g.setColour(juce::Colour(0xff333333));
g.drawLine(3 + 6, 77, 3 + 6 + 269, 77);
// Title
auto typeface = juce::Typeface::createSystemTypefaceFor(Assets::RopaSansRegular_ttf, Assets::RopaSansRegular_ttfSize);
juce::FontOptions options(typeface);
juce::Font font(options);
font.setHeight(23.f);
g.setFont(font);
g.setColour(juce::Colour(0xff919cff));
g.drawText("Timer", 3 + 6 + 13, 39 + 9, font.getStringWidth("Timer"), font.getHeight(), juce::Justification::left);
// Icon
std::unique_ptr<juce::XmlElement> svgIconTimer(juce::XmlDocument::parse(Assets::timer_svg));
changeColor(svgIconTimer.get(), "#919cff");
std::unique_ptr<juce::Drawable> svgDrawableIcon = juce::Drawable::createFromSVG(*svgIconTimer);
svgDrawableIcon->setTransformToFit(juce::Rectangle<float>(getWidth() - 41, 47, 24, 24), juce::RectanglePlacement::centred);
svgDrawableIcon->draw(g, 1.0f);
// Trigger Creator Icon
std::unique_ptr<juce::XmlElement> svgIconCreator(juce::XmlDocument::parse(Assets::creator_svg));
changeColor(svgIconCreator.get(), "#333333");
svgDrawableIcon = juce::Drawable::createFromSVG(*svgIconCreator);
svgDrawableIcon->setTransformToFit(juce::Rectangle<float>(14, 7, 24, 24), juce::RectanglePlacement::centred);
svgDrawableIcon->draw(g, 1.0f);
// Property Text
font.setHeight(19.f);
g.setFont(font);
g.setColour(juce::Colour(0xff111111));
g.drawText("Interval", 3 + 6 + 16, 95, font.getStringWidth("Interval"), font.getHeight(), juce::Justification::left);
// Property Unit
g.setColour(juce::Colour(0xffaaaaaa));
g.drawText("ms", 3 + 6 + 128, 95, font.getStringWidth("ms"), font.getHeight(), juce::Justification::left);
drawConnections(g);
}
void NodeBase::resized() { }
void NodeBase::changeColor(juce::XmlElement* xml, juce::String color_hex)
{
if (xml->hasTagName("svg"))
{
juce::String fill = xml->getStringAttribute("fill");
if (fill.isNotEmpty())
{
fill = color_hex;
xml->setAttribute("fill", fill);
}
}
}
void NodeBase::unFocusPropertyTextEditors() {
giveAwayKeyboardFocus();
intervalProperty->setHighlightedRegion(juce::Range<int>(0,0));
}
void NodeBase::unHoverPropertyTextEditors() {
isPropertyTextEditorHovered = false;
repaint();
}
