feat(ui): cross-platform folder/file dialogs, path presets, and default ui launch
This commit is contained in:
parent
899cb884cd
commit
ca98d44e3b
2 changed files with 122 additions and 44 deletions
|
|
@ -138,6 +138,9 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
vrec::overlay_egui::run_egui_overlay();
|
||||
return Ok(());
|
||||
}
|
||||
"--hotkeys" | "--background" => {
|
||||
// fall through to hotkey loop
|
||||
}
|
||||
"--record" | "--toggle" => {
|
||||
return handle_toggle_recording();
|
||||
}
|
||||
|
|
@ -177,8 +180,9 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
"--help" | "-h" => {
|
||||
println!("vrec-ui - UI Overlay and Hotkey listener for vrec");
|
||||
println!("Usage:");
|
||||
println!(" vrec-ui Run global hotkey manager in background");
|
||||
println!(" vrec-ui --menu Open overlay menu");
|
||||
println!(" vrec-ui Open the interactive overlay UI menu (default)");
|
||||
println!(" vrec-ui --menu Open the interactive overlay UI menu");
|
||||
println!(" vrec-ui --hotkeys Run global hotkey manager in background");
|
||||
println!(" vrec-ui --status Query current daemon status");
|
||||
println!(" vrec-ui --save Save instant replay and show notification");
|
||||
println!(" vrec-ui --record Toggle normal recording on/off (with notification)");
|
||||
|
|
@ -191,6 +195,10 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
ensure_daemon_running();
|
||||
vrec::overlay_egui::run_egui_overlay();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("Starting vrec UI/Hotkey process...");
|
||||
|
|
|
|||
|
|
@ -200,6 +200,96 @@ impl VrecOverlayApp {
|
|||
}
|
||||
}
|
||||
|
||||
// Cross-platform helper to reveal or open directories
|
||||
fn open_folder(path: &std::path::Path) {
|
||||
let p = path.to_path_buf();
|
||||
std::thread::spawn(move || {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let _ = std::process::Command::new("explorer").arg(&p).spawn();
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let _ = std::process::Command::new("open").arg(&p).spawn();
|
||||
}
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
|
||||
{
|
||||
let _ = std::process::Command::new("xdg-open").arg(&p).spawn();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Cross-platform helper to launch media files with default player
|
||||
fn open_file(path: &std::path::Path) {
|
||||
let p = path.to_path_buf();
|
||||
std::thread::spawn(move || {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let _ = std::process::Command::new("cmd")
|
||||
.args(["/c", "start", "", &p.to_string_lossy()])
|
||||
.spawn();
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let _ = std::process::Command::new("open").arg(&p).spawn();
|
||||
}
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
|
||||
{
|
||||
let _ = std::process::Command::new("xdg-open").arg(&p).spawn();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Cross-platform folder picker dialog
|
||||
fn pick_folder(current_dir: &str, tx: Sender<String>) {
|
||||
let cur = current_dir.to_string();
|
||||
std::thread::spawn(move || {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let script = format!(
|
||||
"[System.Reflection.Assembly]::LoadWithPartialName('System.windows.forms') | Out-Null; \
|
||||
$f = New-Object System.Windows.Forms.FolderBrowserDialog; \
|
||||
$f.SelectedPath = '{}'; \
|
||||
if ($f.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {{ Write-Host $f.SelectedPath }}",
|
||||
cur.replace('\'', "''")
|
||||
);
|
||||
if let Ok(out) = std::process::Command::new("powershell")
|
||||
.args(["-NoProfile", "-Command", &script])
|
||||
.output()
|
||||
{
|
||||
let sel = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||
if !sel.is_empty() {
|
||||
let _ = tx.send(sel);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
if let Ok(out) = std::process::Command::new("kdialog")
|
||||
.args(["--getexistingdirectory", &cur])
|
||||
.output()
|
||||
{
|
||||
let sel = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||
if !sel.is_empty() {
|
||||
let _ = tx.send(sel);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Ok(out) = std::process::Command::new("zenity")
|
||||
.args(["--file-selection", "--directory"])
|
||||
.output()
|
||||
{
|
||||
let sel = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||
if !sel.is_empty() {
|
||||
let _ = tx.send(sel);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Helper to render realistic mechanical keyboard keycap badges
|
||||
fn render_keycap(ui: &mut egui::Ui, text: &str) {
|
||||
egui::Frame::NONE
|
||||
|
|
@ -375,9 +465,7 @@ impl eframe::App for VrecOverlayApp {
|
|||
if ui.add_sized([86.0, 26.0], folder_btn).clicked() {
|
||||
let resolved = VrecConfig::expand_tilde(&self.output_dir);
|
||||
let _ = std::fs::create_dir_all(&resolved);
|
||||
std::thread::spawn(move || {
|
||||
let _ = std::process::Command::new("xdg-open").arg(resolved).spawn();
|
||||
});
|
||||
open_folder(&resolved);
|
||||
}
|
||||
|
||||
// Quick Cursor Toggle Capsule
|
||||
|
|
@ -853,9 +941,7 @@ impl eframe::App for VrecOverlayApp {
|
|||
if ui.add_sized([90.0, 24.0], open_folder_btn).clicked() {
|
||||
let resolved = VrecConfig::expand_tilde(&self.output_dir);
|
||||
let _ = std::fs::create_dir_all(&resolved);
|
||||
std::thread::spawn(move || {
|
||||
let _ = std::process::Command::new("xdg-open").arg(resolved).spawn();
|
||||
});
|
||||
open_folder(&resolved);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -936,11 +1022,8 @@ impl eframe::App for VrecOverlayApp {
|
|||
.fill(Color32::from_rgb(16, 185, 129))
|
||||
.stroke(Stroke::new(1.0_f32, Color32::from_rgb(52, 211, 153)))
|
||||
.corner_radius(CornerRadius::same(5_u8));
|
||||
let p = clip.path.clone();
|
||||
if ui.add_sized([85.0, 26.0], play_btn).clicked() {
|
||||
std::thread::spawn(move || {
|
||||
let _ = std::process::Command::new("xdg-open").arg(p).spawn();
|
||||
});
|
||||
open_file(&clip.path);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -999,28 +1082,7 @@ impl eframe::App for VrecOverlayApp {
|
|||
.corner_radius(CornerRadius::same(5_u8));
|
||||
if ui.add(browse_btn).clicked() {
|
||||
let cur = VrecConfig::expand_tilde(&self.output_dir).to_string_lossy().to_string();
|
||||
let tx = self.folder_tx.clone();
|
||||
std::thread::spawn(move || {
|
||||
if let Ok(out) = std::process::Command::new("kdialog")
|
||||
.args(["--getexistingdirectory", &cur])
|
||||
.output()
|
||||
{
|
||||
let sel = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||
if !sel.is_empty() {
|
||||
let _ = tx.send(sel);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Ok(out) = std::process::Command::new("zenity")
|
||||
.args(["--file-selection", "--directory"])
|
||||
.output()
|
||||
{
|
||||
let sel = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||
if !sel.is_empty() {
|
||||
let _ = tx.send(sel);
|
||||
}
|
||||
}
|
||||
});
|
||||
pick_folder(&cur, self.folder_tx.clone());
|
||||
}
|
||||
|
||||
let open_btn = egui::Button::new(egui::RichText::new("Open").color(Color32::from_rgb(203, 213, 225)))
|
||||
|
|
@ -1029,21 +1091,25 @@ impl eframe::App for VrecOverlayApp {
|
|||
if ui.add(open_btn).clicked() {
|
||||
let dir = VrecConfig::expand_tilde(&self.output_dir);
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
std::thread::spawn(move || {
|
||||
let _ = std::process::Command::new("xdg-open").arg(dir).spawn();
|
||||
});
|
||||
open_folder(&dir);
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new("Quick Presets:").font(FontId::proportional(10.0)).color(Color32::from_rgb(100, 116, 139)));
|
||||
let v_dir = format!("{}/Videos/vrec", std::env::var("HOME").unwrap_or_default());
|
||||
let c_dir = format!("{}/Videos/Captures", std::env::var("HOME").unwrap_or_default());
|
||||
let d_dir = format!("{}/Downloads/vrec", std::env::var("HOME").unwrap_or_default());
|
||||
if pill(ui, "~/Videos/vrec", self.output_dir == v_dir) { self.output_dir = v_dir; }
|
||||
if pill(ui, "~/Videos/Captures", self.output_dir == c_dir) { self.output_dir = c_dir; }
|
||||
if pill(ui, "~/Downloads/vrec", self.output_dir == d_dir) { self.output_dir = d_dir; }
|
||||
let v_base = dirs::video_dir().or_else(|| dirs::home_dir().map(|h| h.join("Videos")));
|
||||
let d_base = dirs::download_dir().or_else(|| dirs::home_dir().map(|h| h.join("Downloads")));
|
||||
if let Some(ref vb) = v_base {
|
||||
let v_dir = vb.join("vrec").to_string_lossy().to_string();
|
||||
let c_dir = vb.join("Captures").to_string_lossy().to_string();
|
||||
if pill(ui, "Videos/vrec", self.output_dir == v_dir) { self.output_dir = v_dir; }
|
||||
if pill(ui, "Videos/Captures", self.output_dir == c_dir) { self.output_dir = c_dir; }
|
||||
}
|
||||
if let Some(ref db) = d_base {
|
||||
let d_dir = db.join("vrec").to_string_lossy().to_string();
|
||||
if pill(ui, "Downloads/vrec", self.output_dir == d_dir) { self.output_dir = d_dir; }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1098,7 +1164,11 @@ impl eframe::App for VrecOverlayApp {
|
|||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.label(egui::RichText::new("Codec: Hardware VAAPI H.264 (NV12 Direct Buffer Sharing)").font(FontId::monospace(9.0)).color(Color32::from_rgb(100, 116, 139)));
|
||||
#[cfg(target_os = "windows")]
|
||||
let codec_info = "Codec: Hardware D3D11 / NVENC H.264 (DirectX Desktop Duplication)";
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let codec_info = "Codec: Hardware VAAPI H.264 (NV12 Direct Buffer Sharing)";
|
||||
ui.label(egui::RichText::new(codec_info).font(FontId::monospace(9.0)).color(Color32::from_rgb(100, 116, 139)));
|
||||
});
|
||||
|
||||
// Column 2: Replay Buffer & Hotkeys
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue