Skip to content

Commit

Permalink
plugin: multi-file support
Browse files Browse the repository at this point in the history
  • Loading branch information
JoepVanlier committed Dec 23, 2024
1 parent 4c2922b commit 31cbdbc
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 73 deletions.
179 changes: 108 additions & 71 deletions plugin/components/ide_view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ struct YsfxIDEView::Impl {
YsfxIDEView *m_self = nullptr;
ysfx_u m_fx;

std::vector<std::shared_ptr<YSFXCodeDocument>> m_documents;
std::vector<std::shared_ptr<YSFXCodeEditor>> m_editors;
std::unique_ptr<JSFXTokenizer> m_tokenizer;
std::unique_ptr<YSFXCodeEditor> m_editor;
// std::unique_ptr<YSFXCodeEditor> m_editor;
std::unique_ptr<juce::TextButton> m_btnSave;
std::unique_ptr<juce::TextButton> m_btnUpdate;
std::unique_ptr<juce::Label> m_lblVariablesHeading;
Expand All @@ -40,6 +40,9 @@ struct YsfxIDEView::Impl {
std::unique_ptr<juce::Timer> m_relayoutTimer;
std::unique_ptr<juce::Timer> m_fileCheckTimer;
std::unique_ptr<juce::FileChooser> m_fileChooser;

std::unique_ptr<YSFXTabbedButtonBar> m_tabs;

bool m_fileChooserActive{false};

struct VariableUI {
Expand All @@ -52,12 +55,16 @@ struct YsfxIDEView::Impl {
std::unique_ptr<juce::Timer> m_varsUpdateTimer;

bool m_forceUpdate{false};
int m_currentDocumentIndex{0};
int m_currentEditorIndex{0};

//==========================================================================
void setupNewFx();
void saveCurrentFile();
void saveAs();
std::shared_ptr<YSFXCodeEditor> addEditor();
void openDocument(juce::File file);
void setCurrentEditor(int idx);
std::shared_ptr<YSFXCodeEditor> getCurrentEditor();
void search(juce::String text, bool reverse);

//==========================================================================
Expand All @@ -71,7 +78,6 @@ YsfxIDEView::YsfxIDEView()
: m_impl(new Impl)
{
m_impl->m_self = this;
m_impl->m_documents.push_back(std::shared_ptr<YSFXCodeDocument>(new YSFXCodeDocument()));
m_impl->m_tokenizer.reset(new JSFXTokenizer());

m_impl->createUI();
Expand All @@ -89,7 +95,7 @@ YsfxIDEView::~YsfxIDEView()
void YsfxIDEView::setColourScheme(std::map<std::string, std::array<uint8_t, 3>> colormap)
{
m_impl->m_tokenizer->setColours(colormap);
m_impl->m_editor->setColourScheme(m_impl->m_tokenizer->getDefaultColourScheme());
m_impl->m_editors[0]->setColourScheme(m_impl->m_tokenizer->getDefaultColourScheme());
}

void YsfxIDEView::setEffect(ysfx_t *fx, juce::Time timeStamp)
Expand Down Expand Up @@ -122,15 +128,22 @@ void YsfxIDEView::focusOnCodeEditor()
m_impl->m_forceUpdate = true;
}

std::shared_ptr<YSFXCodeEditor> YsfxIDEView::Impl::getCurrentEditor()
{
if (m_currentEditorIndex >= m_editors.size()) {
setCurrentEditor(0);
}

return m_editors[m_currentEditorIndex];
}

void YsfxIDEView::focusOfChildComponentChanged(FocusChangeType cause)
{
(void)cause;

juce::Component *focus = getCurrentlyFocusedComponent();

if (focus == m_impl->m_editor.get()) {
if (m_impl->getCurrentEditor()->hasFocus()) {
juce::Timer *timer = FunctionalTimer::create([this]() {
for (auto& document : m_impl->m_documents) document->checkFileForModifications();
m_impl->getCurrentEditor()->checkFileForModifications();
});
m_impl->m_fileCheckTimer.reset(timer);
timer->startTimer(100);
Expand All @@ -149,12 +162,12 @@ void YsfxIDEView::Impl::setupNewFx()

if (!fx) {
//
m_documents[0]->reset();
m_editor->setReadOnly(true);
getCurrentEditor()->reset();
getCurrentEditor()->setReadOnly(true);
}
else {
juce::File file{juce::CharPointer_UTF8{ysfx_get_file_path(fx)}};
m_documents[0]->loadFile(file);
m_editors[0]->loadFile(file);

m_vars.ensureStorageAllocated(64);

Expand Down Expand Up @@ -193,7 +206,7 @@ void YsfxIDEView::Impl::setupNewFx()
m_varsUpdateTimer->startTimer(100);
}

m_editor->setReadOnly(false);
m_editors[0]->setReadOnly(false);

relayoutUILater();
}
Expand All @@ -202,15 +215,15 @@ void YsfxIDEView::Impl::setupNewFx()
void YsfxIDEView::Impl::saveAs()
{
if (m_fileChooserActive) return;
if (m_currentDocumentIndex >= m_documents.size()) return;
if (m_currentEditorIndex >= m_editors.size()) return;

auto document = m_documents[m_currentDocumentIndex];
juce::File initialPath = document->getPath().getParentDirectory();
auto editor = m_editors[m_currentEditorIndex];
juce::File initialPath = editor->getPath().getParentDirectory();

m_fileChooser.reset(new juce::FileChooser(TRANS("Choose filename to save JSFX to"), initialPath));
m_fileChooser->launchAsync(
juce::FileBrowserComponent::saveMode|juce::FileBrowserComponent::canSelectFiles,
[this, document](const juce::FileChooser &chooser) {
[this, editor](const juce::FileChooser &chooser) {
juce::File chosenFile = chooser.getResult();
if (chosenFile != juce::File()) {
if (chosenFile.exists()) {
Expand All @@ -222,16 +235,16 @@ void YsfxIDEView::Impl::saveAs()
.withButton(TRANS("Yes"))
.withButton(TRANS("No"))
.withMessage(TRANS("File already exists! Overwrite?")),
[this, chosenFile, document](int result) {
[this, chosenFile, editor](int result) {
if (result == 1) {
document->saveFile(chosenFile);
if (m_self->onFileSaved) m_self->onFileSaved(m_documents[0]->getPath());
editor->saveFile(chosenFile);
if (m_self->onFileSaved) m_self->onFileSaved(m_editors[0]->getPath());
};
}
);
} else {
document->saveFile(chosenFile);
if (m_self->onFileSaved) m_self->onFileSaved(m_documents[0]->getPath());
m_editors[0]->saveFile(chosenFile);
if (m_self->onFileSaved) m_self->onFileSaved(m_editors[0]->getPath());
}
}
m_fileChooserActive = false;
Expand All @@ -245,66 +258,48 @@ void YsfxIDEView::Impl::saveCurrentFile()
if (!fx)
return;

if (m_currentDocumentIndex >= m_documents.size()) return;
if (m_currentEditorIndex >= m_editors.size()) return;

if (m_documents[m_currentDocumentIndex]->getPath().existsAsFile()) {
m_documents[m_currentDocumentIndex]->saveFile();
if (getCurrentEditor()->getPath().existsAsFile()) {
getCurrentEditor()->saveFile();
} else {
saveAs();
}
m_btnSave->setEnabled(false);

if (m_self->onFileSaved)
m_self->onFileSaved(m_documents[0]->getPath());
m_self->onFileSaved(m_editors[0]->getPath());
}

void YsfxIDEView::Impl::search(juce::String text, bool reverse=false)
void YsfxIDEView::Impl::openDocument(juce::File file)
{
if (text.isNotEmpty())
{
if (m_currentDocumentIndex >= m_documents.size()) return;
std::shared_ptr document = m_documents[m_currentDocumentIndex];
auto currentPosition = juce::CodeDocument::Position(*document, m_editor->getCaretPosition());

auto chunk = [this, currentPosition, document](bool before) {
if (before) {
return document->getTextBetween(juce::CodeDocument::Position(*document, 0), currentPosition.movedBy(-1));
} else {
return document->getTextBetween(currentPosition, juce::CodeDocument::Position(*document, document->getNumCharacters()));
}
int idx = 0;
auto fn = file.getFileName();
for (const auto& editor : m_editors) {
if (fn.compareIgnoreCase(editor->getName()) == 0) {
setCurrentEditor(idx);
return;
};
idx += 1;
}

int position = reverse ? chunk(true).lastIndexOfIgnoreCase(text) : chunk(false).indexOfIgnoreCase(text);
juce::CodeDocument::Position searchPosition;
if (position == -1) {
// We didn't find it! Start from the other end!
position = reverse ? chunk(false).lastIndexOfIgnoreCase(text) : chunk(true).indexOfIgnoreCase(text);
auto editor = addEditor();
editor->loadFile(file);
setCurrentEditor(m_editors.size() - 1);
}

if (position == -1) {
// Not found at all -> stop
if (text.compare(document->getTextBetween(currentPosition.movedBy(- text.length()), currentPosition)) != 0) {
m_lblStatus->setText(TRANS("Didn't find search string ") + text, juce::NotificationType::dontSendNotification);
} else {
m_lblStatus->setText(TRANS("Didn't find other copies of search string ") + text, juce::NotificationType::dontSendNotification);
}
m_editor->grabKeyboardFocus();
return;
}
searchPosition = juce::CodeDocument::Position(*document, reverse ? currentPosition.getPosition() + position : position);
} else {
// Found it!
searchPosition = juce::CodeDocument::Position(*document, reverse ? position : currentPosition.getPosition() + position);
}
void YsfxIDEView::Impl::setCurrentEditor(int editorIndex)
{
if (editorIndex >= m_editors.size()) return;

auto pos = juce::CodeDocument::Position(*document, searchPosition.getPosition());
m_editor->grabKeyboardFocus();
m_editor->moveCaretTo(pos, false);
m_editor->moveCaretTo(pos.movedBy(text.length()), true);
m_lblStatus->setText(TRANS("Found ") + text + TRANS(". (SHIFT +) CTRL/CMD + G to repeat search (backwards)."), juce::NotificationType::dontSendNotification);
}
m_editors[m_currentEditorIndex]->setVisible(false);
m_currentEditorIndex = editorIndex;
m_editors[m_currentEditorIndex]->setVisible(true);

relayoutUILater();
}

void YsfxIDEView::Impl::createUI()
std::shared_ptr<YSFXCodeEditor> YsfxIDEView::Impl::addEditor()
{
auto keyPressCallback = [this](const juce::KeyPress& key) -> bool {
if (key.getModifiers().isCommandDown()) {
Expand All @@ -317,7 +312,7 @@ void YsfxIDEView::Impl::createUI()
m_searchEditor->grabKeyboardFocus();
m_searchEditor->setEscapeAndReturnKeysConsumed(true);
m_searchEditor->onReturnKey = [this]() {
search(m_searchEditor->getText());
getCurrentEditor()->search(m_searchEditor->getText());
m_searchEditor->setWantsKeyboardFocus(false);
m_searchEditor->setVisible(false);
m_lblStatus->setVisible(true);
Expand All @@ -338,16 +333,42 @@ void YsfxIDEView::Impl::createUI()

if (key.isKeyCurrentlyDown('g')) {
m_lblStatus->setText("", juce::NotificationType::dontSendNotification);
search(m_searchEditor->getText(), key.getModifiers().isShiftDown());
getCurrentEditor()->search(m_searchEditor->getText(), key.getModifiers().isShiftDown());
return true;
}
}

return false;
};

m_editor.reset(new YSFXCodeEditor(*(m_documents[0]), m_tokenizer.get(), keyPressCallback));
m_self->addAndMakeVisible(*m_editor);
auto dblClickCallback = [this](int x, int y) -> bool {
if (m_currentEditorIndex >= m_editors.size()) return false;

auto line = getCurrentEditor()->getLineAt(x, y);
if (line.startsWithIgnoreCase("import ")) {
// Import statement!
auto potentialFilename = line.substring(7).trimEnd();
if (this->m_fx) {
char *pathString = ysfx_resolve_path_and_allocate(this->m_fx.get(), potentialFilename.toStdString().c_str(), getCurrentEditor()->getPath().getFullPathName().toStdString().c_str());
if (pathString) {
auto path = juce::File(juce::CharPointer_UTF8(pathString));
openDocument(path);
ysfx_free_resolved_path(pathString);
return true;
};
}
}
return false;
};

m_editors.push_back(std::make_shared<YSFXCodeEditor>(m_tokenizer.get(), keyPressCallback, dblClickCallback));
m_self->addAndMakeVisible(m_editors.back()->getVisibleComponent());
return m_editors.back();
}

void YsfxIDEView::Impl::createUI()
{
addEditor();
m_btnSave.reset(new juce::TextButton(TRANS("Save")));
m_btnSave->addShortcut(juce::KeyPress('s', juce::ModifierKeys::ctrlModifier, 0));
m_self->addAndMakeVisible(*m_btnSave);
Expand All @@ -369,6 +390,8 @@ void YsfxIDEView::Impl::createUI()
m_self->addAndMakeVisible(*m_searchEditor);
m_self->addAndMakeVisible(*m_lblStatus);
m_searchEditor->setVisible(false);
m_tabs.reset(new YSFXTabbedButtonBar(juce::TabbedButtonBar::Orientation::TabsAtBottom, [this](int newCurrentTabIndex) { setCurrentEditor(newCurrentTabIndex); }));
m_self->addAndMakeVisible(*m_tabs);
}

void YsfxIDEView::Impl::connectUI()
Expand All @@ -385,6 +408,20 @@ void YsfxIDEView::Impl::relayoutUI()
temp = bounds;
const juce::Rectangle<int> debugArea = temp.removeFromRight(300);
const juce::Rectangle<int> topRow = temp.removeFromTop(50);

if (m_editors.size() > 0) {
const juce::Rectangle<int> tabRow = temp.removeFromTop(30);
m_tabs->setBounds(tabRow);
auto updateBlock = ScopedUpdateBlocker(*m_tabs);
m_tabs->clearTabs();
int idx = 0;
for (const auto m : m_editors) {
m_tabs->addTab(m->getName(), m_self->getLookAndFeel().findColour(m_btnSave->buttonColourId), idx);
++idx;
}
m_tabs->setCurrentTabIndex(m_currentEditorIndex, false);
}

const juce::Rectangle<int> statusArea = temp.removeFromBottom(20);
const juce::Rectangle<int> editArea = temp;

Expand Down Expand Up @@ -415,7 +452,7 @@ void YsfxIDEView::Impl::relayoutUI()
m_lblStatus->setBounds(statusArea);
m_searchEditor->setBounds(statusArea);

m_editor->setBounds(editArea);
getCurrentEditor()->setBounds(editArea);

if (m_relayoutTimer)
m_relayoutTimer->stopTimer();
Expand Down
6 changes: 6 additions & 0 deletions plugin/components/ysfx_document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ juce::File YSFXCodeDocument::getPath(void) {
return m_file;
}

juce::String YSFXCodeDocument::getName(void) {
if (!m_file.existsAsFile()) return juce::String("Untitled");

return m_file.getFileName();
}

void YSFXCodeDocument::checkFileForModifications()
{
if (m_file == juce::File{})
Expand Down
Loading

0 comments on commit 31cbdbc

Please sign in to comment.