kopia lustrzana https://github.com/jameshball/osci-render
Make grid component more generic
rodzic
13a7744fa3
commit
ccfb8391be
|
@ -30,7 +30,7 @@ public:
|
|||
std::make_shared<PerspectiveEffect>(),
|
||||
std::vector<osci::EffectParameter*>{
|
||||
new osci::EffectParameter("Perspective", "Controls the strength of the 3D perspective projection.", "perspectiveStrength", VERSION_HINT, 1.0, 0.0, 1.0),
|
||||
new osci::EffectParameter("FOV", "Controls the camera's field of view in degrees. A lower field of view makes the image look more flat, and a higher field of view makes the image look more 3D.", "perspectiveFov", VERSION_HINT, 50.0, 5.0, 130.0),
|
||||
new osci::EffectParameter("Field of View", "Controls the camera's field of view in degrees. A lower field of view makes the image look more flat, and a higher field of view makes the image look more 3D.", "perspectiveFov", VERSION_HINT, 50.0, 5.0, 130.0),
|
||||
}
|
||||
);
|
||||
return eff;
|
||||
|
|
|
@ -1,25 +1,24 @@
|
|||
#include "EffectTypeGridComponent.h"
|
||||
#include <JuceHeader.h>
|
||||
#include "../LookAndFeel.h"
|
||||
#include "GridComponent.h"
|
||||
#include "GridItemComponent.h"
|
||||
#include <unordered_set>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
// ===================== EffectTypeGridComponent (wrapper) =====================
|
||||
|
||||
EffectTypeGridComponent::EffectTypeGridComponent(OscirenderAudioProcessor& processor)
|
||||
: audioProcessor(processor)
|
||||
{
|
||||
// Setup scrollable viewport and content
|
||||
addAndMakeVisible(viewport);
|
||||
viewport.setViewedComponent(&content, false);
|
||||
viewport.setScrollBarsShown(true, false); // vertical only
|
||||
// Setup reusable bottom fade
|
||||
initScrollFade(*this);
|
||||
attachToViewport(viewport);
|
||||
setupEffectItems();
|
||||
addAndMakeVisible(grid);
|
||||
setSize(400, 200);
|
||||
addAndMakeVisible(cancelButton);
|
||||
cancelButton.onClick = [this]() {
|
||||
if (onCanceled) onCanceled();
|
||||
};
|
||||
setupEffectItems();
|
||||
refreshDisabledStates();
|
||||
}
|
||||
|
||||
|
@ -27,10 +26,8 @@ EffectTypeGridComponent::~EffectTypeGridComponent() = default;
|
|||
|
||||
void EffectTypeGridComponent::setupEffectItems()
|
||||
{
|
||||
// Clear existing items
|
||||
effectItems.clear();
|
||||
content.removeAllChildren();
|
||||
|
||||
grid.clearItems();
|
||||
|
||||
// Get effect types directly from the audio processor's toggleableEffects
|
||||
juce::SpinLock::ScopedLockType lock(audioProcessor.effectsLock);
|
||||
const int n = (int) audioProcessor.toggleableEffects.size();
|
||||
|
@ -52,11 +49,11 @@ void EffectTypeGridComponent::setupEffectItems()
|
|||
// Extract effect name from the effect
|
||||
juce::String effectName = effect->getName();
|
||||
|
||||
// Create new item component
|
||||
auto* item = new EffectTypeItemComponent(effectName, effect->getIcon(), effect->getId());
|
||||
// Create new generic item component
|
||||
auto* item = new GridItemComponent(effectName, effect->getIcon(), effect->getId());
|
||||
|
||||
// Set up callback to forward effect selection
|
||||
item->onEffectSelected = [this](const juce::String& effectId) {
|
||||
// Set up callback to forward selection
|
||||
item->onItemSelected = [this](const juce::String& effectId) {
|
||||
if (onEffectSelected)
|
||||
onEffectSelected(effectId);
|
||||
};
|
||||
|
@ -74,8 +71,7 @@ void EffectTypeGridComponent::setupEffectItems()
|
|||
}
|
||||
};
|
||||
|
||||
effectItems.add(item);
|
||||
content.addAndMakeVisible(item);
|
||||
grid.addItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,13 +89,11 @@ void EffectTypeGridComponent::refreshDisabledStates()
|
|||
selectedIds.insert(eff->getId().toStdString());
|
||||
}
|
||||
}
|
||||
for (auto* item : effectItems) {
|
||||
const bool disable = selectedIds.find(item->getEffectId().toStdString()) != selectedIds.end();
|
||||
for (auto* item : grid.getItems()) {
|
||||
const bool disable = selectedIds.find(item->getId().toStdString()) != selectedIds.end();
|
||||
item->setEnabled(! disable);
|
||||
}
|
||||
cancelButton.setVisible(anySelected);
|
||||
// Update fade visibility/layout in case scrollability changed
|
||||
layoutScrollFade(viewport.getBounds(), true, 48);
|
||||
}
|
||||
|
||||
void EffectTypeGridComponent::paint(juce::Graphics& g)
|
||||
|
@ -112,94 +106,5 @@ void EffectTypeGridComponent::resized()
|
|||
auto bounds = getLocalBounds();
|
||||
auto topBar = bounds.removeFromTop(30);
|
||||
cancelButton.setBounds(topBar.removeFromRight(80).reduced(4));
|
||||
viewport.setBounds(bounds);
|
||||
auto contentArea = viewport.getLocalBounds();
|
||||
// Lock content width to viewport width to avoid horizontal scrolling
|
||||
content.setSize(contentArea.getWidth(), content.getHeight());
|
||||
|
||||
// Create FlexBox for responsive grid layout within content
|
||||
flexBox = juce::FlexBox();
|
||||
flexBox.flexWrap = juce::FlexBox::Wrap::wrap;
|
||||
flexBox.justifyContent = juce::FlexBox::JustifyContent::spaceBetween;
|
||||
flexBox.alignContent = juce::FlexBox::AlignContent::flexStart;
|
||||
flexBox.flexDirection = juce::FlexBox::Direction::row;
|
||||
|
||||
// Determine fixed per-item width for this viewport width
|
||||
const int viewW = contentArea.getWidth();
|
||||
const int viewH = contentArea.getHeight();
|
||||
const int itemsPerRow = juce::jmax(1, viewW / MIN_ITEM_WIDTH);
|
||||
const int fixedItemWidth = (itemsPerRow > 0 ? viewW / itemsPerRow : viewW);
|
||||
|
||||
// Add each effect item with a fixed width, and pad the final row with placeholders so it's centered
|
||||
const int total = effectItems.size();
|
||||
const int fullRows = (itemsPerRow > 0 ? total / itemsPerRow : 0);
|
||||
const int remainder = (itemsPerRow > 0 ? total % itemsPerRow : 0);
|
||||
|
||||
auto addItemFlex = [&](juce::Component* c)
|
||||
{
|
||||
flexBox.items.add(juce::FlexItem(*c)
|
||||
.withMinWidth((float) fixedItemWidth)
|
||||
.withMaxWidth((float) fixedItemWidth)
|
||||
.withHeight((float) ITEM_HEIGHT)
|
||||
.withFlex(1.0f) // keep existing flex behaviour; fixed max width holds size
|
||||
.withMargin(juce::FlexItem::Margin(0)));
|
||||
};
|
||||
|
||||
auto addPlaceholder = [&]()
|
||||
{
|
||||
// Placeholder occupies a slot visually but has no component; ensures last row is centered
|
||||
juce::FlexItem placeholder((float) fixedItemWidth, (float) ITEM_HEIGHT);
|
||||
placeholder.flexGrow = 1.0f; // match item flex for consistent spacing
|
||||
placeholder.margin = juce::FlexItem::Margin(0);
|
||||
flexBox.items.add(std::move(placeholder));
|
||||
};
|
||||
|
||||
int index = 0;
|
||||
// Add complete rows
|
||||
for (int r = 0; r < fullRows; ++r)
|
||||
for (int c = 0; c < itemsPerRow; ++c)
|
||||
addItemFlex(effectItems.getUnchecked(index++));
|
||||
|
||||
// Add last row centered with balanced placeholders
|
||||
if (remainder > 0)
|
||||
{
|
||||
const int missing = itemsPerRow - remainder;
|
||||
const int leftPad = missing / 2;
|
||||
const int rightPad = missing - leftPad;
|
||||
|
||||
for (int i = 0; i < leftPad; ++i) addPlaceholder();
|
||||
for (int i = 0; i < remainder; ++i) addItemFlex(effectItems.getUnchecked(index++));
|
||||
for (int i = 0; i < rightPad; ++i) addPlaceholder();
|
||||
}
|
||||
|
||||
// Compute required content height
|
||||
const int requiredHeight = calculateRequiredHeight(viewW);
|
||||
|
||||
// If content is shorter than viewport, make content at least as tall as viewport
|
||||
int yOffset = 0;
|
||||
if (requiredHeight < viewH) {
|
||||
content.setSize(viewW, viewH);
|
||||
yOffset = (viewH - requiredHeight) / 2;
|
||||
} else {
|
||||
content.setSize(viewW, requiredHeight);
|
||||
}
|
||||
// Layout items within content at the computed offset
|
||||
flexBox.performLayout(juce::Rectangle<float>(0.0f, (float) yOffset, (float) viewW, (float) requiredHeight));
|
||||
|
||||
// Layout bottom scroll fade over the viewport area
|
||||
layoutScrollFade(viewport.getBounds(), true, 48);
|
||||
}
|
||||
|
||||
int EffectTypeGridComponent::calculateRequiredHeight(int availableWidth) const
|
||||
{
|
||||
if (effectItems.isEmpty())
|
||||
return ITEM_HEIGHT;
|
||||
|
||||
// Calculate how many items can fit per row
|
||||
int itemsPerRow = juce::jmax(1, availableWidth / MIN_ITEM_WIDTH);
|
||||
|
||||
// Calculate number of rows needed
|
||||
int numRows = (effectItems.size() + itemsPerRow - 1) / itemsPerRow; // Ceiling division
|
||||
|
||||
return numRows * ITEM_HEIGHT;
|
||||
grid.setBounds(bounds);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
#include "../PluginProcessor.h"
|
||||
#include "EffectTypeItemComponent.h"
|
||||
#include "ScrollFadeMixin.h"
|
||||
#include "GridComponent.h"
|
||||
|
||||
class EffectTypeGridComponent : public juce::Component, private ScrollFadeMixin
|
||||
// Effect-specific wrapper that declares which items appear in the grid
|
||||
class EffectTypeGridComponent : public juce::Component
|
||||
{
|
||||
public:
|
||||
EffectTypeGridComponent(OscirenderAudioProcessor& processor);
|
||||
|
@ -13,23 +13,16 @@ public:
|
|||
void paint(juce::Graphics& g) override;
|
||||
void resized() override;
|
||||
|
||||
int calculateRequiredHeight(int availableWidth) const;
|
||||
std::function<void(const juce::String& effectId)> onEffectSelected;
|
||||
std::function<void()> onCanceled; // optional cancel handler
|
||||
void refreshDisabledStates(); // grey-out items that are already selected
|
||||
|
||||
private:
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
juce::Viewport viewport; // scroll container
|
||||
juce::Component content; // holds the grid items
|
||||
juce::OwnedArray<EffectTypeItemComponent> effectItems;
|
||||
juce::FlexBox flexBox;
|
||||
GridComponent grid;
|
||||
juce::TextButton cancelButton { "Cancel" };
|
||||
|
||||
static constexpr int ITEM_HEIGHT = 80;
|
||||
static constexpr int MIN_ITEM_WIDTH = 180;
|
||||
|
||||
|
||||
void setupEffectItems();
|
||||
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EffectTypeGridComponent)
|
||||
};
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
#include "GridComponent.h"
|
||||
|
||||
GridComponent::GridComponent()
|
||||
{
|
||||
// Setup scrollable viewport and content
|
||||
addAndMakeVisible(viewport);
|
||||
viewport.setViewedComponent(&content, false);
|
||||
viewport.setScrollBarsShown(true, false); // vertical only
|
||||
// Setup reusable bottom fade
|
||||
initScrollFade(*this);
|
||||
attachToViewport(viewport);
|
||||
}
|
||||
|
||||
GridComponent::~GridComponent() = default;
|
||||
|
||||
void GridComponent::clearItems()
|
||||
{
|
||||
items.clear();
|
||||
content.removeAllChildren();
|
||||
}
|
||||
|
||||
void GridComponent::addItem(GridItemComponent* item)
|
||||
{
|
||||
items.add(item);
|
||||
content.addAndMakeVisible(item);
|
||||
}
|
||||
|
||||
void GridComponent::paint(juce::Graphics& g)
|
||||
{
|
||||
// transparent background
|
||||
}
|
||||
|
||||
void GridComponent::resized()
|
||||
{
|
||||
auto bounds = getLocalBounds();
|
||||
viewport.setBounds(bounds);
|
||||
auto contentArea = viewport.getLocalBounds();
|
||||
// Lock content width to viewport width to avoid horizontal scrolling
|
||||
content.setSize(contentArea.getWidth(), content.getHeight());
|
||||
|
||||
// Create FlexBox for responsive grid layout within content
|
||||
flexBox = juce::FlexBox();
|
||||
flexBox.flexWrap = juce::FlexBox::Wrap::wrap;
|
||||
flexBox.justifyContent = juce::FlexBox::JustifyContent::spaceBetween;
|
||||
flexBox.alignContent = juce::FlexBox::AlignContent::flexStart;
|
||||
flexBox.flexDirection = juce::FlexBox::Direction::row;
|
||||
|
||||
// Determine fixed per-item width for this viewport width
|
||||
const int viewW = contentArea.getWidth();
|
||||
const int viewH = contentArea.getHeight();
|
||||
const int itemsPerRow = juce::jmax(1, viewW / MIN_ITEM_WIDTH);
|
||||
const int fixedItemWidth = (itemsPerRow > 0 ? viewW / itemsPerRow : viewW);
|
||||
|
||||
// Add each item with a fixed width, and pad the final row with placeholders so it's centered
|
||||
const int total = items.size();
|
||||
const int fullRows = (itemsPerRow > 0 ? total / itemsPerRow : 0);
|
||||
const int remainder = (itemsPerRow > 0 ? total % itemsPerRow : 0);
|
||||
|
||||
auto addItemFlex = [&](juce::Component* c)
|
||||
{
|
||||
flexBox.items.add(juce::FlexItem(*c)
|
||||
.withMinWidth((float) fixedItemWidth)
|
||||
.withMaxWidth((float) fixedItemWidth)
|
||||
.withHeight(80.0f)
|
||||
.withFlex(1.0f)
|
||||
.withMargin(juce::FlexItem::Margin(0)));
|
||||
};
|
||||
|
||||
auto addPlaceholder = [&]()
|
||||
{
|
||||
// Placeholder occupies a slot visually but has no component; ensures last row is centered
|
||||
juce::FlexItem placeholder((float) fixedItemWidth, 80.0f);
|
||||
placeholder.flexGrow = 1.0f; // match item flex for consistent spacing
|
||||
placeholder.margin = juce::FlexItem::Margin(0);
|
||||
flexBox.items.add(std::move(placeholder));
|
||||
};
|
||||
|
||||
int index = 0;
|
||||
// Add complete rows
|
||||
for (int r = 0; r < fullRows; ++r)
|
||||
for (int c = 0; c < itemsPerRow; ++c)
|
||||
addItemFlex(items.getUnchecked(index++));
|
||||
|
||||
// Add last row centered with balanced placeholders
|
||||
if (remainder > 0)
|
||||
{
|
||||
const int missing = itemsPerRow - remainder;
|
||||
const int leftPad = missing / 2;
|
||||
const int rightPad = missing - leftPad;
|
||||
|
||||
for (int i = 0; i < leftPad; ++i) addPlaceholder();
|
||||
for (int i = 0; i < remainder; ++i) addItemFlex(items.getUnchecked(index++));
|
||||
for (int i = 0; i < rightPad; ++i) addPlaceholder();
|
||||
}
|
||||
|
||||
// Compute required content height
|
||||
const int requiredHeight = calculateRequiredHeight(viewW);
|
||||
|
||||
// If content is shorter than viewport, make content at least as tall as viewport
|
||||
int yOffset = 0;
|
||||
if (requiredHeight < viewH) {
|
||||
content.setSize(viewW, viewH);
|
||||
yOffset = (viewH - requiredHeight) / 2;
|
||||
} else {
|
||||
content.setSize(viewW, requiredHeight);
|
||||
}
|
||||
// Layout items within content at the computed offset
|
||||
flexBox.performLayout(juce::Rectangle<float>(0.0f, (float) yOffset, (float) viewW, (float) requiredHeight));
|
||||
|
||||
// Layout bottom scroll fade over the viewport area
|
||||
layoutScrollFadeIfNeeded();
|
||||
}
|
||||
|
||||
int GridComponent::calculateRequiredHeight(int availableWidth) const
|
||||
{
|
||||
if (items.isEmpty())
|
||||
return 80; // ITEM_HEIGHT
|
||||
|
||||
// Calculate how many items can fit per row
|
||||
int itemsPerRow = juce::jmax(1, availableWidth / MIN_ITEM_WIDTH);
|
||||
|
||||
// Calculate number of rows needed
|
||||
int numRows = (items.size() + itemsPerRow - 1) / itemsPerRow; // Ceiling division
|
||||
|
||||
return numRows * 80; // ITEM_HEIGHT
|
||||
}
|
||||
|
||||
void GridComponent::layoutScrollFadeIfNeeded()
|
||||
{
|
||||
layoutScrollFade(viewport.getBounds(), true, 48);
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
#include "ScrollFadeMixin.h"
|
||||
#include "GridItemComponent.h"
|
||||
|
||||
// Generic grid component that owns and lays out GridItemComponent children
|
||||
class GridComponent : public juce::Component, private ScrollFadeMixin
|
||||
{
|
||||
public:
|
||||
GridComponent();
|
||||
~GridComponent() override;
|
||||
|
||||
void paint(juce::Graphics& g) override;
|
||||
void resized() override;
|
||||
|
||||
void clearItems();
|
||||
void addItem(GridItemComponent* item); // takes ownership
|
||||
juce::OwnedArray<GridItemComponent>& getItems() { return items; }
|
||||
int calculateRequiredHeight(int availableWidth) const;
|
||||
|
||||
private:
|
||||
juce::Viewport viewport; // scroll container
|
||||
juce::Component content; // holds the grid items
|
||||
juce::OwnedArray<GridItemComponent> items;
|
||||
juce::FlexBox flexBox;
|
||||
|
||||
static constexpr int ITEM_HEIGHT = 80;
|
||||
static constexpr int MIN_ITEM_WIDTH = 180;
|
||||
|
||||
void layoutScrollFadeIfNeeded();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GridComponent)
|
||||
};
|
|
@ -0,0 +1,123 @@
|
|||
#include "GridItemComponent.h"
|
||||
|
||||
GridItemComponent::GridItemComponent(const juce::String& name, const juce::String& icon, const juce::String& id)
|
||||
: itemName(name), itemId(id)
|
||||
{
|
||||
juce::String iconSvg = icon;
|
||||
if (icon.isEmpty()) {
|
||||
// Default icon if none is provided
|
||||
iconSvg = juce::String::createStringFromData(BinaryData::rotate_svg, BinaryData::rotate_svgSize);
|
||||
}
|
||||
iconButton = std::make_unique<SvgButton>(
|
||||
"gridItemIcon",
|
||||
iconSvg,
|
||||
juce::Colours::white.withAlpha(0.7f)
|
||||
);
|
||||
|
||||
// Make the icon non-interactive since this is just a visual element
|
||||
iconButton->setInterceptsMouseClicks(false, false);
|
||||
addAndMakeVisible(*iconButton);
|
||||
}
|
||||
|
||||
GridItemComponent::~GridItemComponent() = default;
|
||||
|
||||
void GridItemComponent::paint(juce::Graphics& g)
|
||||
{
|
||||
auto bounds = getLocalBounds().toFloat().reduced(10);
|
||||
|
||||
// Get animation progress from inherited HoverAnimationMixin (disabled => no hover)
|
||||
auto animationProgress = isEnabled() ? getAnimationProgress() : 0.0f;
|
||||
|
||||
// Apply upward shift based on animation progress
|
||||
auto yOffset = -animationProgress * HOVER_LIFT_AMOUNT;
|
||||
bounds = bounds.translated(0, yOffset);
|
||||
|
||||
// Draw drop shadow
|
||||
if (animationProgress > 0.01f) {
|
||||
juce::DropShadow shadow;
|
||||
shadow.colour = juce::Colours::lime.withAlpha(animationProgress * 0.2f);
|
||||
shadow.radius = 15 * animationProgress;
|
||||
shadow.offset = juce::Point<int>(0, 4);
|
||||
|
||||
if (shadow.radius > 0) {
|
||||
juce::Path shadowPath;
|
||||
shadowPath.addRoundedRectangle(bounds.toFloat(), CORNER_RADIUS);
|
||||
shadow.drawForPath(g, shadowPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw background with rounded corners - interpolate between normal and hover colors
|
||||
juce::Colour normalBgColour = Colours::veryDark;
|
||||
juce::Colour hoverBgColour = normalBgColour.brighter(0.05f);
|
||||
juce::Colour bgColour = normalBgColour.interpolatedWith(hoverBgColour, animationProgress);
|
||||
|
||||
g.setColour(bgColour);
|
||||
g.fillRoundedRectangle(bounds.toFloat(), CORNER_RADIUS);
|
||||
|
||||
// Draw colored outline
|
||||
juce::Colour outlineColour = juce::Colour::fromRGB(160, 160, 160);
|
||||
g.setColour(outlineColour.withAlpha(0.9f));
|
||||
g.drawRoundedRectangle(bounds.toFloat(), CORNER_RADIUS, 1.0f);
|
||||
|
||||
// Create text area - now accounting for icon space on the left
|
||||
auto textArea = bounds.reduced(8, 4);
|
||||
textArea.removeFromLeft(28); // Remove space for icon (24px + 4px gap)
|
||||
|
||||
g.setColour(juce::Colours::white);
|
||||
g.setFont(juce::FontOptions(16.0f, juce::Font::plain));
|
||||
g.drawText(itemName, textArea, juce::Justification::centred, true);
|
||||
|
||||
// If disabled, draw a dark transparent overlay over the rounded rect to simplify visuals
|
||||
if (! isEnabled()) {
|
||||
g.setColour(juce::Colours::black.withAlpha(0.35f));
|
||||
g.fillRoundedRectangle(bounds.toFloat(), CORNER_RADIUS);
|
||||
}
|
||||
}
|
||||
|
||||
void GridItemComponent::resized()
|
||||
{
|
||||
auto bounds = getLocalBounds().reduced(10);
|
||||
|
||||
// Reserve space for the icon on the left
|
||||
auto iconArea = bounds.removeFromLeft(60); // 24px for icon
|
||||
iconArea = iconArea.withSizeKeepingCentre(40, 40); // Make icon 20x20px
|
||||
|
||||
iconButton->setBounds(iconArea);
|
||||
|
||||
// Get animation progress and calculate Y offset
|
||||
auto animationProgress = isEnabled() ? getAnimationProgress() : 0.0f;
|
||||
auto yOffset = -animationProgress * HOVER_LIFT_AMOUNT;
|
||||
|
||||
iconButton->setTransform(juce::AffineTransform::translation(0, yOffset));
|
||||
}
|
||||
|
||||
void GridItemComponent::mouseDown(const juce::MouseEvent& event)
|
||||
{
|
||||
if (! isEnabled()) return;
|
||||
// Extend base behavior to keep hover press animation
|
||||
HoverAnimationMixin::mouseDown(event);
|
||||
// Ensure any hover preview is cleared before permanently selecting/enabling the item
|
||||
if (onHoverEnd) onHoverEnd();
|
||||
if (onItemSelected) {
|
||||
onItemSelected(itemId);
|
||||
}
|
||||
}
|
||||
|
||||
void GridItemComponent::mouseMove(const juce::MouseEvent& event) {
|
||||
setMouseCursor(isEnabled() ? juce::MouseCursor::PointingHandCursor : juce::MouseCursor::NormalCursor);
|
||||
juce::Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate();
|
||||
}
|
||||
|
||||
void GridItemComponent::mouseEnter(const juce::MouseEvent& event)
|
||||
{
|
||||
HoverAnimationMixin::mouseEnter(event);
|
||||
if (isEnabled() && onHoverStart)
|
||||
onHoverStart(itemId);
|
||||
}
|
||||
|
||||
void GridItemComponent::mouseExit(const juce::MouseEvent& event)
|
||||
{
|
||||
HoverAnimationMixin::mouseExit(event);
|
||||
if (onHoverEnd)
|
||||
onHoverEnd();
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
#include "../LookAndFeel.h"
|
||||
#include "HoverAnimationMixin.h"
|
||||
#include "SvgButton.h"
|
||||
|
||||
// Generic grid item with name, icon, and id
|
||||
class GridItemComponent : public HoverAnimationMixin
|
||||
{
|
||||
public:
|
||||
GridItemComponent(const juce::String& name, const juce::String& icon, const juce::String& id);
|
||||
~GridItemComponent() override;
|
||||
|
||||
void paint(juce::Graphics& g) override;
|
||||
void resized() override;
|
||||
void mouseDown(const juce::MouseEvent& event) override;
|
||||
void mouseMove(const juce::MouseEvent& event) override;
|
||||
void mouseEnter(const juce::MouseEvent& event) override;
|
||||
void mouseExit(const juce::MouseEvent& event) override;
|
||||
|
||||
const juce::String& getId() const { return itemId; }
|
||||
const juce::String& getName() const { return itemName; }
|
||||
|
||||
std::function<void(const juce::String& id)> onItemSelected;
|
||||
std::function<void(const juce::String& id)> onHoverStart;
|
||||
std::function<void()> onHoverEnd;
|
||||
|
||||
private:
|
||||
juce::String itemName;
|
||||
juce::String itemId;
|
||||
|
||||
// Icon for the item
|
||||
std::unique_ptr<SvgButton> iconButton;
|
||||
|
||||
static constexpr int CORNER_RADIUS = 8;
|
||||
static constexpr float HOVER_LIFT_AMOUNT = 2.0f;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GridItemComponent)
|
||||
};
|
|
@ -197,6 +197,13 @@
|
|||
file="Source/components/EffectTypeItemComponent.h"/>
|
||||
<FILE id="aEprcE" name="ErrorCodeEditorComponent.h" compile="0" resource="0"
|
||||
file="Source/components/ErrorCodeEditorComponent.h"/>
|
||||
<FILE id="sqD2Zy" name="GridComponent.cpp" compile="1" resource="0"
|
||||
file="Source/components/GridComponent.cpp"/>
|
||||
<FILE id="QRwdXD" name="GridComponent.h" compile="0" resource="0" file="Source/components/GridComponent.h"/>
|
||||
<FILE id="Dfsnmg" name="GridItemComponent.cpp" compile="1" resource="0"
|
||||
file="Source/components/GridItemComponent.cpp"/>
|
||||
<FILE id="MwZoTt" name="GridItemComponent.h" compile="0" resource="0"
|
||||
file="Source/components/GridItemComponent.h"/>
|
||||
<FILE id="SYA32K" name="HoverAnimationMixin.cpp" compile="1" resource="0"
|
||||
file="Source/components/HoverAnimationMixin.cpp"/>
|
||||
<FILE id="JyPgec" name="HoverAnimationMixin.h" compile="0" resource="0"
|
||||
|
|
Ładowanie…
Reference in New Issue