diff --git a/src/surge-xt/gui/overlays/LuaEditors.cpp b/src/surge-xt/gui/overlays/LuaEditors.cpp index 2b6365ad770..b2058ff1ec4 100644 --- a/src/surge-xt/gui/overlays/LuaEditors.cpp +++ b/src/surge-xt/gui/overlays/LuaEditors.cpp @@ -42,48 +42,178 @@ namespace Overlays { /* - --------------------------------------- - Search box - --------------------------------------- + TextfieldPopup + Basic class that can be used for creating other textfield popups like the search */ +TextfieldButton::TextfieldButton(juce::String &svg) : juce::Component() +{ -CodeEditorSearch::CodeEditorSearch(juce::CodeEditorComponent &editor, Surge::GUI::Skin::ptr_t skin) + xml = juce::XmlDocument::parse(svg); + // std::cout << "xml to string " << xml->toString() << "\n"; + + svgGraphics = juce::Drawable::createFromSVG(*xml); + + // std::cout << "svg created : " << (svgGraphics == nullptr); + setBounds(juce::Rectangle(0, 0, TextfieldPopup::STYLE_BUTTON_SIZE, + TextfieldPopup::STYLE_BUTTON_SIZE)); + + // svgGraphics->setBounds(juce::Rectangle(0, 0, 50, 50)); + + addAndMakeVisible(*svgGraphics); + const juce::Rectangle bounds = + juce::Rectangle(0.f, 0.f, (float)TextfieldPopup::STYLE_BUTTON_SIZE, + (float)TextfieldPopup::STYLE_BUTTON_SIZE); + + svgGraphics->setTransformToFit(bounds, juce::RectanglePlacement::yTop); + // setSelectable(); +} + +void TextfieldButton::paint(juce::Graphics &g) +{ + + Component::paint(g); + auto bounds = getBounds(); + + float alpha = isMouseOver && enabled ? 0.2 : 0; + + g.setFillType(juce::FillType(juce::Colour(255, 255, 255).withAlpha(alpha))); + + g.fillRoundedRectangle(0, 0, TextfieldPopup::STYLE_BUTTON_SIZE, + TextfieldPopup::STYLE_BUTTON_SIZE, 2); +} + +void TextfieldButton::setEnabled(bool v) +{ + enabled = v; + updateGraphicState(); + repaint(); +} + +void TextfieldButton::mouseEnter(const juce::MouseEvent &event) +{ + isMouseOver = true; + repaint(); +} + +void TextfieldButton::mouseExit(const juce::MouseEvent &event) +{ + isMouseOver = false; + repaint(); +} + +void TextfieldButton::mouseDown(const juce::MouseEvent &event) { isMouseDown = true; } + +void TextfieldButton::mouseUp(const juce::MouseEvent &event) +{ + + if (isSelectable) + { + select(isSelected() == false ? true : false); + } + + if (isMouseDown) + { + onClick(); + } + + isMouseDown = false; +} + +void TextfieldButton::updateGraphicState() +{ + svgGraphics->setAlpha(enabled ? (isSelectable && selected) || (!isSelectable) ? 1 : 0.5 : 0.15); +} + +void TextfieldButton::setSelectable() +{ + isSelectable = true; + updateGraphicState(); + repaint(); +} + +void TextfieldButton::select(bool v) +{ + selected = v; + updateGraphicState(); + repaint(); +} + +Textfield::Textfield() : juce::TextEditor() {} +void Textfield::paint(juce::Graphics &g) +{ + + juce::TextEditor::paint(g); + + if (isEmpty()) + { + // draw prefix text + g.setColour(colour.withAlpha(0.85f)); + + g.setFont(getFont()); + auto bounds = getBounds(); + int border = getBorder().getLeft(); + auto textBounds = + juce::Rectangle(getLeftIndent() + border, 0, bounds.getWidth(), bounds.getHeight()); + + g.drawText(header, textBounds, juce::Justification::verticallyCentred, true); + } +} + +void Textfield::setHeader(juce::String h) { header = h; } + +void Textfield::setColour(int colourID, juce::Colour newColour) +{ + Component::setColour(colourID, newColour); + colour = newColour; +} + +TextfieldPopup::TextfieldPopup(juce::CodeEditorComponent &editor, Surge::GUI::Skin::ptr_t skin) : juce::Component(), juce::TextEditor::Listener(), juce::KeyListener(), Surge::GUI::SkinConsumingComponent() { + ed = &editor; currentSkin = skin; juce::Rectangle boundsLabel = juce::Rectangle(95, 2, 80, 20); - textfield = std::make_unique(); + textfield = std::make_unique(); labelResult = std::make_unique(); labelResult->setBounds(boundsLabel); labelResult->setFont(juce::FontOptions(10)); - labelResult->setJustificationType(juce::Justification::right); - labelResult->setColour(juce::Label::textColourId, skin->getColor(Colors::Dialog::Button::Text)); + labelResult->setJustificationType(juce::Justification::left); + // labelResult->setColour(juce::Label::textColourId, + // skin->getColor(Colors::Dialog::Button::Text)); + labelResult->setColour(juce::Label::textColourId, juce::Colour(255, 255, 255)); addAndMakeVisible(*labelResult); - textfield->setBorder(juce::BorderSize(2, 5, 2, 5)); + textfield->setBorder(juce::BorderSize(0, 5, 0, 5)); textfield->setFont(juce::FontOptions(12)); textfield->setColour(juce::TextEditor::ColourIds::textColourId, skin->getColor(Colors::Dialog::Button::Text)); textfield->setColour(juce::TextEditor::backgroundColourId, - skin->getColor(Colors::Dialog::Button::Background)); + skin->getColor(Colors::FormulaEditor::Background)); + + textfield->setColour(juce::TextEditor::focusedOutlineColourId, + skin->getColor(Colors::FormulaEditor::Background).brighter(0.05)); + textfield->setColour(juce::TextEditor::outlineColourId, + skin->getColor(Colors::FormulaEditor::Background).brighter(0)); addAndMakeVisible(*textfield); - textfield->setTitle(""); + textfield->setHeader("Search..."); textfield->setText(""); - + // textfield->setAlpha(0.5); textfield->setEscapeAndReturnKeysConsumed(true); textfield->addListener(this); textfield->addKeyListener(this); + setPaintingIsUnclipped(true); + juce::Rectangle bounds = juce::Rectangle(0, 0, 150, 20); setBounds(bounds); @@ -91,7 +221,135 @@ CodeEditorSearch::CodeEditorSearch(juce::CodeEditorComponent &editor, Surge::GUI resize(); } -void CodeEditorSearch::paint(juce::Graphics &g) {} +void TextfieldPopup::paint(juce::Graphics &g) +{ + + auto bounds = getBounds(); + auto rect = juce::Rectangle(0, 0, bounds.getWidth(), bounds.getHeight()); + + auto c = skin->getColor(Colors::FormulaEditor::Background).darker(1.3); + auto col = juce::Colour(c.getRed(), c.getGreen(), c.getBlue()); + g.setFillType(juce::FillType(col)); + g.fillRect(rect); + juce::Component::paint(g); +} +void TextfieldPopup::onClick(std::unique_ptr &btn) +{ + std::cout << "click button\n"; +} + +void TextfieldPopup::createButton(juce::String svg) +{ + + button[buttonCount] = std::make_unique(svg); + auto btn = &button[buttonCount]; + + auto callback = [this, btn]() { onClick(*btn); }; + button[buttonCount]->onClick = callback; + addAndMakeVisible(*button[buttonCount]); + + buttonCount++; +} + +void TextfieldPopup::setTextWidth(int w) { textWidth = w; } + +void TextfieldPopup::setHeader(juce::String t) { textfield->setHeader(t); } + +void TextfieldPopup::resize() +{ + + int totalWidth = STYLE_MARGIN + textWidth + STYLE_MARGIN + + STYLE_MARGIN_BETWEEN_TEXT_AND_BUTTONS + STYLE_MARGIN + + (STYLE_BUTTON_SIZE + STYLE_BUTTON_MARGIN) * buttonCount + STYLE_MARGIN; + int totalHeight = STYLE_TEXT_HEIGHT + STYLE_MARGIN * 2; + + juce::Rectangle bounds = + juce::Rectangle(ed->getWidth() - totalWidth + 2, 2, totalWidth, totalHeight); + + setBounds(bounds); + + auto boundsLabel = labelResult->getBounds(); + labelResult->setBounds(STYLE_MARGIN + textWidth + STYLE_MARGIN, + totalHeight * 0.5 - boundsLabel.getHeight() * 0.5, + boundsLabel.getWidth(), boundsLabel.getHeight()); + + textfield->setBounds(STYLE_MARGIN, + (STYLE_TEXT_HEIGHT + STYLE_MARGIN * 2) / 2 - STYLE_TEXT_HEIGHT * 0.5, + textWidth, STYLE_TEXT_HEIGHT); + + int buttonY = totalHeight / 2 - STYLE_BUTTON_SIZE * 0.5; + + for (int i = 0; i < buttonCount; i++) + { + auto bounds = juce::Rectangle(STYLE_MARGIN + textWidth + STYLE_MARGIN + + STYLE_MARGIN_BETWEEN_TEXT_AND_BUTTONS + STYLE_MARGIN + + (STYLE_BUTTON_SIZE + STYLE_BUTTON_MARGIN) * i, + buttonY, STYLE_BUTTON_SIZE, STYLE_BUTTON_SIZE); + button[i]->setBounds(bounds); + } +} + +/* +--------------------------------------- +Search box +--------------------------------------- +*/ + +CodeEditorSearch::CodeEditorSearch(juce::CodeEditorComponent &editor, Surge::GUI::Skin::ptr_t skin) + : TextfieldPopup(editor, skin) +{ + + // Case sensitivity + createButton( + {R"()"}); + button[0]->setSelectable(); + + // whole word match + createButton( + {R"()"}); + button[1]->setSelectable(); + + // arrow up + createButton( + {R"()"}); + + // arrow down + createButton({R"( +)"}); + + button[1]->setEnabled(false); + setHeader("Search.."); + repaint(); +} + +void CodeEditorSearch::onClick(std::unique_ptr &btn) +{ + // case sensitive + if (btn == button[0]) + { + search(); + } + // whole word + if (btn == button[1]) + { + search(); + } + + if (btn == button[2]) + { + showResult(1, true); + } + + if (btn == button[3]) + { + showResult(-1, true); + } +} + +int *CodeEditorSearch::getResult() { return result; } +int CodeEditorSearch::getResultTotal() { return resultTotal; } + +// void CodeEditorSearch::paint(juce::Graphics &g) {} bool CodeEditorSearch::isActive() { return active; } @@ -141,8 +399,9 @@ void CodeEditorSearch::setHighlightColors() { auto color = skin->getColor(Colors::FormulaEditor::Background); - ed->setColour(juce::CodeEditorComponent::highlightColourId, - color.interpolatedWith(juce::Colour(108, 147, 25), 0.5)); + ed->setColour( + juce::CodeEditorComponent::highlightColourId, + color.interpolatedWith(juce::Colour(COLOR_MATCH[0], COLOR_MATCH[1], COLOR_MATCH[3]), 0.6)); } void CodeEditorSearch::removeHighlightColors() @@ -173,6 +432,13 @@ void CodeEditorSearch::show() ed->repaint(); // force update selection color } +void CodeEditorSearch::hide() +{ + removeHighlightColors(); + setVisible(false); + ed->repaint(); +} + void CodeEditorSearch::textEditorEscapeKeyPressed(juce::TextEditor &) { hide(); } void CodeEditorSearch::textEditorReturnKeyPressed(juce::TextEditor &) {} @@ -206,39 +472,30 @@ bool CodeEditorSearch::keyPressed(const juce::KeyPress &key, juce::Component *or return true; } -void CodeEditorSearch::hide() -{ - removeHighlightColors(); - setVisible(false); -} - void CodeEditorSearch::textEditorTextChanged(juce::TextEditor &textEditor) { search(); } void CodeEditorSearch::showResult(int increment, bool moveCaret) { int id = resultCurrent + 1; + + button[2]->setEnabled(resultTotal < 2 ? false : true); + button[3]->setEnabled(resultTotal < 2 ? false : true); + if (resultTotal == 0) { removeHighlightColors(); id = 0; - textfield->setColour(juce::TextEditor::focusedOutlineColourId, juce::Colour(204, 70, 70)); - textfield->setColour(juce::TextEditor::outlineColourId, - skin->getColor(Colors::FormulaEditor::Debugger::Text)); } else { setHighlightColors(); - textfield->setColour(juce::TextEditor::focusedOutlineColourId, - skin->getColor(Colors::FormulaEditor::Debugger::Text)); - textfield->setColour(juce::TextEditor::outlineColourId, - skin->getColor(Colors::FormulaEditor::Debugger::Text)); } labelResult->setText(juce::String(std::to_string(id) + '/' + std::to_string(resultTotal)), juce::NotificationType::dontSendNotification); repaint(); - + ed->repaint(); if (resultTotal == 0) return; @@ -248,14 +505,17 @@ void CodeEditorSearch::showResult(int increment, bool moveCaret) ed->setHighlightedRegion( juce::Range(result[resultCurrent], result[resultCurrent] + textfield->getTotalNumChars())); saveCaretStartPositionLock = false; + + // std::cout << "show result " << resultTotal << "\n"; } +/* void CodeEditorSearch::resize() { juce::Rectangle bounds = juce::Rectangle(ed->getBounds().getWidth() - 150 - 10, 6, 150, 24); setBounds(bounds); textfield->setBounds(0, 0, 150, 24); -} +}*/ void CodeEditorSearch::search() { @@ -271,12 +531,16 @@ void CodeEditorSearch::search() juce::String txt = ed->getDocument().getAllContent(); int pos = 0; int count = 0; - int res = txt.indexOfIgnoreCase(pos, textfield->getText()); + int res = !button[0]->isSelected() ? txt.indexOfIgnoreCase(pos, textfield->getText()) + : txt.indexOf(pos, textfield->getText()); resultCurrent = 0; bool firstFound = false; - while (res != -1 && count < 128) + while (res != -1 && count < 512) { - + // whole word + if (button[1]->isSelected()) + { + } result[count] = res; if (caretPos <= res && !firstFound) { @@ -285,7 +549,8 @@ void CodeEditorSearch::search() } pos = res + 1; - res = txt.indexOfIgnoreCase(pos, textfield->getText()); + res = !button[0]->isSelected() ? txt.indexOfIgnoreCase(pos, textfield->getText()) + : txt.indexOf(pos, textfield->getText()); count++; } @@ -294,6 +559,8 @@ void CodeEditorSearch::search() showResult(0, true); } +juce::String CodeEditorSearch::getSearchQuery() { return textfield->getText(); } + /* --------------------------------------- end search @@ -307,6 +574,59 @@ SurgeCodeEditorComponent::SurgeCodeEditorComponent(juce::CodeDocument &d, juce:: void SurgeCodeEditorComponent::setSearch(CodeEditorSearch &s) { search = &s; } +void SurgeCodeEditorComponent::paint(juce::Graphics &g) +{ + // draw background + + juce::Colour bgColor = findColour(juce::CodeEditorComponent::backgroundColourId).withAlpha(1.f); + + auto bounds = getBounds(); + bounds.translate(0, -2); + + g.setFillType(juce::FillType(bgColor)); + g.fillRect(bounds); + // Draw search matches + + if (search != nullptr && search->isVisible() && search->getResultTotal() > 0) + { + auto result = search->getResult(); + int resultTotal = search->getResultTotal(); + + int firstLine = getFirstLineOnScreen(); + int lastLine = firstLine + getNumLinesOnScreen(); + + auto highlightColor = bgColor.interpolatedWith( + juce::Colour(CodeEditorSearch::COLOR_MATCH[0], CodeEditorSearch::COLOR_MATCH[1], + CodeEditorSearch::COLOR_MATCH[2]), + 0.4); + + for (int i = 0; i < resultTotal; i++) + { + + auto pos = juce::CodeDocument::Position(getDocument(), result[i]); + auto line = pos.getLineNumber(); + + if (line >= firstLine && line <= lastLine) + { + + auto posEnd = juce::CodeDocument::Position( + getDocument(), result[i] + search->getSearchQuery().length()); + + auto bounds = getCharacterBounds(pos); + auto boundsEnd = getCharacterBounds(posEnd); + + g.setFillType(juce::FillType(highlightColor)); + int width = boundsEnd.getX() - bounds.getX(); + int height = bounds.getHeight(); + + g.fillRect(bounds.getX(), bounds.getY(), width, height); + } + } + } + + juce::CodeEditorComponent::paint(g); +} + void SurgeCodeEditorComponent::handleEscapeKey() { if (search->isVisible()) @@ -408,7 +728,7 @@ struct EditorColors comp->setColourScheme(cs); comp->setColour(juce::CodeEditorComponent::backgroundColourId, - skin->getColor(Colors::FormulaEditor::Background)); + skin->getColor(Colors::FormulaEditor::Background).withMultipliedAlpha(0)); comp->setColour(juce::CodeEditorComponent::highlightColourId, skin->getColor(Colors::FormulaEditor::Highlight)); comp->setColour(juce::CodeEditorComponent::defaultTextColourId, diff --git a/src/surge-xt/gui/overlays/LuaEditors.h b/src/surge-xt/gui/overlays/LuaEditors.h index bf25dff33fb..82debf29c7a 100644 --- a/src/surge-xt/gui/overlays/LuaEditors.h +++ b/src/surge-xt/gui/overlays/LuaEditors.h @@ -44,46 +44,141 @@ namespace Surge namespace Overlays { -class CodeEditorSearch : public juce::Component, - public juce::TextEditor::Listener, - public juce::KeyListener, - public Surge::GUI::SkinConsumingComponent +class TextfieldButton : public juce::Component +{ + + protected: + std::unique_ptr xml; + bool isSelectable = false; + bool selected = false; + bool isMouseDown = false; + bool isMouseOver = false; + bool enabled = true; + void (*callback)(TextfieldButton &f); + + public: + // void setCallback(std::function btn){callback = btn}; + bool isSelected() { return selected; }; + TextfieldButton(juce::String &svg); + void loadSVG(juce::String &svg); + void setSelectable(); + void select(bool v); + void setEnabled(bool v); + + void updateGraphicState(); + void paint(juce::Graphics &g) override; + void mouseDown(const juce::MouseEvent &event) override; + void mouseUp(const juce::MouseEvent &event) override; + void mouseEnter(const juce::MouseEvent &event) override; + void mouseExit(const juce::MouseEvent &event) override; + // void onClick(TextfieldButton &btn); + std::function onClick; + + private: + std::unique_ptr svgGraphics; +}; + +class Textfield : public juce::TextEditor +{ + + private: + juce::Colour colour; + juce::String header; + + protected: + juce::String title; + + public: + Textfield(); + void paint(juce::Graphics &g) override; + void setColour(int colourID, juce::Colour newColour); + void setHeader(juce::String h); +}; + +class TextfieldPopup : public juce::Component, + public juce::TextEditor::Listener, + public juce::KeyListener, + public Surge::GUI::SkinConsumingComponent +{ + public: + static inline int STYLE_MARGIN = 5; + static inline int STYLE_TEXT_HEIGHT = 20; + static inline int STYLE_BUTTON_MARGIN = 5; + static inline int STYLE_BUTTON_SIZE = 14; + static inline int STYLE_MARGIN_BETWEEN_TEXT_AND_BUTTONS = 40; + + protected: + juce::CodeEditorComponent *ed; + Surge::GUI::Skin::ptr_t currentSkin; + std::unique_ptr textfield; + std::unique_ptr labelResult; + std::unique_ptr button[8]; + // TextfieldButton button[8]; + juce::String header; + int buttonCount = 0; + + private: + int textWidth = 120; + + public: + void paint(juce::Graphics &g) override; + virtual void onClick(std::unique_ptr &btn); + TextfieldPopup(juce::CodeEditorComponent &editor, Surge::GUI::Skin::ptr_t); + void resize(); + void setHeader(juce::String); + void createButton(juce::String svg); + void setTextWidth(int w); +}; + +class CodeEditorSearch : public TextfieldPopup { private: virtual void setHighlightColors(); virtual void removeHighlightColors(); - juce::CodeEditorComponent *ed; + // juce::CodeEditorComponent *ed; bool active = false; - int result[128]; - int resultCurrent; - int resultTotal; + + int result[512]; + int resultCurrent = 0; + int resultTotal = 0; bool saveCaretStartPositionLock; - Surge::GUI::Skin::ptr_t currentSkin; - std::unique_ptr textfield; - std::unique_ptr labelResult; + // Surge::GUI::Skin::ptr_t currentSkin; + + // std::unique_ptr textfield; + // std::unique_ptr labelResult; juce::CodeDocument::Position startCaretPosition; virtual void search(); public: + static constexpr int COLOR_MATCH[3] = {255, 98, 165}; + static constexpr int COLOR_HIGHLIGHT[3] = {25, 187, 105}; + static constexpr int COLOR_ALERT[3] = {255, 0, 1}; + + virtual juce::String getSearchQuery(); virtual bool isActive(); virtual void show(); virtual void hide(); - virtual void resize(); + // virtual void resize(); + + virtual void onClick(std::unique_ptr &btn) override; virtual void textEditorTextChanged(juce::TextEditor &textEditor) override; virtual void mouseDown(const juce::MouseEvent &event) override; virtual void focusLost(FocusChangeType) override; - - CodeEditorSearch(juce::CodeEditorComponent &editor, Surge::GUI::Skin::ptr_t); - virtual void textEditorEscapeKeyPressed(juce::TextEditor &) override; - virtual void textEditorReturnKeyPressed(juce::TextEditor &) override; virtual bool keyPressed(const juce::KeyPress &key, juce::Component *originatingComponent) override; + virtual void textEditorEscapeKeyPressed(juce::TextEditor &) override; + virtual void textEditorReturnKeyPressed(juce::TextEditor &) override; + + CodeEditorSearch(juce::CodeEditorComponent &editor, Surge::GUI::Skin::ptr_t); + virtual void saveCaretStartPosition(bool onlyReadCaretPosition); virtual void showResult(int increase, bool moveCaret); - virtual void paint(juce::Graphics &g) override; + // virtual void paint(juce::Graphics &g) override; + virtual int *getResult(); + virtual int getResultTotal(); }; class SurgeCodeEditorComponent : public juce::CodeEditorComponent @@ -93,6 +188,7 @@ class SurgeCodeEditorComponent : public juce::CodeEditorComponent virtual void handleReturnKey() override; virtual void caretPositionMoved() override; + virtual void paint(juce::Graphics &) override; virtual void setSearch(CodeEditorSearch &s); SurgeCodeEditorComponent(juce::CodeDocument &d, juce::CodeTokeniser *t);