mirror of
https://github.com/Pixelplanet/Pixel-VR-Player.git
synced 2026-10-20 20:01:38 +02:00
Non-blocking navigation (no main-thread scan join); downloads dialog buttons + modal confirm with folder size; darker model; bigger badge
This commit is contained in:
1 parent
a1e1cbff5e
commit
587f8baebf
6 files changed
+172
-39
No files matched your search
+2
-2
@@ -28,7 +28,7 @@ void main() {
|
||||
// with plain diffuse only (no specular/rim) so there is no shimmer, while the
|
||||
// printed button labels still read.
|
||||
float diff = max(dot(N, L), 0.0);
|
||||
float amb = 0.05 + 0.05 * (N.y * 0.5 + 0.5);
|
||||
outColor = vec4(tex.rgb * (amb + 0.40 * diff) * pc.color.rgb,
|
||||
float amb = 0.03 + 0.03 * (N.y * 0.5 + 0.5);
|
||||
outColor = vec4(tex.rgb * (amb + 0.30 * diff) * pc.color.rgb,
|
||||
tex.a * pc.color.a);
|
||||
}
|
||||
+40
-15
@@ -542,12 +542,15 @@ int App::run() {
|
||||
// Downloads window: job list + Pause/Stop/Clear actions. Rows are
|
||||
// clickable (completed files play, actions run).
|
||||
std::vector<std::string> drows;
|
||||
std::vector<int> dact; // 1 pause/resume, 2 stop, 3 clear, 100+i play
|
||||
std::vector<int> dact; // 1 pause/resume, 2 stop, 3 clear, 100+i play
|
||||
std::vector<uint8_t> dkind; // 0 text, 1 button
|
||||
if (browser_.downloadActive()) {
|
||||
drows.push_back(browser_.downloadPaused() ? "[ Resume ]" : "[ Pause ]");
|
||||
drows.push_back(browser_.downloadPaused() ? "Resume" : "Pause");
|
||||
dact.push_back(1);
|
||||
drows.push_back("[ Stop ]");
|
||||
dkind.push_back(1);
|
||||
drows.push_back("Stop");
|
||||
dact.push_back(2);
|
||||
dkind.push_back(1);
|
||||
}
|
||||
{
|
||||
const auto& jobs = browser_.downloadJobs();
|
||||
@@ -563,8 +566,7 @@ int App::run() {
|
||||
nm.c_str(), pct, j.bytes / 1048576.0,
|
||||
j.total / 1048576.0);
|
||||
} else if (j.state == 1) {
|
||||
std::snprintf(buf, sizeof(buf), "%s done (trigger to play)",
|
||||
nm.c_str());
|
||||
std::snprintf(buf, sizeof(buf), "%s done", nm.c_str());
|
||||
} else if (j.state == 3) {
|
||||
std::snprintf(buf, sizeof(buf), "%s stopped", nm.c_str());
|
||||
} else {
|
||||
@@ -572,17 +574,21 @@ int App::run() {
|
||||
}
|
||||
drows.push_back(buf);
|
||||
dact.push_back(j.state == 1 ? (100 + static_cast<int>(i)) : 0);
|
||||
dkind.push_back(0);
|
||||
}
|
||||
}
|
||||
drows.push_back("[ Clear finished ]");
|
||||
drows.push_back("Clear finished");
|
||||
dact.push_back(3);
|
||||
dkind.push_back(1);
|
||||
|
||||
const bool dlWin = downloadsWindow_ && !browser_.isOpen() &&
|
||||
!settings_.isOpen();
|
||||
!settings_.isOpen() && pendingDl_.empty();
|
||||
renderer_.setDownloadsModal(false);
|
||||
renderer_.setDownloads(dlWin, "Downloads", drows,
|
||||
browser_.downloadActive()
|
||||
? browser_.downloadText()
|
||||
: "Trigger: select B: close");
|
||||
: "Point + Trigger to select B: close",
|
||||
dkind);
|
||||
if (dlWin && in.select) {
|
||||
const int hov = renderer_.downloadsHovered();
|
||||
if (hov >= 0 && hov < static_cast<int>(dact.size())) {
|
||||
@@ -607,23 +613,41 @@ int App::run() {
|
||||
}
|
||||
}
|
||||
|
||||
// Folder-download confirmation overlay (overrides the downloads panel).
|
||||
// Folder-download confirmation: a modal with the size and two buttons.
|
||||
if (!pendingDl_.empty()) {
|
||||
bool confirmNow = false;
|
||||
if (pendingDlFresh_) {
|
||||
pendingDlFresh_ = false; // ignore the press that opened it
|
||||
} else if (in.togglePlay) {
|
||||
browser_.startDownload(pendingDl_, pendingDlName_, true);
|
||||
pendingDl_.clear();
|
||||
confirmNow = true;
|
||||
} else if (in.browserToggle) {
|
||||
pendingDl_.clear();
|
||||
}
|
||||
if (!pendingDl_.empty()) {
|
||||
std::vector<std::string> crows{
|
||||
"Folder: " + pendingDlName_,
|
||||
"Download this folder and everything in it?"};
|
||||
char sz[96];
|
||||
std::snprintf(sz, sizeof(sz), "Size: %.2f GB%s",
|
||||
browser_.measuredSize() / 1073741824.0,
|
||||
browser_.measuring() ? " (scanning...)" : "");
|
||||
std::vector<std::string> crows{"Download this folder?",
|
||||
pendingDlName_, sz, "Download",
|
||||
"Cancel"};
|
||||
std::vector<uint8_t> ckind{0, 0, 0, 2, 1};
|
||||
renderer_.setDownloadsModal(true);
|
||||
renderer_.setDownloads(true, "Confirm Download", crows,
|
||||
"A: Download B: Cancel");
|
||||
"A: Download B: Cancel", ckind);
|
||||
const int hov = renderer_.downloadsHovered();
|
||||
if (in.select && hov == 3) {
|
||||
confirmNow = true;
|
||||
} else if (in.select && hov == 4) {
|
||||
pendingDl_.clear();
|
||||
}
|
||||
}
|
||||
if (confirmNow && !pendingDl_.empty()) {
|
||||
browser_.startDownload(pendingDl_, pendingDlName_, true);
|
||||
pendingDl_.clear();
|
||||
}
|
||||
} else {
|
||||
renderer_.setDownloadsModal(false);
|
||||
}
|
||||
// Publish the browser panel first so the pointer can raycast it.
|
||||
if (browser_.isOpen()) {
|
||||
@@ -793,6 +817,7 @@ int App::run() {
|
||||
pendingDl_ = fe->path;
|
||||
pendingDlName_ = fe->label;
|
||||
pendingDlFresh_ = true;
|
||||
browser_.measureFolder(fe->path); // size for the dialog
|
||||
} else {
|
||||
browser_.downloadFocused();
|
||||
}
|
||||
|
||||
+27
-10
@@ -1601,16 +1601,16 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
const float badgeY = kBarY + kBtnHalfH * 0.55f;
|
||||
if (downloadsAllDone_) {
|
||||
const float tickBg[4] = {0.10f, 0.65f, 0.25f, 1.0f};
|
||||
drawUiElement(vpPanel, badgeX, badgeY, kBtnHalfH * 0.62f,
|
||||
kBtnHalfH * 0.62f, UiShape::RoundedBox, tickBg,
|
||||
drawUiElement(vpPanel, badgeX, badgeY, kBtnHalfH * 0.85f,
|
||||
kBtnHalfH * 0.85f, UiShape::RoundedBox, tickBg,
|
||||
0.50f, 0.0f, nullptr);
|
||||
drawUiElement(vpPanel, badgeX, badgeY, kBtnHalfH * 0.44f,
|
||||
kBtnHalfH * 0.44f, UiShape::Checkmark,
|
||||
drawUiElement(vpPanel, badgeX, badgeY, kBtnHalfH * 0.62f,
|
||||
kBtnHalfH * 0.62f, UiShape::Checkmark,
|
||||
iconCol);
|
||||
} else {
|
||||
const float dotCol[4] = {0.30f, 0.75f, 1.0f, 1.0f};
|
||||
drawUiElement(vpPanel, badgeX, badgeY, kBtnHalfH * 0.34f,
|
||||
kBtnHalfH * 0.34f, UiShape::Cursor, dotCol);
|
||||
drawUiElement(vpPanel, badgeX, badgeY, kBtnHalfH * 0.45f,
|
||||
kBtnHalfH * 0.45f, UiShape::Cursor, dotCol);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1860,15 +1860,32 @@ void VulkanRenderer::renderView(uint32_t viewIndex, uint32_t imageIndex,
|
||||
const float rowH = kRowH;
|
||||
const float panelBg[4] = {0.02f, 0.03f, 0.05f, 0.94f};
|
||||
const float panelBorder[4] = {0.25f, 0.45f, 0.65f, 0.85f};
|
||||
if (downloadsModal_) { // dim backdrop so a confirmation reads as a modal
|
||||
const float backdrop[4] = {0.0f, 0.0f, 0.0f, 0.55f};
|
||||
drawUiElement(vpPanel, 0.0f, 0.0f, panelW * 1.35f, panelTop * 1.25f,
|
||||
UiShape::RoundedBox, backdrop, 0.06f, 0.0f, nullptr);
|
||||
}
|
||||
drawUiElement(vpPanel, 0.0f, 0.0f, panelW, panelTop, UiShape::RoundedBox,
|
||||
panelBg, 0.05f, 0.015f, panelBorder);
|
||||
const float hlCol[4] = {0.18f, 0.45f, 0.85f, 0.85f};
|
||||
const float btnBg[4] = {0.10f, 0.14f, 0.22f, 0.95f};
|
||||
const float btnBgHover[4] = {0.18f, 0.42f, 0.82f, 0.95f};
|
||||
const float btnBorder[4] = {0.30f, 0.45f, 0.65f, 0.80f};
|
||||
const float primBg[4] = {0.12f, 0.50f, 0.22f, 0.95f};
|
||||
for (std::size_t i = 0; i < downloadsRows_.size(); ++i) {
|
||||
if (static_cast<int>(i) == downloadsHovered_) {
|
||||
const float ry = panelTop - 0.2f - static_cast<float>(i) * rowH;
|
||||
const float ry = panelTop - 0.2f - static_cast<float>(i) * rowH;
|
||||
const uint8_t kind =
|
||||
(i < downloadsKinds_.size()) ? downloadsKinds_[i] : 0;
|
||||
const bool hovered = (static_cast<int>(i) == downloadsHovered_);
|
||||
if (kind != 0) {
|
||||
const float* bg = hovered ? btnBgHover : (kind == 2 ? primBg : btnBg);
|
||||
drawUiElement(vpPanel, 0.0f, ry + rowH * 0.30f, panelW - 0.06f,
|
||||
rowH * 0.50f, UiShape::RoundedBox, bg, 0.25f, 0.03f,
|
||||
btnBorder);
|
||||
} else if (hovered) {
|
||||
drawUiElement(vpPanel, 0.0f, ry + rowH * 0.28f, panelW - 0.04f,
|
||||
rowH * 0.44f, UiShape::RoundedBox, hlCol, 0.20f,
|
||||
0.0f, nullptr);
|
||||
rowH * 0.44f, UiShape::RoundedBox, hlCol, 0.20f, 0.0f,
|
||||
nullptr);
|
||||
}
|
||||
}
|
||||
vkCmdBindPipeline(commandBuffer_, VK_PIPELINE_BIND_POINT_GRAPHICS, textPipeline_);
|
||||
|
||||
@@ -119,12 +119,16 @@ public:
|
||||
// shown while downloads run (and briefly after) with the browser closed.
|
||||
void setDownloads(bool visible, const std::string& title,
|
||||
const std::vector<std::string>& rows,
|
||||
const std::string& status) {
|
||||
const std::string& status,
|
||||
const std::vector<uint8_t>& kinds = {}) {
|
||||
downloadsVisible_ = visible;
|
||||
downloadsTitle_ = title;
|
||||
downloadsRows_ = rows;
|
||||
downloadsStatus_ = status;
|
||||
downloadsKinds_ = kinds; // 0 = item text, 1 = button, 2 = primary button
|
||||
}
|
||||
// Draws a stronger modal backdrop behind the panel (for confirmations).
|
||||
void setDownloadsModal(bool modal) { downloadsModal_ = modal; }
|
||||
// The downloads-window row under the pointer, or -1 if none.
|
||||
int downloadsHovered() const { return downloadsHovered_; }
|
||||
|
||||
@@ -292,6 +296,8 @@ private:
|
||||
std::string downloadsStatus_;
|
||||
bool downloadsActive_ = false;
|
||||
bool downloadsAllDone_ = false;
|
||||
bool downloadsModal_ = false;
|
||||
std::vector<uint8_t> downloadsKinds_;
|
||||
int downloadsHovered_ = -1;
|
||||
|
||||
// Flat-screen placement in the app reference space (updated by recenter and
|
||||
|
||||
+81
-9
@@ -267,6 +267,15 @@ void Browser::scanSmbInto(const std::string& uri, ScanResult& out) {
|
||||
// Starts a background scan so SMB discovery (NetBIOS + mDNS, seconds) and large
|
||||
// directories never freeze the render loop. poll() publishes the result.
|
||||
void Browser::beginScan(const std::string& location, bool isSmb) {
|
||||
if (scanning_.load()) {
|
||||
// A scan is already running (possibly waiting on the SMB lock held by a
|
||||
// download). Defer this request rather than joining on the main thread,
|
||||
// which would freeze the UI until the download's read returns.
|
||||
pendingScanLoc_ = location;
|
||||
pendingScanSmb_ = isSmb;
|
||||
hasPendingScan_ = true;
|
||||
return;
|
||||
}
|
||||
if (scanThread_.joinable()) scanThread_.join(); // previous scan has finished
|
||||
scanReady_.store(false);
|
||||
scanning_.store(true);
|
||||
@@ -360,6 +369,7 @@ const Browser::Entry* Browser::focused() const {
|
||||
Browser::~Browser() {
|
||||
if (scanThread_.joinable()) scanThread_.join();
|
||||
if (dlThread_.joinable()) dlThread_.join();
|
||||
if (measureThread_.joinable()) measureThread_.join();
|
||||
}
|
||||
|
||||
void Browser::startDownload(const std::string& uri, const std::string& nameIn,
|
||||
@@ -711,19 +721,81 @@ void Browser::clearFinished() {
|
||||
status_ = "Cleared finished downloads";
|
||||
}
|
||||
|
||||
void Browser::measureFolder(const std::string& uri) {
|
||||
#ifdef PIXELVR_HAVE_SMB
|
||||
if (measuring_.load()) return;
|
||||
if (measureThread_.joinable()) measureThread_.join();
|
||||
measuredSize_.store(0);
|
||||
measuring_.store(true);
|
||||
measureThread_ = std::thread([this, uri]() {
|
||||
long long total = 0;
|
||||
std::function<void(const std::string&)> sum = [&](const std::string& src) {
|
||||
int d = -1;
|
||||
{
|
||||
std::lock_guard<std::mutex> smb(smbGlobalLock());
|
||||
d = smbc_opendir(src.c_str());
|
||||
}
|
||||
if (d < 0) return;
|
||||
for (;;) {
|
||||
std::string nm;
|
||||
int typ = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> smb(smbGlobalLock());
|
||||
struct smbc_dirent* de = smbc_readdir(d);
|
||||
if (de == nullptr) break;
|
||||
nm = de->name;
|
||||
typ = de->smbc_type;
|
||||
}
|
||||
if (nm.empty() || nm == "." || nm == "..") continue;
|
||||
std::string s = src;
|
||||
if (!s.empty() && s.back() != '/') s += '/';
|
||||
s += nm;
|
||||
if (typ == SMBC_DIR) {
|
||||
sum(s);
|
||||
} else {
|
||||
struct stat st{};
|
||||
{
|
||||
std::lock_guard<std::mutex> smb(smbGlobalLock());
|
||||
smbc_stat(s.c_str(), &st);
|
||||
}
|
||||
total += static_cast<long long>(st.st_size);
|
||||
measuredSize_.store(total); // live-updating estimate
|
||||
}
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> smb(smbGlobalLock());
|
||||
smbc_closedir(d);
|
||||
}
|
||||
};
|
||||
sum(uri);
|
||||
measuredSize_.store(total);
|
||||
measuring_.store(false);
|
||||
});
|
||||
#else
|
||||
(void)uri;
|
||||
#endif
|
||||
}
|
||||
|
||||
Browser::Result Browser::poll(std::string& playPath) {
|
||||
// Publish a finished async directory scan (SMB discovery / large dirs).
|
||||
if (scanReady_.exchange(false)) {
|
||||
if (scanThread_.joinable()) scanThread_.join();
|
||||
std::lock_guard<std::mutex> lk(scanMutex_);
|
||||
entries_ = std::move(scanResult_.entries);
|
||||
title_ = std::move(scanResult_.title);
|
||||
status_ = std::move(scanResult_.status);
|
||||
location_ = scanResult_.location;
|
||||
inSmb_ = scanResult_.inSmb;
|
||||
selected_ = 0;
|
||||
lastLocation_ = location_; // remember for the next open()
|
||||
lastInSmb_ = inSmb_;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(scanMutex_);
|
||||
entries_ = std::move(scanResult_.entries);
|
||||
title_ = std::move(scanResult_.title);
|
||||
status_ = std::move(scanResult_.status);
|
||||
location_ = scanResult_.location;
|
||||
inSmb_ = scanResult_.inSmb;
|
||||
selected_ = 0;
|
||||
lastLocation_ = location_; // remember for the next open()
|
||||
lastInSmb_ = inSmb_;
|
||||
}
|
||||
// Run a navigation the user requested while this scan was busy.
|
||||
if (hasPendingScan_) {
|
||||
hasPendingScan_ = false;
|
||||
beginScan(pendingScanLoc_, pendingScanSmb_);
|
||||
}
|
||||
}
|
||||
if (dlActive_.load()) {
|
||||
const long long b = dlBytes_.load();
|
||||
|
||||
+15
-2
@@ -99,6 +99,11 @@ public:
|
||||
// Removes finished/stopped/failed jobs (deleting any partial files).
|
||||
void clearFinished();
|
||||
|
||||
// Background size probe for a folder (shown on the download confirmation).
|
||||
void measureFolder(const std::string& uri);
|
||||
bool measuring() const { return measuring_.load(); }
|
||||
long long measuredSize() const { return measuredSize_.load(); }
|
||||
|
||||
~Browser();
|
||||
Browser(const Browser&) = delete;
|
||||
Browser& operator=(const Browser&) = delete;
|
||||
@@ -114,6 +119,11 @@ private:
|
||||
};
|
||||
void beginScan(const std::string& location, bool isSmb); // async navigate
|
||||
void runScan(std::string location, bool isSmb); // worker body
|
||||
// Navigation requested while a scan was already running (deferred so the
|
||||
// main thread never blocks joining a scan stuck on the SMB lock).
|
||||
std::string pendingScanLoc_;
|
||||
bool pendingScanSmb_ = false;
|
||||
bool hasPendingScan_ = false;
|
||||
void scanLocalInto(const std::string& dir, ScanResult& out);
|
||||
void scanSmbInto(const std::string& uri, ScanResult& out);
|
||||
void startDownloadFile(const std::string& uri, const std::string& local);
|
||||
@@ -133,8 +143,7 @@ private:
|
||||
// Async directory scan (SMB discovery / large dirs) off the main thread.
|
||||
std::thread scanThread_;
|
||||
std::atomic<bool> scanning_{false};
|
||||
std::atomic<bool> scanReady_{false}; // results waiting to publish
|
||||
std::mutex scanMutex_; // guards scanResult_
|
||||
std::atomic<bool> scanReady_{false}; // results waiting to publish std::mutex scanMutex_; // guards scanResult_
|
||||
ScanResult scanResult_;
|
||||
|
||||
// Async SMB download.
|
||||
@@ -152,6 +161,10 @@ private:
|
||||
std::atomic<bool> dlPaused_{false};
|
||||
std::atomic<bool> dlAbort_{false};
|
||||
std::atomic<bool> dlStopped_{false};
|
||||
// Folder size probe (background).
|
||||
std::thread measureThread_;
|
||||
std::atomic<bool> measuring_{false};
|
||||
std::atomic<long long> measuredSize_{0};
|
||||
double dlSpeed_ = 0.0; // EMA bytes/sec for the status readout
|
||||
long long speedBytes_ = 0;
|
||||
std::chrono::steady_clock::time_point speedTime_{};
|
||||
|
||||
Reference in new issue
Block a user