kopia lustrzana https://github.com/jameshball/osci-render
Add first pass at example files dialog
rodzic
ccfb8391be
commit
517534782d
|
|
@ -12,6 +12,12 @@ MainComponent::MainComponent(OscirenderAudioProcessor& p, OscirenderAudioProcess
|
|||
addAndMakeVisible(fileButton);
|
||||
fileButton.setButtonText("Choose File(s)");
|
||||
|
||||
// Show Examples panel
|
||||
addAndMakeVisible(showExamplesButton);
|
||||
showExamplesButton.onClick = [this] {
|
||||
pluginEditor.settings.showExamples(true);
|
||||
};
|
||||
|
||||
fileButton.onClick = [this] {
|
||||
juce::String fileFormats;
|
||||
for (auto& ext : audioProcessor.FILE_EXTENSIONS) {
|
||||
|
|
@ -198,6 +204,8 @@ void MainComponent::resized() {
|
|||
auto row = bounds.removeFromTop(buttonHeight);
|
||||
fileButton.setBounds(row.removeFromLeft(buttonWidth));
|
||||
row.removeFromLeft(rowPadding);
|
||||
showExamplesButton.setBounds(row.removeFromLeft(buttonWidth));
|
||||
row.removeFromLeft(rowPadding);
|
||||
inputEnabled.setBounds(row.removeFromLeft(20));
|
||||
row.removeFromLeft(rowPadding);
|
||||
if (audioProcessor.getCurrentFileIndex() != -1) {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ private:
|
|||
juce::TextEditor fileName;
|
||||
juce::ComboBox fileType;
|
||||
juce::TextButton createFile{"Create File"};
|
||||
juce::TextButton showExamplesButton{"Examples"};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainComponent)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,6 +11,15 @@ SettingsComponent::SettingsComponent(OscirenderAudioProcessor& p, OscirenderAudi
|
|||
addAndMakeVisible(midi);
|
||||
addChildComponent(txt);
|
||||
addChildComponent(frame);
|
||||
addChildComponent(examples);
|
||||
|
||||
examples.onClosed = [this]() {
|
||||
showExamples(false);
|
||||
};
|
||||
examples.onExampleOpened = [this](const juce::String& fileName, bool shouldOpenEditor) {
|
||||
pluginEditor.addCodeEditor(audioProcessor.getCurrentFileIndex());
|
||||
pluginEditor.fileUpdated(fileName, shouldOpenEditor);
|
||||
};
|
||||
|
||||
double midiLayoutPreferredSize = std::any_cast<double>(audioProcessor.getProperty("midiLayoutPreferredSize", pluginEditor.CLOSED_PREF_SIZE));
|
||||
double mainLayoutPreferredSize = std::any_cast<double>(audioProcessor.getProperty("mainLayoutPreferredSize", -0.5));
|
||||
|
|
@ -63,10 +72,22 @@ void SettingsComponent::resized() {
|
|||
dummyBounds.removeFromBottom(pluginEditor.RESIZER_BAR_SIZE);
|
||||
}
|
||||
|
||||
perspective.setBounds(dummyBounds.removeFromBottom(120));
|
||||
dummyBounds.removeFromBottom(pluginEditor.RESIZER_BAR_SIZE);
|
||||
|
||||
effects.setBounds(dummyBounds);
|
||||
if (examplesVisible) {
|
||||
// Hide other panels while examples are visible
|
||||
perspective.setVisible(false);
|
||||
effects.setVisible(false);
|
||||
txt.setVisible(false);
|
||||
frame.setVisible(false);
|
||||
examples.setVisible(true);
|
||||
examples.setBounds(dummyBounds);
|
||||
} else {
|
||||
examples.setVisible(false);
|
||||
perspective.setVisible(true);
|
||||
effects.setVisible(true);
|
||||
perspective.setBounds(dummyBounds.removeFromBottom(120));
|
||||
dummyBounds.removeFromBottom(pluginEditor.RESIZER_BAR_SIZE);
|
||||
effects.setBounds(dummyBounds);
|
||||
}
|
||||
|
||||
if (isVisible() && getWidth() > 0 && getHeight() > 0) {
|
||||
audioProcessor.setProperty("midiLayoutPreferredSize", midiLayout.getItemCurrentRelativeSize(2));
|
||||
|
|
@ -128,6 +149,11 @@ void SettingsComponent::mouseMove(const juce::MouseEvent& event) {
|
|||
setMouseCursor(juce::MouseCursor::NormalCursor);
|
||||
}
|
||||
|
||||
void SettingsComponent::showExamples(bool shouldShow) {
|
||||
examplesVisible = shouldShow;
|
||||
resized();
|
||||
}
|
||||
|
||||
void SettingsComponent::mouseDown(const juce::MouseEvent& event) {
|
||||
for (int i = 0; i < 1; i++) {
|
||||
if (toggleComponents[i]->getBounds().removeFromTop(pluginEditor.CLOSED_PREF_SIZE).contains(event.getPosition())) {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include "PerspectiveComponent.h"
|
||||
#include "PluginProcessor.h"
|
||||
#include "TxtComponent.h"
|
||||
#include "components/ExampleFilesGridComponent.h"
|
||||
|
||||
class OscirenderAudioProcessorEditor;
|
||||
class SettingsComponent : public juce::Component {
|
||||
|
|
@ -21,6 +22,8 @@ public:
|
|||
void update();
|
||||
void mouseMove(const juce::MouseEvent& event) override;
|
||||
void mouseDown(const juce::MouseEvent& event) override;
|
||||
// Show or hide the example files grid panel on the right-hand side
|
||||
void showExamples(bool shouldShow);
|
||||
|
||||
private:
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
|
|
@ -32,6 +35,9 @@ private:
|
|||
FrameSettingsComponent frame{audioProcessor, pluginEditor};
|
||||
EffectsComponent effects{audioProcessor, pluginEditor};
|
||||
MidiComponent midi{audioProcessor, pluginEditor};
|
||||
ExampleFilesGridComponent examples{audioProcessor};
|
||||
|
||||
bool examplesVisible = false;
|
||||
|
||||
juce::StretchableLayoutManager midiLayout;
|
||||
juce::StretchableLayoutResizerBar midiResizerBar{&midiLayout, 1, false};
|
||||
|
|
|
|||
|
|
@ -1,123 +0,0 @@
|
|||
#include "EffectTypeItemComponent.h"
|
||||
|
||||
EffectTypeItemComponent::EffectTypeItemComponent(const juce::String& name, const juce::String& icon, const juce::String& id)
|
||||
: effectName(name), effectId(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>(
|
||||
"effectIcon",
|
||||
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);
|
||||
}
|
||||
|
||||
EffectTypeItemComponent::~EffectTypeItemComponent() = default;
|
||||
|
||||
void EffectTypeItemComponent::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(effectName, 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 EffectTypeItemComponent::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 EffectTypeItemComponent::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 effect
|
||||
if (onHoverEnd) onHoverEnd();
|
||||
if (onEffectSelected) {
|
||||
onEffectSelected(effectId);
|
||||
}
|
||||
}
|
||||
|
||||
void EffectTypeItemComponent::mouseMove(const juce::MouseEvent& event) {
|
||||
setMouseCursor(isEnabled() ? juce::MouseCursor::PointingHandCursor : juce::MouseCursor::NormalCursor);
|
||||
juce::Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate();
|
||||
}
|
||||
|
||||
void EffectTypeItemComponent::mouseEnter(const juce::MouseEvent& event)
|
||||
{
|
||||
HoverAnimationMixin::mouseEnter(event);
|
||||
if (isEnabled() && onHoverStart)
|
||||
onHoverStart(effectId);
|
||||
}
|
||||
|
||||
void EffectTypeItemComponent::mouseExit(const juce::MouseEvent& event)
|
||||
{
|
||||
HoverAnimationMixin::mouseExit(event);
|
||||
if (onHoverEnd)
|
||||
onHoverEnd();
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
#pragma once
|
||||
#include <JuceHeader.h>
|
||||
#include "../LookAndFeel.h"
|
||||
#include "HoverAnimationMixin.h"
|
||||
#include "SvgButton.h"
|
||||
|
||||
class EffectTypeItemComponent : public HoverAnimationMixin
|
||||
{
|
||||
public:
|
||||
EffectTypeItemComponent(const juce::String& name, const juce::String& icon, const juce::String& id);
|
||||
~EffectTypeItemComponent() 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& getEffectId() const { return effectId; }
|
||||
const juce::String& getEffectName() const { return effectName; }
|
||||
|
||||
std::function<void(const juce::String& effectId)> onEffectSelected;
|
||||
std::function<void(const juce::String& effectId)> onHoverStart;
|
||||
std::function<void()> onHoverEnd;
|
||||
|
||||
private:
|
||||
juce::String effectName;
|
||||
juce::String effectId;
|
||||
|
||||
// Icon for the effect
|
||||
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(EffectTypeItemComponent)
|
||||
};
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
#include "ExampleFilesGridComponent.h"
|
||||
#include "GridItemComponent.h"
|
||||
#include "../JuceLibraryCode/BinaryData.h"
|
||||
|
||||
ExampleFilesGridComponent::ExampleFilesGridComponent(OscirenderAudioProcessor& processor)
|
||||
: audioProcessor(processor)
|
||||
{
|
||||
// Top bar
|
||||
addAndMakeVisible(title);
|
||||
addAndMakeVisible(closeButton);
|
||||
styleHeading(title);
|
||||
closeButton.onClick = [this]() { if (onClosed) onClosed(); };
|
||||
|
||||
// Add categories to component
|
||||
auto addCat = [this](CategoryViews& cat) {
|
||||
styleHeading(cat.heading);
|
||||
addAndMakeVisible(cat.heading);
|
||||
addAndMakeVisible(cat.grid);
|
||||
};
|
||||
addCat(audioCat);
|
||||
addCat(textCat);
|
||||
addCat(imagesCat);
|
||||
addCat(luaCat);
|
||||
addCat(modelsCat);
|
||||
addCat(svgsCat);
|
||||
|
||||
populate();
|
||||
}
|
||||
|
||||
void ExampleFilesGridComponent::styleHeading(juce::Label& l)
|
||||
{
|
||||
l.setInterceptsMouseClicks(false, false);
|
||||
l.setJustificationType(juce::Justification::left);
|
||||
l.setFont(juce::FontOptions(16.0f, juce::Font::bold));
|
||||
}
|
||||
|
||||
void ExampleFilesGridComponent::paint(juce::Graphics& g)
|
||||
{
|
||||
// transparent background
|
||||
}
|
||||
|
||||
void ExampleFilesGridComponent::resized()
|
||||
{
|
||||
auto bounds = getLocalBounds();
|
||||
auto top = bounds.removeFromTop(30);
|
||||
title.setBounds(top.removeFromLeft(200).reduced(4));
|
||||
closeButton.setBounds(top.removeFromRight(80).reduced(4));
|
||||
|
||||
auto layCat = [&](CategoryViews& cat) {
|
||||
auto header = bounds.removeFromTop(24);
|
||||
cat.heading.setBounds(header.reduced(2));
|
||||
auto h = cat.grid.calculateRequiredHeight(bounds.getWidth());
|
||||
cat.grid.setBounds(bounds.removeFromTop(h));
|
||||
bounds.removeFromTop(8); // gap
|
||||
};
|
||||
|
||||
layCat(audioCat);
|
||||
layCat(textCat);
|
||||
layCat(imagesCat);
|
||||
layCat(luaCat);
|
||||
layCat(modelsCat);
|
||||
layCat(svgsCat);
|
||||
}
|
||||
|
||||
void ExampleFilesGridComponent::addExample(CategoryViews& cat, const juce::String& fileName, const char* data, int size)
|
||||
{
|
||||
// Use placeholder icon for now
|
||||
auto* item = new GridItemComponent(fileName, juce::String::createStringFromData(BinaryData::random_svg, BinaryData::random_svgSize), fileName);
|
||||
item->onItemSelected = [this, fileName, data, size](const juce::String&) {
|
||||
juce::SpinLock::ScopedLockType parsersLock(audioProcessor.parsersLock);
|
||||
audioProcessor.addFile(fileName, data, size);
|
||||
// Signal to UI layer that a new example was added so it can open editors, etc.
|
||||
const bool openEditor = fileName.endsWithIgnoreCase(".lua") || fileName.endsWithIgnoreCase(".txt");
|
||||
if (onExampleOpened) onExampleOpened(fileName, openEditor);
|
||||
};
|
||||
cat.grid.addItem(item);
|
||||
}
|
||||
|
||||
void ExampleFilesGridComponent::populate()
|
||||
{
|
||||
// Audio examples
|
||||
addExample(audioCat, "sosci.flac", BinaryData::sosci_flac, BinaryData::sosci_flacSize);
|
||||
|
||||
// Text examples
|
||||
addExample(textCat, "helloworld.txt", BinaryData::helloworld_txt, BinaryData::helloworld_txtSize);
|
||||
addExample(textCat, "greek.txt", BinaryData::greek_txt, BinaryData::greek_txtSize);
|
||||
|
||||
// Image examples (will open as images via frame settings path)
|
||||
addExample(imagesCat, "empty.jpg", BinaryData::empty_jpg, BinaryData::empty_jpgSize);
|
||||
addExample(imagesCat, "no_reflection.jpg", BinaryData::no_reflection_jpg, BinaryData::no_reflection_jpgSize);
|
||||
addExample(imagesCat, "noise.jpg", BinaryData::noise_jpg, BinaryData::noise_jpgSize);
|
||||
addExample(imagesCat, "real.png", BinaryData::real_png, BinaryData::real_pngSize);
|
||||
addExample(imagesCat, "real_reflection.png", BinaryData::real_reflection_png, BinaryData::real_reflection_pngSize);
|
||||
addExample(imagesCat, "vector_display.png", BinaryData::vector_display_png, BinaryData::vector_display_pngSize);
|
||||
addExample(imagesCat, "vector_display_reflection.png", BinaryData::vector_display_reflection_png, BinaryData::vector_display_reflection_pngSize);
|
||||
|
||||
// Lua examples
|
||||
addExample(luaCat, "demo.lua", BinaryData::demo_lua, BinaryData::demo_luaSize);
|
||||
|
||||
// 3D model examples
|
||||
addExample(modelsCat, "cube.obj", BinaryData::cube_obj, BinaryData::cube_objSize);
|
||||
|
||||
// SVG examples (just a subset for brevity, can add more)
|
||||
addExample(svgsCat, "demo.svg", BinaryData::demo_svg, BinaryData::demo_svgSize);
|
||||
addExample(svgsCat, "lua.svg", BinaryData::lua_svg, BinaryData::lua_svgSize);
|
||||
addExample(svgsCat, "trace.svg", BinaryData::trace_svg, BinaryData::trace_svgSize);
|
||||
addExample(svgsCat, "wobble.svg", BinaryData::wobble_svg, BinaryData::wobble_svgSize);
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
#include "../PluginProcessor.h"
|
||||
#include "GridComponent.h"
|
||||
|
||||
// A grid-based browser for example files grouped by category
|
||||
class ExampleFilesGridComponent : public juce::Component
|
||||
{
|
||||
public:
|
||||
ExampleFilesGridComponent(OscirenderAudioProcessor& processor);
|
||||
~ExampleFilesGridComponent() override = default;
|
||||
|
||||
void paint(juce::Graphics& g) override;
|
||||
void resized() override;
|
||||
|
||||
// Called when the user closes the view
|
||||
std::function<void()> onClosed;
|
||||
// Called after the file has been added to the processor; consumer may open editors, etc.
|
||||
std::function<void(const juce::String& fileName, bool shouldOpenEditor)> onExampleOpened;
|
||||
|
||||
private:
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
|
||||
// Top bar
|
||||
juce::Label title { {}, "Examples" };
|
||||
juce::TextButton closeButton { "Close" };
|
||||
|
||||
// Categories
|
||||
struct CategoryViews {
|
||||
juce::Label heading;
|
||||
GridComponent grid;
|
||||
};
|
||||
|
||||
CategoryViews audioCat { juce::Label({}, "Audio"), GridComponent{} };
|
||||
CategoryViews textCat { juce::Label({}, "Text"), GridComponent{} };
|
||||
CategoryViews imagesCat { juce::Label({}, "Images"), GridComponent{} };
|
||||
CategoryViews luaCat { juce::Label({}, "Lua"), GridComponent{} };
|
||||
CategoryViews modelsCat { juce::Label({}, "3D models"), GridComponent{} };
|
||||
CategoryViews svgsCat { juce::Label({}, "SVGs"), GridComponent{} };
|
||||
|
||||
// Helpers
|
||||
void addExample(CategoryViews& cat, const juce::String& fileName, const char* data, int size);
|
||||
void populate();
|
||||
void styleHeading(juce::Label& l);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ExampleFilesGridComponent)
|
||||
};
|
||||
|
|
@ -9,6 +9,9 @@
|
|||
pluginAUMainType="'aumf'">
|
||||
<MAINGROUP id="j5Ge2T" name="osci-render">
|
||||
<GROUP id="{5ABCED88-0059-A7AF-9596-DBF91DDB0292}" name="Resources">
|
||||
<GROUP id="{17F5509B-A3D9-D161-3F5C-4DC2E1C335E7}" name="audio">
|
||||
<FILE id="BEhvxK" name="sosci.flac" compile="0" resource="1" file="Resources/audio/sosci.flac"/>
|
||||
</GROUP>
|
||||
<GROUP id="{8930EC48-30FD-646B-9DC5-0861171F8B2E}" name="fonts">
|
||||
<FILE id="GXGPCT" name="FiraSans-Bold.ttf" compile="0" resource="1"
|
||||
file="Resources/fonts/FiraSans-Bold.ttf"/>
|
||||
|
|
@ -191,12 +194,12 @@
|
|||
file="Source/components/EffectTypeGridComponent.cpp"/>
|
||||
<FILE id="Fb2Qci" name="EffectTypeGridComponent.h" compile="0" resource="0"
|
||||
file="Source/components/EffectTypeGridComponent.h"/>
|
||||
<FILE id="PPK1iQ" name="EffectTypeItemComponent.cpp" compile="1" resource="0"
|
||||
file="Source/components/EffectTypeItemComponent.cpp"/>
|
||||
<FILE id="kGAfsx" name="EffectTypeItemComponent.h" compile="0" resource="0"
|
||||
file="Source/components/EffectTypeItemComponent.h"/>
|
||||
<FILE id="aEprcE" name="ErrorCodeEditorComponent.h" compile="0" resource="0"
|
||||
file="Source/components/ErrorCodeEditorComponent.h"/>
|
||||
<FILE id="EB1SYX" name="ExampleFilesGridComponent.cpp" compile="1"
|
||||
resource="0" file="Source/components/ExampleFilesGridComponent.cpp"/>
|
||||
<FILE id="K9EITe" name="ExampleFilesGridComponent.h" compile="0" resource="0"
|
||||
file="Source/components/ExampleFilesGridComponent.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"/>
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue