Skip to content

Commit

Permalink
Browser and ListView work (#1461)
Browse files Browse the repository at this point in the history
1. Create a jucegui ListView class with a lighter component
   based API and use that for the locations area (but not the
   FS area yet)
2. Add a menu item to listview devices to remove a device and
   to add one with finder. Closes #720
3. Make preview state management better so swapping doesnt have
   hangs for on/off state. Closes #1457
4. Start refactoring innards of database to allow indexing for
   search but don't use that code, just make API changes.
  • Loading branch information
baconpaul authored Nov 24, 2024
1 parent f8ed759 commit 6ccc3bf
Show file tree
Hide file tree
Showing 14 changed files with 270 additions and 93 deletions.
208 changes: 150 additions & 58 deletions src-ui/app/browser-ui/BrowserPane.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "sst/jucegui/components/NamedPanelDivider.h"
#include "sst/jucegui/components/TextPushButton.h"
#include "sst/jucegui/components/GlyphButton.h"
#include "sst/jucegui/components/ListView.h"
#include "sst/plugininfra/strnatcmp.h"

#include <infrastructure/user_defaults.h>
Expand Down Expand Up @@ -59,57 +60,121 @@ struct TempPane : HasEditor, juce::Component
}
};

struct DriveListBoxModel : juce::ListBoxModel
struct DriveArea : juce::Component, HasEditor
{
BrowserPane *browserPane{nullptr};
DriveListBoxModel(BrowserPane *b) : browserPane(b) {}
int getNumRows() override { return browserPane->roots.size(); }
void paintListBoxItem(int rowNumber, juce::Graphics &g, int width, int height,
bool rowIsSelected) override
struct DriveAreaRow : juce::Component
{
if (rowNumber >= 0 && rowNumber < browserPane->roots.size())
DriveArea *driveArea{nullptr};
uint32_t rowNumber{0};
bool selected{false};
bool hovered{false};

DriveAreaRow(DriveArea *p) : driveArea(p) {}
void paint(juce::Graphics &g) override
{
g.setFont(browserPane->editor->themeApplier.interMediumFor(12));
auto *ed = driveArea->browserPane->editor;
auto *bp = driveArea->browserPane;

// TODO: Style all of these
auto textColor =
browserPane->editor->themeColor(theme::ColorMap::generic_content_medium);
auto fillColor = juce::Colour(0, 0, 0).withAlpha(0.f);
if (rowNumber >= 0 && rowNumber < bp->roots.size())
{
g.setFont(ed->themeApplier.interMediumFor(12));

// TODO: Style all of these
auto textColor = ed->themeColor(theme::ColorMap::generic_content_medium);
auto fillColor = juce::Colour(0, 0, 0).withAlpha(0.f);

if (selected)
{
fillColor = ed->themeColor(theme::ColorMap::bg_3);
textColor = ed->themeColor(theme::ColorMap::generic_content_high);
}
else if (hovered)
{
fillColor = ed->themeColor(theme::ColorMap::bg_3);
}
g.setColour(fillColor);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColour(textColor);
g.drawText(std::get<1>(bp->roots[rowNumber]), 1, 1, getWidth() - 2, getHeight() - 2,
juce::Justification::centredLeft);
}
}
void mouseEnter(const juce::MouseEvent &event) override
{
hovered = true;
repaint();
}
void mouseExit(const juce::MouseEvent &event) override
{
hovered = false;
repaint();
}

void mouseDown(const juce::MouseEvent &e) override;

void showPopup()
{
auto *bp = driveArea->browserPane;
auto pth = std::get<0>(bp->roots[rowNumber]);
auto nm = std::get<1>(bp->roots[rowNumber]);

auto p = juce::PopupMenu();
p.addSectionHeader(nm);
p.addSeparator();

p.addItem("Remove '" + nm + "' from Locations", [w = juce::Component::SafePointer(
driveArea->browserPane),
removeThis = pth]() {
if (!w)
return;
namespace cmsg = scxt::messaging::client;
w->sendToSerialization(cmsg::RemoveBrowserDeviceLocation(removeThis.u8string()));
});
// TODO: If Indexed
p.addItem("Add '" + nm + "' to Search Index", false, false, []() {});

if (rowIsSelected)
if (!fs::is_directory(pth))
{
fillColor = browserPane->editor->themeColor(theme::ColorMap::bg_3);
textColor = browserPane->editor->themeColor(theme::ColorMap::generic_content_high);
pth = pth.parent_path();
}
g.setColour(fillColor);
g.fillRect(0, 0, width, height);
g.setColour(textColor);
g.drawText(browserPane->roots[rowNumber].second, 1, 1, width - 2, height - 2,
juce::Justification::centredLeft);
p.addItem("Reveal '" + nm + "'...",
[pth]() { juce::File(pth.u8string()).revealToUser(); });
p.addSeparator();
p.addItem("Add Location...", [w = juce::Component::SafePointer(this)]() {
if (!w)
return;
auto ed = w->driveArea->browserPane->editor;
ed->fileChooser = std::make_unique<juce::FileChooser>("Add Location");
ed->fileChooser->launchAsync(
juce::FileBrowserComponent::canSelectDirectories, [w, ed](const auto &c) {
auto result = c.getResults();
namespace cmsg = scxt::messaging::client;
w->driveArea->browserPane->sendToSerialization(
cmsg::AddBrowserDeviceLocation(
result[0].getFullPathName().toStdString()));
});
});
p.showMenuAsync(driveArea->browserPane->editor->defaultPopupMenuOptions());
}
}
void selectedRowsChanged(int lastRowSelected) override;
};
};

struct DriveArea : juce::Component, HasEditor
{
BrowserPane *browserPane{nullptr};
std::unique_ptr<DriveListBoxModel> model;
std::unique_ptr<juce::ListBox> lbox;
std::unique_ptr<sst::jucegui::components::ListView> listView;
DriveArea(BrowserPane *b, SCXTEditor *e) : browserPane(b), HasEditor(e)
{
model = std::make_unique<DriveListBoxModel>(browserPane);
lbox = std::make_unique<juce::ListBox>("Root Devices", model.get());
lbox->setClickingTogglesRowSelection(true);
lbox->setMultipleSelectionEnabled(false);
lbox->setRowHeight(18);
lbox->setColour(juce::ListBox::ColourIds::backgroundColourId,
juce::Colour(0.f, 0.f, 0.f, 0.f));

addAndMakeVisible(*lbox);
listView = std::make_unique<sst::jucegui::components::ListView>();
listView->getRowCount = [this]() { return browserPane->roots.size(); };
listView->getRowHeight = [this]() { return 18; };
listView->makeRowComponent = [this]() { return makeComponent(); };
listView->assignComponentToRow = [this](const auto &c, auto r) {
return assignComponentToRow(c, r);
};
listView->setRowSelection = [this](const auto &c, auto r) { return setRowSelection(c, r); };
listView->refresh();
addAndMakeVisible(*listView);
};

~DriveArea() { lbox->setModel(nullptr); }
~DriveArea() {}

void paint(juce::Graphics &g) override
{
Expand All @@ -119,7 +184,29 @@ struct DriveArea : juce::Component, HasEditor
g.drawRoundedRectangle(getLocalBounds().toFloat(), 2, 1);
}

void resized() override { lbox->setBounds(getLocalBounds().reduced(1)); }
void resized() override { listView->setBounds(getLocalBounds().reduced(1)); }
std::unique_ptr<juce::Component> makeComponent()
{
return std::make_unique<DriveAreaRow>(this);
}
void assignComponentToRow(const std::unique_ptr<juce::Component> &c, int r)
{
auto *dar = dynamic_cast<DriveAreaRow *>(c.get());
if (dar)
{
dar->rowNumber = r;
dar->repaint();
}
}
void setRowSelection(const std::unique_ptr<juce::Component> &c, bool r)
{
auto *dar = dynamic_cast<DriveAreaRow *>(c.get());
if (dar)
{
dar->selected = r;
dar->repaint();
}
}
};

struct DriveFSListBoxModel : juce::ListBoxModel
Expand Down Expand Up @@ -272,9 +359,21 @@ struct DevicesPane : HasEditor, juce::Component
}
};

void DriveListBoxModel::selectedRowsChanged(int lastRowSelected)
void DriveArea::DriveAreaRow::mouseDown(const juce::MouseEvent &e)
{
browserPane->devicesPane->driveFSArea->setRootRow(browserPane->roots[lastRowSelected].first);
if (rowNumber >= 0 && rowNumber < driveArea->browserPane->roots.size())
{
if (e.mods.isPopupMenu())
{
showPopup();
}
else
{
driveArea->browserPane->devicesPane->driveFSArea->setRootRow(
std::get<0>(driveArea->browserPane->roots[rowNumber]));
driveArea->listView->rowSelected(rowNumber, true);
}
}
}

int DriveFSListBoxModel::getNumRows()
Expand Down Expand Up @@ -302,23 +401,16 @@ struct DriveFSListBoxRow : public juce::Component
isMouseDownWithoutDrag = true;
if (browserPane->autoPreviewEnabled && isFile())
{
if (hasStartedPreview)
{
stopPreview();
}
else
const auto &data = browserPane->devicesPane->driveFSArea->contents;
const auto &entry = data[rowNumber];
if (browser::Browser::isLoadableSingleSample(entry.path()))
{
const auto &data = browserPane->devicesPane->driveFSArea->contents;
const auto &entry = data[rowNumber];
if (browser::Browser::isLoadableSingleSample(entry.path()))
{
hasStartedPreview = true;
namespace cmsg = scxt::messaging::client;
scxt::messaging::client::clientSendToSerialization(
cmsg::PreviewBrowserSample({true, data[rowNumber].path().u8string()}),
browserPane->editor->msgCont);
repaint();
}
hasStartedPreview = true;
namespace cmsg = scxt::messaging::client;
scxt::messaging::client::clientSendToSerialization(
cmsg::PreviewBrowserSample({true, data[rowNumber].path().u8string()}),
browserPane->editor->msgCont);
repaint();
}
}

Expand Down Expand Up @@ -652,8 +744,8 @@ void BrowserPane::resized()
void BrowserPane::resetRoots()
{
roots = editor->browser.getRootPathsForDeviceView();
if (devicesPane && devicesPane->driveArea && devicesPane->driveArea->lbox)
devicesPane->driveArea->lbox->updateContent();
if (devicesPane && devicesPane->driveArea && devicesPane->driveArea->listView)
devicesPane->driveArea->listView->refresh();
repaint();
}

Expand Down
3 changes: 2 additions & 1 deletion src-ui/app/browser-ui/BrowserPane.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <juce_gui_basics/juce_gui_basics.h>
#include <sst/jucegui/components/NamedPanel.h>
#include "app/HasEditor.h"
#include "browser/browser.h"

#include "sst/jucegui/components/ToggleButtonRadioGroup.h"

Expand All @@ -53,7 +54,7 @@ struct BrowserPane : public app::HasEditor, sst::jucegui::components::NamedPanel
void resized() override;

void resetRoots();
std::vector<std::pair<fs::path, std::string>> roots;
std::vector<scxt::browser::Browser::indexedRootPath_t> roots;

std::unique_ptr<DevicesPane> devicesPane;
std::unique_ptr<FavoritesPane> favoritesPane;
Expand Down
19 changes: 14 additions & 5 deletions src/browser/browser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,18 @@ Browser::Browser(BrowserDB &db, const infrastructure::DefaultsProvider &dp, cons
themeDirectory = create("Themes");
}

std::vector<std::pair<fs::path, std::string>> Browser::getRootPathsForDeviceView() const
std::vector<Browser::indexedRootPath_t> Browser::getRootPathsForDeviceView() const
{
// TODO - append local favorites
auto osdef = getOSDefaultRootPathsForDeviceView();
std::vector<Browser::indexedRootPath_t> res;
for (const auto &[p, s] : osdef)
res.emplace_back(p, s, false);
auto fav = browserDb.getDeviceLocations();
for (const auto &p : fav)
osdef.push_back({p, p.filename().u8string()});
res.emplace_back(p.first, p.first.filename().u8string(), p.second);

return osdef;
return res;
}

const std::vector<std::string> Browser::LoadableFile::singleSample{".wav", ".flac", ".mp3", ".aif",
Expand Down Expand Up @@ -97,9 +100,15 @@ bool Browser::isShortCircuitFormatFile(const fs::path &p)
[p](auto e) { return extensionMatches(p, e); });
}

void Browser::addRootPathForDeviceView(const fs::path &p)
void Browser::addRootPathForDeviceView(const fs::path &p, bool indexed)
{
browserDb.addDeviceLocation(p);
browserDb.addRemoveDeviceLocation(p, true);
browserDb.waitForJobsOutstandingComplete(100);
}

void Browser::removeRootPathForDeviceView(const fs::path &p)
{
browserDb.addRemoveDeviceLocation(p, false);
browserDb.waitForJobsOutstandingComplete(100);
}
} // namespace scxt::browser
10 changes: 6 additions & 4 deletions src/browser/browser.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,13 @@ struct Browser
* Filesystem Views: Very simple. The Browser gives you a set of
* filesystem roots and allows you to add your own roots to the
* browser (which will be persisted system wide). This is safe to call
* from any thread other than the sql thread but is really intended
* to be called from the UI thread.
* from any non realtime thread other than the sql thread but is really intended
* to be called from the serialization thread.
*/
std::vector<std::pair<fs::path, std::string>> getRootPathsForDeviceView() const;
void addRootPathForDeviceView(const fs::path &);
using indexedRootPath_t = std::tuple<fs::path, std::string, bool>;
std::vector<indexedRootPath_t> getRootPathsForDeviceView() const;
void addRootPathForDeviceView(const fs::path &, bool indexed);
void removeRootPathForDeviceView(const fs::path &);

static bool isLoadableFile(const fs::path &);
static bool isLoadableSample(const fs::path &);
Expand Down
Loading

0 comments on commit 6ccc3bf

Please sign in to comment.