From 1649d4f137a7968c867386fd1c13853ed4dea224 Mon Sep 17 00:00:00 2001 From: dantrueman Date: Thu, 24 Oct 2024 08:52:14 -0400 Subject: [PATCH] created BKSliders.h.cpp and brought over BKStackedSlider from old code --- CMakeLists.txt | 2 + source/interface/BKSliders.cpp | 565 ++++++++++++++++++ source/interface/BKSliders.h | 136 +++++ .../Preparations/DirectPreparation.h | 48 +- 4 files changed, 750 insertions(+), 1 deletion(-) create mode 100644 source/interface/BKSliders.cpp create mode 100644 source/interface/BKSliders.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8964d2a..36b88c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,6 +116,8 @@ add_library(SharedCode INTERFACE source/interface/Cable/CableView.cpp source/interface/Cable/CableView.h source/synthesis/framework/tuning_systems.h + source/interface/BKSliders.cpp + source/interface/BKSliders.h ) # C++20, please diff --git a/source/interface/BKSliders.cpp b/source/interface/BKSliders.cpp new file mode 100644 index 0000000..2dbb32a --- /dev/null +++ b/source/interface/BKSliders.cpp @@ -0,0 +1,565 @@ +// +// Created by Dan Trueman on 10/22/24. +// + +#include "BKSliders.h" + +/** + * move this stuff somewhere else + */ +juce::String BKfloatArrayToString(juce::Array arr) +{ + juce::String s = ""; + for (auto key : arr) + { + s.append(juce::String(key), 6); + s.append(" ", 1); + } + return s; +} + +juce::Array BKstringToFloatArray(juce::String s) +{ + juce::Array arr = juce::Array(); + + juce::String temp = ""; + bool inNumber = false; + + juce::String::CharPointerType c = s.getCharPointer(); + + juce::juce_wchar prd = '.'; + juce::juce_wchar dash = '-'; + juce::juce_wchar slash = '/'; // blank: put a zero in + + int prdCnt = 0; + + // DEBUG + for (int i = 0; i < (s.length()+1); i++) + { + juce::juce_wchar c1 = c.getAndAdvance(); + + bool isPrd = !juce::CharacterFunctions::compare(c1, prd); + bool isDash = !juce::CharacterFunctions::compare(c1, dash); + bool isSlash = !juce::CharacterFunctions::compare(c1, slash); + + if (isPrd) prdCnt += 1; + + bool isNumChar = juce::CharacterFunctions::isDigit(c1) || isPrd || isDash; + + if (!isNumChar) + { + if (inNumber) + { + arr.add(temp.getFloatValue()); + temp = ""; + } + + // slash indicates a zero slot + if (isSlash) { + arr.add(0.); + temp = ""; + } + + inNumber = false; + continue; + } + else + { + inNumber = true; + + temp += c1; + } + } + + return arr; +} + + +BKStackedSlider::BKStackedSlider( + juce::String sliderName, + double min, + double max, + double defmin, + double defmax, + double def, + double increment): + sliderName(sliderName), + sliderMin(min), + sliderMax(max), + sliderMinDefault(defmin), + sliderMaxDefault(defmax), + sliderDefault(def), + sliderIncrement(increment) +{ + + showName.setText(sliderName, juce::dontSendNotification); + showName.setInterceptsMouseClicks(false, true); + addAndMakeVisible(showName); + + editValsTextField = std::make_unique(); + editValsTextField->setMultiLine(true); + editValsTextField->setName("PARAMTXTEDIT"); + editValsTextField->addListener(this); + editValsTextField->setColour(juce::TextEditor::highlightColourId, juce::Colours::darkgrey); +#if JUCE_IOS + editValsTextField->setReadOnly(true); + editValsTextField->setCaretVisible(true); +#endif + addAndMakeVisible(*editValsTextField); + editValsTextField->setVisible(false); + + numSliders = 12; + numActiveSliders = 1; + + for(int i=0; isetSliderStyle(juce::Slider::LinearBar); + newSlider->setTextBoxStyle(juce::Slider::NoTextBox, false, 0, 0); + newSlider->setRange(sliderMin, sliderMax, sliderIncrement); + newSlider->setValue(sliderDefault, juce::dontSendNotification); + //newSlider->setLookAndFeel(&stackedSliderLookAndFeel); + //newSlider->addListener(this); + addAndMakeVisible(newSlider); + if(i>0) { + newSlider->setVisible(false); + activeSliders.insert(i, false); + } + else activeSliders.insert(0, true); + } + + topSlider = std::make_unique(); + topSlider->setSliderStyle(juce::Slider::LinearBar); + topSlider->setTextBoxStyle(juce::Slider::TextEntryBoxPosition::TextBoxLeft, true, 0,0); + //topSlider->setTextBoxStyle(Slider::TextEntryBoxPosition::TextBoxLeft, true, 50,50); + topSlider->setRange(sliderMin, sliderMax, sliderIncrement); + topSlider->setValue(sliderDefault, juce::dontSendNotification); + topSlider->addListener(this); + topSlider->addMouseListener(this, true); + //topSlider->setLookAndFeel(&topSliderLookAndFeel); + topSlider->setAlpha(0.); + addAndMakeVisible(*topSlider); + + //topSliderLookAndFeel.setColour(Slider::thumbColourId, Colour::greyLevel (0.8f).contrasting().withAlpha (0.0f)); + //stackedSliderLookAndFeel.setColour(Slider::thumbColourId, Colours::goldenrod.withMultipliedAlpha(0.95)); + +} + +void BKStackedSlider::setDim(float alphaVal) +{ + showName.setAlpha(alphaVal); + //topSlider->setAlpha(alphaVal); + + for(int i=0; isetAlpha(alphaVal); + } + } + } +} + +void BKStackedSlider::setBright() +{ + showName.setAlpha(1.); + //topSlider->setAlpha(1.); + + for(int i=0; isetAlpha(1.); + } + } + } +} + +void BKStackedSlider::sliderValueChanged (juce::Slider *slider) +{ + +} + +void BKStackedSlider::addSlider(juce::NotificationType newnotify) +{ + juce::Array sliderVals = getAllActiveValues(); + //sliderVals.add(sliderDefault); + sliderVals.add(topSlider->proportionOfLengthToValue((double)clickedPosition / getWidth())); + setTo(sliderVals, newnotify); + topSlider->setValue(topSlider->proportionOfLengthToValue((double)clickedPosition / getWidth()), juce::dontSendNotification); + + listeners.call(&BKStackedSlider::Listener::BKStackedSliderValueChanged, + getName(), + getAllActiveValues()); +} + +void BKStackedSlider::setTo(juce::Array newvals, juce::NotificationType newnotify) +{ + + int slidersToActivate = newvals.size(); + if(slidersToActivate > numSliders) slidersToActivate = numSliders; + if(slidersToActivate <= 0) + { + slidersToActivate = 1; + newvals.add(sliderDefault); + } + + //activate sliders + for(int i=0; i sliderMax) + newSlider->setRange(sliderMin, newvals.getUnchecked(i), sliderIncrement); + + if(newvals.getUnchecked(i) < sliderMin) + newSlider->setRange(newvals.getUnchecked(i), sliderMax, sliderIncrement); + + newSlider->setValue(newvals.getUnchecked(i)); + newSlider->setVisible(true); + } + + activeSliders.set(i, true); + } + + //deactivate unused sliders + for(int i=slidersToActivate; isetValue(sliderDefault); + newSlider->setVisible(false); + } + + activeSliders.set(i, false); + } + + //make sure there is one! + if(slidersToActivate <= 0) + { + dataSliders.getFirst()->setValue(sliderDefault); + activeSliders.set(0, true); + } + + resetRanges(); + + topSlider->setValue(dataSliders.getFirst()->getValue(), juce::dontSendNotification); +} + + +void BKStackedSlider::mouseDown (const juce::MouseEvent &event) +{ + if(event.mouseWasClicked()) + { + clickedSlider = whichSlider(); + clickedPosition = event.x; + + mouseJustDown = true; + + if(event.mods.isCtrlDown()) + { + showModifyPopupMenu(); + } + } +} + + +void BKStackedSlider::mouseDrag(const juce::MouseEvent& e) +{ + if(!mouseJustDown) + { + juce::Slider* currentSlider = dataSliders.operator[](clickedSlider); + if(currentSlider != nullptr) + { + if(e.mods.isShiftDown()) + { + currentSlider->setValue(round(topSlider->getValue())); + topSlider->setValue(round(topSlider->getValue())); + } + else { + currentSlider->setValue(topSlider->getValue(), juce::sendNotification); + } + } + } + else mouseJustDown = false; + +} + +void BKStackedSlider::mouseUp(const juce::MouseEvent& e) +{ + DBG("BKStackedSlider::mouseUp"); + + listeners.call(&BKStackedSlider::Listener::BKStackedSliderValueChanged, + getName(), + getAllActiveValues()); + +} + +void BKStackedSlider::mouseMove(const juce::MouseEvent& e) +{ + //topSlider->setValue(topSlider->proportionOfLengthToValue((double)e.x / getWidth()), dontSendNotification); + topSlider->setValue(dataSliders.getUnchecked(whichSlider(e))->getValue()); + + for(int i=0; isetTextBoxStyle(juce::Slider::TextBoxLeft, false, 50, 50); + else + dataSliders.getUnchecked(i)->setTextBoxStyle(juce::Slider::NoTextBox, false, 50, 50); + } +} + + +void BKStackedSlider::mouseDoubleClick (const juce::MouseEvent &e) +{ + //highlight number for current slider + +#if JUCE_IOS + hasBigOne = true; + editValsTextField->setText(floatArrayToString(getAllActiveValues()), dontSendNotification); + WantsBigOne::listeners.call(&WantsBigOne::Listener::iWantTheBigOne, editValsTextField.get(), sliderName); +#else + juce::StringArray tokens; + tokens.addTokens(BKfloatArrayToString(getAllActiveValues()), false); //arrayFloatArrayToString + int startPoint = 0; + int endPoint; + + int which = whichSlider(); + + for(int i=0; i < which; i++) { + startPoint += tokens[i].length() + 1; + } + endPoint = startPoint + tokens[which].length(); + + editValsTextField->setVisible(true); + editValsTextField->toFront(true); + editValsTextField->grabKeyboardFocus(); + editValsTextField->setText(BKfloatArrayToString(getAllActiveValues()), juce::dontSendNotification); //arrayFloatArrayToString + + juce::Range highlightRange(startPoint, endPoint); + editValsTextField->setHighlightedRegion(highlightRange); + + focusLostByEscapeKey = false; +#endif + +} + +void BKStackedSlider::textEditorReturnKeyPressed(juce::TextEditor& textEditor) +{ + if(textEditor.getName() == editValsTextField->getName()) + { + editValsTextField->setVisible(false); + editValsTextField->toBack(); + + setTo(BKstringToFloatArray(textEditor.getText()), juce::sendNotification); + clickedSlider = 0; + resized(); + + listeners.call(&BKStackedSlider::Listener::BKStackedSliderValueChanged, + getName(), + getAllActiveValues()); + + } +} + +void BKStackedSlider::textEditorFocusLost(juce::TextEditor& textEditor) +{ +#if !JUCE_IOS + if(!focusLostByEscapeKey) { + textEditorReturnKeyPressed(textEditor); + } +#endif +} + +void BKStackedSlider::textEditorEscapeKeyPressed (juce::TextEditor& textEditor) +{ + if(textEditor.getName() == editValsTextField->getName()) + { + focusLostByEscapeKey = true; + editValsTextField->setVisible(false); + editValsTextField->toBack(); + unfocusAllComponents(); + } +} + +void BKStackedSlider::textEditorTextChanged(juce::TextEditor& textEditor) +{ +#if JUCE_IOS + if (hasBigOne) + { + hasBigOne = false; + textEditorReturnKeyPressed(textEditor); + } +#endif +} + +void BKStackedSlider::resetRanges() +{ + + double sliderMinTemp = sliderMinDefault; + double sliderMaxTemp = sliderMaxDefault; + + for(int i = 0; igetValue() > sliderMaxTemp) sliderMaxTemp = currentSlider->getValue(); + if(currentSlider->getValue() < sliderMinTemp) sliderMinTemp = currentSlider->getValue(); + } + } + + if( (sliderMax != sliderMaxTemp) || sliderMin != sliderMinTemp) + { + sliderMax = sliderMaxTemp; + sliderMin = sliderMinTemp; + + for(int i = 0; isetRange(sliderMin, sliderMax, sliderIncrement); + } + } + + topSlider->setRange(sliderMin, sliderMax, sliderIncrement); + } +} + + +juce::Array BKStackedSlider::getAllActiveValues() +{ + juce::Array currentVals; + + for(int i=0; igetValue()); + } + + } + + return currentVals; +} + +int BKStackedSlider::whichSlider() +{ + float refDistance; + int whichSub = 0; + + juce::Slider* refSlider = dataSliders.getFirst(); + refDistance = fabs(refSlider->getValue() - topSlider->getValue()); + + for(int i=1; igetValue() - topSlider->getValue()); + if(tempDistance < refDistance) + { + whichSub = i; + refDistance = tempDistance; + } + } + } + } + //DBG("whichSlider = " + String(whichSub)); + + return whichSub; +} + +int BKStackedSlider::whichSlider(const juce::MouseEvent& e) +{ + float refDistance; + float topSliderVal = topSlider->proportionOfLengthToValue((double)e.x / getWidth()); + + int whichSub = 0; + + juce::Slider* refSlider = dataSliders.getFirst(); + refDistance = fabs(refSlider->getValue() - topSliderVal); + + for(int i=1; igetValue() - topSliderVal); + if(tempDistance < refDistance) + { + whichSub = i; + refDistance = tempDistance; + } + } + } + } + //DBG("whichSlider = " + String(whichSub)); + + return whichSub; +} + +void BKStackedSlider::showModifyPopupMenu() +{ + juce::PopupMenu m; + m.addItem (1, juce::translate ("add transposition"), true, false); + m.addSeparator(); + + m.showMenuAsync (juce::PopupMenu::Options(), + juce::ModalCallbackFunction::forComponent (sliderModifyMenuCallback, this)); +} + +void BKStackedSlider::sliderModifyMenuCallback (const int result, BKStackedSlider* ss) +{ + if (ss == nullptr) + { + juce::PopupMenu::dismissAllActiveMenus(); + return; + } + + switch (result) + { + case 1: ss->addSlider(juce::sendNotification); break; + + default: break; + } +} + + +void BKStackedSlider::resized () +{ + juce::Rectangle area (getLocalBounds()); + + showName.setBounds(area.toNearestInt()); + showName.setJustificationType(juce::Justification::topRight); + showName.toFront(false); + + topSlider->setBounds(area); + + editValsTextField->setBounds(area); + editValsTextField->setVisible(false); + + for(int i=0; isetBounds(area); + } + +} diff --git a/source/interface/BKSliders.h b/source/interface/BKSliders.h new file mode 100644 index 0000000..007138c --- /dev/null +++ b/source/interface/BKSliders.h @@ -0,0 +1,136 @@ +// +// Created by Dan Trueman on 10/22/24. +// + +#ifndef BITKLAVIER2_BKSLIDERS_H +#define BITKLAVIER2_BKSLIDERS_H + +#endif //BITKLAVIER2_BKSLIDERS_H + +#include "FullInterface.h" +#include "PreparationSection.h" + +/** + * BKStacked Slider + * + * for having multiple simultaneous overlaid sliders + * main application: transposition sliders in Direct and Nostalgic + * + */ +class BKStackedSlider : + public juce::Component, + public juce::Slider::Listener, + public juce::TextEditor::Listener +{ +public: + + BKStackedSlider(juce::String sliderName, double min, double max, double defmin, double defmax, double def, double increment); + + ~BKStackedSlider() + { + /* + topSlider->setLookAndFeel(nullptr); + + for(int i=0; isetLookAndFeel(nullptr); + } + } + */ + }; + + void sliderValueChanged (juce::Slider *slider) override; + void textEditorReturnKeyPressed(juce::TextEditor& textEditor) override; + void textEditorFocusLost(juce::TextEditor& textEditor) override; + void textEditorEscapeKeyPressed (juce::TextEditor& textEditor) override; + void textEditorTextChanged(juce::TextEditor& textEditor) override; + void mouseDown (const juce::MouseEvent &event) override; + void mouseDrag(const juce::MouseEvent& e) override; + void mouseUp(const juce::MouseEvent& e) override; + void mouseMove(const juce::MouseEvent& e) override; + void mouseDoubleClick (const juce::MouseEvent &e) override; + + inline juce::TextEditor* getTextEditor(void) + { + return editValsTextField.get(); + } + + inline void dismissTextEditor(bool setValue = false) + { + if (setValue) textEditorReturnKeyPressed(*editValsTextField); + else textEditorEscapeKeyPressed(*editValsTextField); + } + + void setTo(juce::Array newvals, juce::NotificationType newnotify); + void setValue(juce::Array newvals, juce::NotificationType newnotify) { setTo(newvals, newnotify); } + void resetRanges(); + int whichSlider(); + int whichSlider(const juce::MouseEvent& e); + void addSlider(juce::NotificationType newnotify); + + inline juce::String getText(void) { return editValsTextField->getText(); } + inline void setText(juce::String text) { editValsTextField->setText(text, juce::dontSendNotification); } + + void setName(juce::String newName) { sliderName = newName; showName.setText(sliderName, juce::dontSendNotification); } + juce::String getName() { return sliderName; } + void setTooltip(juce::String newTip) { topSlider->setTooltip(newTip); showName.setTooltip(newTip); } + + void resized() override; + + void setDim(float newAlpha); + void setBright(); + + class Listener + { + + public: + + virtual ~Listener() {}; + + virtual void BKStackedSliderValueChanged(juce::String name, juce::Array val) = 0; //rewrite all this to pass "this" and check by slider ref instead of name? + }; + + juce::ListenerList listeners; + void addMyListener(Listener* listener) { listeners.add(listener); } + void removeMyListener(Listener* listener) { listeners.remove(listener); } + + +private: + + std::unique_ptr topSlider; //user interacts with this + juce::OwnedArray dataSliders; //displays data, user controls with topSlider + juce::Array activeSliders; + + std::unique_ptr editValsTextField; + + int numSliders; + int numActiveSliders; + int clickedSlider; + float clickedPosition; + + juce::String sliderName; + juce::Label showName; + bool justifyRight; + + //BKMultiSliderLookAndFeel stackedSliderLookAndFeel; + //BKMultiSliderLookAndFeel topSliderLookAndFeel; + + double sliderMin, sliderMax, sliderMinDefault, sliderMaxDefault; + double sliderDefault; + double sliderIncrement; + double currentDisplaySliderValue; + + bool focusLostByEscapeKey; + bool focusLostByNumPad; + bool mouseJustDown; + + juce::Array getAllActiveValues(); + + void showModifyPopupMenu(); + static void sliderModifyMenuCallback (const int result, BKStackedSlider* ss); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BKStackedSlider) +}; \ No newline at end of file diff --git a/source/interface/Preparations/DirectPreparation.h b/source/interface/Preparations/DirectPreparation.h index 726c69e..0383de3 100644 --- a/source/interface/Preparations/DirectPreparation.h +++ b/source/interface/Preparations/DirectPreparation.h @@ -10,11 +10,57 @@ #include "popup_browser.h" #include "ParameterView/ParametersView.h" #include "FullInterface.h" +#include "BKSliders.h" /************************************************************************************/ /* CLASS: OpenGlSlider */ /************************************************************************************/ -class OpenGlSlider; +class OpenGlTranspositionSlider : public OpenGlAutoImageComponent +{ +public: + OpenGlTranspositionSlider(const juce::ValueTree& v) : + OpenGlAutoImageComponent( + "Transpositions", // slider name + 0., // min + 10., // max + 0., // default min + 10., // default max + 1., // default val + 0.001) + { // increment + image_component_ = std::make_shared(); + setLookAndFeel(DefaultLookAndFeel::instance()); + image_component_->setComponent(this); + } + + virtual void resized() override + { + OpenGlAutoImageComponent::resized(); + if (isShowing()) + redoImage(); + } +}; + +/* + * class OpenGlMidiSelector : public OpenGlAutoImageComponent { +public: + OpenGlMidiSelector(const juce::ValueTree& v) : + OpenGlAutoImageComponent(v) { + image_component_ = std::make_shared(); + setLookAndFeel(DefaultLookAndFeel::instance()); + image_component_->setComponent(this); + } + +virtual void resized() override { + OpenGlAutoImageComponent::resized(); + if (isShowing()) + redoImage(); +} + +private: +JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OpenGlMidiSelector) +}; + */ /************************************************************************************/ /* CLASS: DirectPreparation, inherits from PreparationSection */