From 23d8f06c163bcd8a5b4d780562fd5ea81f0d990f Mon Sep 17 00:00:00 2001 From: mxmgorin <102797145+mxmgorin@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:41:58 +0300 Subject: [PATCH 1/2] fix(browser): the Miyoo card as a root, not the app folder --- CHANGELOG.md | 8 ++++++++ src/overlay/browser.rs | 20 ++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87180c1..3033082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- **The file browser opened inside the app's own folder on the Miyoo CFWs** and + would not leave it. `/mnt/SDCARD` was no known mount point, so the only root + left was `$HOME` — which those launchers point at the app folder for a + writable path — and a root is the one directory B cannot leave. The card is a + root now, and `$HOME` only becomes one when it sits outside every other. + ## [0.7.0] - 2026-08-24 ### Added diff --git a/src/overlay/browser.rs b/src/overlay/browser.rs index dfd9751..8d0987c 100644 --- a/src/overlay/browser.rs +++ b/src/overlay/browser.rs @@ -7,8 +7,9 @@ use std::path::{Path, PathBuf}; /// Mount points worth offering on handheld CFWs, in preference order. /// Only the ones that exist become roots; `$HOME` covers the desktop. -const ROOT_CANDIDATES: [&str; 5] = [ +const ROOT_CANDIDATES: [&str; 6] = [ "/roms", + "/mnt/SDCARD", // the Miyoo card: Onion, Allium, spruce "/mnt/mmc", "/mnt/sdcard", "/userdata/roms", @@ -408,7 +409,7 @@ fn build_roots(extra: &[String]) -> Vec { } if let Ok(home) = std::env::var("HOME") { let home = PathBuf::from(home); - if home.is_dir() && !roots.contains(&home) { + if home.is_dir() && wants_home_root(&roots, &home) { roots.push(home); } } @@ -418,6 +419,12 @@ fn build_roots(extra: &[String]) -> Vec { roots } +/// `$HOME` earns a root of its own only outside every other one: the handheld +/// launchers point it at the app folder, and a root is what B cannot leave. +fn wants_home_root(roots: &[PathBuf], home: &Path) -> bool { + !roots.iter().any(|root| home.starts_with(root)) +} + #[cfg(test)] mod tests { use super::*; @@ -752,6 +759,15 @@ mod tests { std::fs::remove_dir_all(&root).unwrap(); } + #[test] + fn home_inside_a_root_is_not_a_root_of_its_own() { + let card = PathBuf::from("/mnt/SDCARD"); + let roots = vec![card.clone()]; + assert!(!wants_home_root(&roots, &card.join("Apps/Retsend.pak"))); + assert!(wants_home_root(&roots, Path::new("/home/user"))); + assert!(wants_home_root(&[], Path::new("/home/user"))); + } + #[test] fn cursor_clamps() { let root = temp_tree(); From 2eff2d5268efde70ed7423417c73340b6ed19d72 Mon Sep 17 00:00:00 2001 From: mxmgorin <102797145+mxmgorin@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:42:38 +0300 Subject: [PATCH 2/2] feat(browser): a band behind the pinned rows --- CHANGELOG.md | 7 +++++++ src/overlay/browser.rs | 8 ++++++++ src/ui/browser.rs | 25 +++++++++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3033082..4610b9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- **Pinned rows sit in a band of their own** in the file browser, closed by a + rule where the folder's own entries start. They lead every listing but belong + to other folders, and a star alone left them reading as rows of the folder + being looked at. + ### Fixed - **The file browser opened inside the app's own folder on the Miyoo CFWs** and diff --git a/src/overlay/browser.rs b/src/overlay/browser.rs index 8d0987c..00ec1bb 100644 --- a/src/overlay/browser.rs +++ b/src/overlay/browser.rs @@ -266,6 +266,12 @@ impl FileBrowser { Some((here.len(), bytes, !all_selected)) } + /// How many rows at the top of the listing are pins. They always lead, so + /// this is also where the folder's own entries begin. + pub fn pinned_rows(&self) -> usize { + self.entries.iter().take_while(|e| e.pinned).count() + } + /// What Y acts on: the row under the cursor, or the folder being looked at /// when there is no row to point at (an empty listing). pub fn pin_target(&self) -> Option { @@ -477,6 +483,8 @@ mod tests { // The first row is the pinned one, the second the real child directory. assert!(b.entries[0].pinned); assert!(!b.entries[1].pinned); + // Where the renderer closes the pinned band. + assert_eq!(b.pinned_rows(), 1); std::fs::remove_dir_all(&root).unwrap(); } diff --git a/src/ui/browser.rs b/src/ui/browser.rs index e6d0958..3324ec7 100644 --- a/src/ui/browser.rs +++ b/src/ui/browser.rs @@ -13,6 +13,12 @@ const ENTRY_HEIGHT: f32 = 30.0; /// Below this many seconds left, the incoming request's countdown turns red. const URGENT_SECS: u32 = 10; +/// The pinned band: an accent wash faint enough to stay under the cursor's own +/// 0.30 fill, the rule closing it, and the padding it grows by past its rows. +const BAND_TINT: f32 = 0.10; +const RULE_TINT: f32 = 0.55; +const BAND_INSET: f32 = 2.0; + /// `deadline_secs` is set only while the browser is picking a destination for /// a parked incoming request: the modal (and its countdown bar) is hidden /// behind us, so the seconds left have to show up here. @@ -130,6 +136,25 @@ pub fn render( if browser.cursor < total { ui.scroll_to_rect(row_rect(browser.cursor), None); } + // Pins lead the listing but belong to other folders: a band behind + // them and a rule under the last one keep them from reading as rows + // of the folder being looked at. + let pins = browser.pinned_rows(); + if pins > 0 { + let band = row_rect(0).union(row_rect(pins - 1)); + ui.painter().rect_filled( + band.expand2(egui::vec2(0.0, BAND_INSET)), + 4.0, + theme::ACCENT.linear_multiply(BAND_TINT), + ); + if pins < total { + ui.painter().hline( + band.x_range(), + band.max.y + spacing * 0.5, + egui::Stroke::new(1.0, theme::DIM.linear_multiply(RULE_TINT)), + ); + } + } let first = (viewport.min.y / step).max(0.0) as usize; let last = ((viewport.max.y / step).ceil() as usize + 1).min(total); // Hit-tested, not sensed: virtualized rows are painted from rects,