diff --git a/doc/manual.md b/doc/manual.md index a76be94..f824522 100644 --- a/doc/manual.md +++ b/doc/manual.md @@ -121,6 +121,31 @@ Press the COG icon at the top of the UI to get the settings screen, where you can control MPE, Mono mode, pitch bend depth, and more. +## Screen Reader and Accessible Support + +Six Sines supports screen readers and accessible gestures, making +the UI and programming model as inscrutable to these assistive technologies +as it is to users with a visual display. Since the UI is quite big there's +a few extra features for screen reader navigation. + +First, standard edit gestures should work on all controls, and I tried +really hard to make sure tab order makes sense and labels are reasonable. If +you find one which is wrong, please just drop a note on discord or github. + +The structure of the UI is that knbos (like "Op3 feedback level") a panel +in the bottom 1/4 of the screen to edit the modulators and stuff. This panel +arrives when you mouse click or edit the knob. A few features make this +easier to navigate for a screen reader. + +If on a knob in the top third, `Command-A' will arm that knob (namely select the +knob modulation panel in the area below) + +If on a knob in the top third, `Command-J` will jump to the control panel in the +bottom + +And finally from anywhere in the UI, `Command-N` will expose a menu allowing +you to focus any of the focusable top-section knobs or preset manager. + ## Good Luck, and.. Good luck! Its fun. But tricky. If you want to add to this manual diff --git a/libs/sst/sst-jucegui b/libs/sst/sst-jucegui index a47c2df..7ecc2c2 160000 --- a/libs/sst/sst-jucegui +++ b/libs/sst/sst-jucegui @@ -1 +1 @@ -Subproject commit a47c2df6bf7f4bb4942cfebc7b3ea44ca79fe1cb +Subproject commit 7ecc2c2e112fc710678326d1d80748506247ab42 diff --git a/src/ui/knob-highlight.h b/src/ui/knob-highlight.h index 4940967..ec883ae 100644 --- a/src/ui/knob-highlight.h +++ b/src/ui/knob-highlight.h @@ -20,6 +20,12 @@ namespace baconpaul::six_sines::ui { struct KnobHighlight : public juce::Component { + KnobHighlight() + { + setAccessible(false); + setWantsKeyboardFocus(false); + setInterceptsMouseClicks(false, false); + } void paint(juce::Graphics &g) override { g.setColour(juce::Colour(0xFF * 0.3, 0x90 * 0.3, 00)); diff --git a/src/ui/main-panel.cpp b/src/ui/main-panel.cpp index c2f4639..6e6469a 100644 --- a/src/ui/main-panel.cpp +++ b/src/ui/main-panel.cpp @@ -92,6 +92,24 @@ void MainPanel::resized() // voiceLimit->setBounds(vb); } +void MainPanel::mouseDown(const juce::MouseEvent &e) +{ + for (int i = 0; i < numOps; ++i) + { + if (rectangleFor(i).contains(e.position.toInt())) + { + beginEdit(i); + } + } +} + +juce::Rectangle MainPanel::rectangleFor(int idx) +{ + auto b = getContentArea().reduced(uicMargin, 0); + return juce::Rectangle(b.getX() + idx * uicKnobSize + (idx ? idx - 0.5 : 0) * uicMargin, + b.getY(), uicKnobSize + uicMargin, uicLabeledKnobHeight); +} + void MainPanel::beginEdit(int which) { if (which == 3) diff --git a/src/ui/main-panel.h b/src/ui/main-panel.h index 7ea570c..d016005 100644 --- a/src/ui/main-panel.h +++ b/src/ui/main-panel.h @@ -33,6 +33,9 @@ struct MainPanel : jcmp::NamedPanel, HasEditor void resized() override; + void mouseDown(const juce::MouseEvent &e) override; + juce::Rectangle rectangleFor(int idx); + void beginEdit(int which); std::unique_ptr highlight; diff --git a/src/ui/matrix-panel.cpp b/src/ui/matrix-panel.cpp index fd75f28..5678cd9 100644 --- a/src/ui/matrix-panel.cpp +++ b/src/ui/matrix-panel.cpp @@ -160,6 +160,47 @@ void MatrixPanel::paint(juce::Graphics &g) } } +void MatrixPanel::mouseDown(const juce::MouseEvent &e) +{ + for (int i = 0; i < numOps; ++i) + { + if (rectangleFor(i, true).contains(e.position.toInt())) + { + beginEdit(i, true); + return; + } + } + + for (int i = 0; i < matrixSize; ++i) + { + if (rectangleFor(i, false).contains(e.position.toInt())) + { + beginEdit(i, false); + return; + } + } +} + +juce::Rectangle MatrixPanel::rectangleFor(int idx, bool self) +{ + auto b = getContentArea().reduced(uicMargin, 0); + + if (self) + { + return juce::Rectangle(b.getX() + idx * (uicPowerKnobWidth + uicMargin), + b.getY() + idx * (uicLabeledKnobHeight + uicMargin), + uicPowerKnobWidth + 2, uicLabeledKnobHeight); + } + else + { + auto si = MatrixIndex::sourceIndexAt(idx); + auto ti = MatrixIndex::targetIndexAt(idx); + auto y = b.getY() + ti * (uicLabeledKnobHeight + uicMargin); + auto x = b.getX() + si * (uicPowerKnobWidth + uicMargin); + return juce::Rectangle(x, y, uicPowerKnobWidth + 2, uicLabeledKnobHeight); + } +} + void MatrixPanel::beginEdit(size_t idx, bool self) { editor.hideAllSubPanels(); @@ -182,27 +223,9 @@ void MatrixPanel::beginEdit(size_t idx, bool self) editor.matrixSubPanel->setSelectedIndex(idx); } - if (self) - { - highlight->setVisible(true); - auto b = getContentArea().reduced(uicMargin, 0); - highlight->setBounds(b.getX() + idx * (uicPowerKnobWidth + uicMargin), - b.getY() + idx * (uicLabeledKnobHeight + uicMargin), - uicPowerKnobWidth + 2, uicLabeledKnobHeight); - highlight->toBack(); - } - else - { - highlight->setVisible(true); - auto b = getContentArea().reduced(uicMargin, 0); - auto si = MatrixIndex::sourceIndexAt(idx); - auto ti = MatrixIndex::targetIndexAt(idx); - auto y = b.getY() + ti * (uicLabeledKnobHeight + uicMargin); - auto x = b.getX() + si * (uicPowerKnobWidth + uicMargin); - - highlight->setBounds(x, y, uicPowerKnobWidth + 2, uicLabeledKnobHeight); - highlight->toBack(); - } + highlight->setVisible(true); + highlight->setBounds(rectangleFor(idx, self)); + highlight->toBack(); } } // namespace baconpaul::six_sines::ui \ No newline at end of file diff --git a/src/ui/matrix-panel.h b/src/ui/matrix-panel.h index a438d9e..c491163 100644 --- a/src/ui/matrix-panel.h +++ b/src/ui/matrix-panel.h @@ -34,6 +34,9 @@ struct MatrixPanel : jcmp::NamedPanel, HasEditor void beginEdit(size_t, bool); + void mouseDown(const juce::MouseEvent &e) override; + juce::Rectangle rectangleFor(int idx, bool self); + std::unique_ptr highlight; void clearHighlight() { diff --git a/src/ui/mixer-panel.cpp b/src/ui/mixer-panel.cpp index 7c2c0f1..586ce36 100644 --- a/src/ui/mixer-panel.cpp +++ b/src/ui/mixer-panel.cpp @@ -81,6 +81,25 @@ void MixerPanel::resized() } } +void MixerPanel::mouseDown(const juce::MouseEvent &e) +{ + for (int i = 0; i < numOps; ++i) + { + if (rectangleFor(i).contains(e.position.toInt())) + { + beginEdit(i); + } + } +} + +juce::Rectangle MixerPanel::rectangleFor(int idx) +{ + auto b = getContentArea().reduced(uicMargin, 0); + return juce::Rectangle(b.getX(), b.getY() + idx * (uicLabeledKnobHeight + uicMargin), + uicPowerKnobWidth + uicMargin + uicKnobSize + 2, + uicLabeledKnobHeight); +} + void MixerPanel::beginEdit(size_t idx) { editor.hideAllSubPanels(); @@ -90,9 +109,7 @@ void MixerPanel::beginEdit(size_t idx) editor.singlePanel->setName("Op " + std::to_string(idx + 1) + " Mix"); highlight->setVisible(true); - auto b = getContentArea().reduced(uicMargin, 0); - highlight->setBounds(b.getX(), b.getY() + idx * (uicLabeledKnobHeight + uicMargin), - uicPowerKnobWidth + uicMargin + uicKnobSize + 2, uicLabeledKnobHeight); + highlight->setBounds(rectangleFor(idx)); highlight->toBack(); } diff --git a/src/ui/mixer-panel.h b/src/ui/mixer-panel.h index 055cadb..bc1f441 100644 --- a/src/ui/mixer-panel.h +++ b/src/ui/mixer-panel.h @@ -44,6 +44,9 @@ struct MixerPanel : jcmp::NamedPanel, HasEditor highlight->setVisible(false); } + void mouseDown(const juce::MouseEvent &e) override; + juce::Rectangle rectangleFor(int idx); + std::array, numOps> knobs; std::array, numOps> knobsData; std::array, numOps> power; diff --git a/src/ui/patch-data-bindings.h b/src/ui/patch-data-bindings.h index d3714cf..e68e38e 100644 --- a/src/ui/patch-data-bindings.h +++ b/src/ui/patch-data-bindings.h @@ -199,6 +199,7 @@ void createComponent(SixSinesEditor &e, P &panel, const Param &parm, std::unique cm->setSource(pc.get()); e.componentByID[id] = juce::Component::SafePointer(cm.get()); + e.panelSelectGestureFor[cm.get()] = [args..., &panel]() { panel.beginEdit(args...); }; } template @@ -234,6 +235,7 @@ void createRescaledComponent(SixSinesEditor &e, P &panel, const Param &parm, std cm->setSource(rc.get()); e.componentByID[id] = juce::Component::SafePointer(cm.get()); + e.panelSelectGestureFor[cm.get()] = [args..., &panel]() { panel.beginEdit(args...); }; } } // namespace baconpaul::six_sines::ui diff --git a/src/ui/six-sines-editor.cpp b/src/ui/six-sines-editor.cpp index b405443..9bde21b 100644 --- a/src/ui/six-sines-editor.cpp +++ b/src/ui/six-sines-editor.cpp @@ -713,11 +713,28 @@ void SixSinesEditor::postPatchChange(const std::string &dn) bool SixSinesEditor::keyPressed(const juce::KeyPress &key) { - if (key.getModifiers().isCommandDown() && (char)key.getKeyCode() == 78) + if (key.getModifiers().isCommandDown() && (char)key.getKeyCode() == 'N') { SXSNLOG("Navigation Menu"); return true; } + + if (key.getModifiers().isCommandDown() && (char)key.getKeyCode() == 'A') + { + auto c = getCurrentlyFocusedComponent(); + auto psgf = panelSelectGestureFor.find(c); + + if (psgf != panelSelectGestureFor.end()) + psgf->second(); + return true; + } + + if (key.getModifiers().isCommandDown() && (char)key.getKeyCode() == 'J') + { + singlePanel->grabKeyboardFocus(); + return true; + } + return false; } diff --git a/src/ui/six-sines-editor.h b/src/ui/six-sines-editor.h index 7e27c6c..38941e9 100644 --- a/src/ui/six-sines-editor.h +++ b/src/ui/six-sines-editor.h @@ -128,6 +128,8 @@ struct SixSinesEditor : jcmp::WindowPanel void visibilityChanged() override; void parentHierarchyChanged() override; std::unique_ptr focusDebugger; + + std::unordered_map> panelSelectGestureFor; }; struct HasEditor diff --git a/src/ui/source-panel.cpp b/src/ui/source-panel.cpp index 2d7e676..adf4bd3 100644 --- a/src/ui/source-panel.cpp +++ b/src/ui/source-panel.cpp @@ -62,6 +62,24 @@ void SourcePanel::resized() } } +void SourcePanel::mouseDown(const juce::MouseEvent &e) +{ + for (int i = 0; i < numOps; ++i) + { + if (rectangleFor(i).contains(e.position.toInt())) + { + beginEdit(i); + } + } +} + +juce::Rectangle SourcePanel::rectangleFor(int idx) +{ + auto b = getContentArea().reduced(uicMargin, 0); + return juce::Rectangle(b.getX() + idx * (uicPowerKnobWidth + uicMargin), b.getY(), + uicPowerKnobWidth + 2, uicLabeledKnobHeight); +} + void SourcePanel::beginEdit(size_t idx) { editor.hideAllSubPanels(); @@ -70,9 +88,7 @@ void SourcePanel::beginEdit(size_t idx) editor.singlePanel->setName("Op " + std::to_string(idx + 1) + " Source"); highlight->setVisible(true); - auto b = getContentArea().reduced(uicMargin, 0); - highlight->setBounds(b.getX() + idx * (uicPowerKnobWidth + uicMargin), b.getY(), - uicPowerKnobWidth + 2, uicLabeledKnobHeight); + highlight->setBounds(rectangleFor(idx)); highlight->toBack(); } diff --git a/src/ui/source-panel.h b/src/ui/source-panel.h index 03f9863..1f4470a 100644 --- a/src/ui/source-panel.h +++ b/src/ui/source-panel.h @@ -31,6 +31,9 @@ struct SourcePanel : jcmp::NamedPanel, HasEditor void beginEdit(size_t idx); + void mouseDown(const juce::MouseEvent &e) override; + juce::Rectangle rectangleFor(int idx); + std::unique_ptr highlight; void clearHighlight() {