diff --git a/Source/LcdPanel.cpp b/Source/LcdPanel.cpp index 70dc3fb..aafba63 100644 --- a/Source/LcdPanel.cpp +++ b/Source/LcdPanel.cpp @@ -191,7 +191,7 @@ LcdPanel::LcdPanel (RMX19AudioProcessor& p) } else { - screen = Screen::Home; + restoreUiState(); } lastMidi = proc.getLastMidiTime(); startTimerHz (22); @@ -199,6 +199,7 @@ LcdPanel::LcdPanel (RMX19AudioProcessor& p) LcdPanel::~LcdPanel() { + rememberUiState(); stopTimer(); } @@ -221,10 +222,101 @@ bool LcdPanel::firstRunEver() return true; } +// --------------------------------------------------------------------------- +// The panel remembers where the user was (screen + cursors) when the editor is +// closed, and reopens there instead of always dumping them back on Home. +// --------------------------------------------------------------------------- + +juce::File LcdPanel::uiStateFile() +{ + return welcomeFlagFile().getParentDirectory().getChildFile ("ui.state"); +} + +void LcdPanel::rememberUiState() +{ + if (screen == Screen::Splash) + return; + const juce::String txt = juce::String ((int) screen) + " " + + juce::String (cursor) + " " + + juce::String (patchCursor) + " " + + juce::String (paramScreenIdx) + " " + + juce::String (catCursor); + const auto f = uiStateFile(); + f.getParentDirectory().createDirectory(); + f.replaceWithText (txt); +} + +bool LcdPanel::restoreUiState() +{ + const auto f = uiStateFile(); + if (! f.existsAsFile()) + { + screen = Screen::Home; + return false; + } + const auto toks = juce::StringArray::fromTokens (f.loadFileAsString(), juce::StringRef (" \t\r\n"), ""); + if (toks.size() < 5) + { + screen = Screen::Home; + return false; + } + const int s = toks[0].getIntValue(); + const int cu = toks[1].getIntValue(); + const int pc = toks[2].getIntValue(); + const int pm = toks[3].getIntValue(); + const int cc = toks[4].getIntValue(); + + if (s <= (int) Screen::Splash || s > (int) Screen::Category) + { + screen = Screen::Home; + return false; + } + screen = (Screen) s; + + const int cats = (int) proc.getBank().categories().names.size(); + switch (screen) + { + case Screen::Param: + paramScreenIdx = juce::jlimit (0, 6, pm); + cursor = juce::jlimit (0, paramCountForScreen (paramScreenIdx) - 1, cu); + break; + case Screen::Patch: + catCursor = juce::jlimit (0, cats - 1, cc); + buildFilteredPatches (catCursor); + patchCursor = juce::jlimit (0, patchCountForCurrentView() - 1, pc); + cursor = 0; + break; + case Screen::Patches: + catCursor = juce::jlimit (0, cats - 1, cc); + cursor = 0; + break; + case Screen::Category: + catCursor = juce::jlimit (0, cats, cc); // +1 SETTINGS row + cursor = 0; + break; + case Screen::Home: + cursor = 0; + break; + default: + cursor = juce::jlimit (0, rowCountForScreen() - 1, cu); + break; + } + return true; +} + void LcdPanel::timerCallback() { bool needsRedraw = (screen == Screen::Splash); + // debounce the last-screen write so navigation during tweaking doesn't + // hammer the disk + if (uiDirty && (juce::Time::getMillisecondCounter() - lastUiWrite) > 700) + { + rememberUiState(); + uiDirty = false; + lastUiWrite = juce::Time::getMillisecondCounter(); + } + if (proc.getLastMidiTime() != lastMidi) { lastMidi = proc.getLastMidiTime(); @@ -843,6 +935,7 @@ void LcdPanel::moveCursor (int d) cursor = cyclicStep (cursor, rowCountForScreen(), d); setDirty(); } + uiDirty = true; } void LcdPanel::stepDelaySync (int dir) diff --git a/Source/LcdPanel.h b/Source/LcdPanel.h index 541c541..3790ccc 100644 --- a/Source/LcdPanel.h +++ b/Source/LcdPanel.h @@ -19,6 +19,7 @@ public: void resetToHome(); static bool firstRunEver(); + static juce::File uiStateFile(); static constexpr int gridW = 160; // dot-matrix cell resolution static constexpr int gridH = 64; @@ -52,6 +53,13 @@ private: int lastVoices = -1; juce::uint32 lastMidi = 0; + // last-screen persistence: the panel reopens where the user left off + juce::uint32 lastUiWrite = 0; + bool uiDirty = false; + + void rememberUiState(); + bool restoreUiState(); + juce::Image displayImage; // (gridW*subsample)x(gridH*subsample) render target: glass base + content dots juce::Image presentImage; // (gridW*presentScale)x(gridH*presentScale) box-downsampled for the panel juce::Image backdrop; // (gridW*subsample)x(gridH*subsample) static 3D glass base (backlight + sheen + vignette + ghost dots) @@ -89,7 +97,7 @@ private: void stepDelaySync (int dir); void setDirty() { contentDirty = true; repaint(); } - void enterScreen (Screen s) { screen = s; cursor = 0; setDirty(); } + void enterScreen (Screen s) { screen = s; cursor = 0; uiDirty = true; setDirty(); } // open the category/patch browser, positioned on the loaded patch's category void openPatchBrowser();