Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 116 additions & 21 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ static FILE_INDEX: Lazy<RwLock<FileIndexData>> = Lazy::new(|| RwLock::new(FileIn
static SHOW_RECENTS: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(true));
static REFOCUS_ON_BLUR: AtomicBool = AtomicBool::new(false);
static IS_INDEXING: AtomicBool = AtomicBool::new(false);
static WINDOW_Y_PERCENT: Lazy<Mutex<u8>> = Lazy::new(|| Mutex::new(30));

const PRESET_SHORTCUTS: &[&str] = &[
"Super+Shift+.",
Expand Down Expand Up @@ -129,6 +130,39 @@ fn save_shortcut(app: &AppHandle, shortcut: &str) {
let _ = std::fs::write(&path, serde_json::to_string_pretty(&data).unwrap_or_default());
}

fn load_window_y_percent(app: &AppHandle) -> u8 {
let path = get_config_path(app);
if let Ok(content) = std::fs::read_to_string(&path) {
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&content) {
if let Some(percentage) = v.get("window_y_percent").and_then(|p| p.as_u64()) {
return (percentage as u8).clamp(5, 85);
}
}
}
30
}

fn save_window_y_percent(app: &AppHandle, percent: u8) {
let path = get_config_path(app);
let mut data = serde_json::json!({});
if let Ok(content) = std::fs::read_to_string(&path) {
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&content) {
data = v;
}
}
if let Some(obj) = data.as_object_mut() {
obj.insert("window_y_percent".to_string(), serde_json::json!(percent));
} else {
data = serde_json::json!({ "window_y_percent": percent });
}
let _ = std::fs::write(&path, serde_json::to_string_pretty(&data).unwrap_or_default());
}

/// Converts vertical percentage to physical-pixel offset from monitor top. All values are in device-pixel coordinates.
fn compute_window_y_phys(mon_height_phys: u32, y_percent: u8) -> i32 {
(mon_height_phys as f64 * (y_percent as f64 / 100.0)).round() as i32
}

fn get_file_icon_base64(path: &str) -> Option<String> {
match get_icon(path, 32) {
Ok(icon_vec) => {
Expand Down Expand Up @@ -178,23 +212,47 @@ fn show_main_window(app: &AppHandle) {
if let Some(window) = app.get_webview_window("main") {
let show_recents = *SHOW_RECENTS.lock().unwrap();

if show_recents {
let _ = window.set_size(tauri::Size::Logical(tauri::LogicalSize {
width: 800.0,
height: 400.0,
}));
let win_width = 800.0 as f64;
let win_height = if show_recents { 400.0 as f64 } else { 70.0 as f64 };

let _ = window.set_size(tauri::Size::Logical(tauri::LogicalSize {
width: win_width,
height: win_height,
}));

let target_monitor = app
.cursor_position()
.ok()
.and_then(|cursor| {
app.available_monitors().ok().and_then(|monitors| {
monitors.into_iter().find(|m| {
let pos = m.position();
let size = m.size();
let right = pos.x + size.width as i32;
let bottom = pos.y + size.height as i32;
(cursor.x as i32) >= pos.x && (cursor.x as i32) < right
&& (cursor.y as i32) >= pos.y && (cursor.y as i32) < bottom
})
})
})
.or_else(|| app.primary_monitor().ok().flatten());

if let Some(monitor) = target_monitor {
let scale = monitor.scale_factor();
let mon_pos = monitor.position();
let mon_sz = monitor.size();

// Center horizontally on this monitor; place at the user-configured % from its top.
let win_w_phys = (win_width * scale).round() as i32;
let x = mon_pos.x + ((mon_sz.width as i32 - win_w_phys) / 2).max(0);
let y_percent = *WINDOW_Y_PERCENT.lock().unwrap();
let y = mon_pos.y + compute_window_y_phys(mon_sz.height, y_percent);

let _ = window.set_position(tauri::Position::Physical(tauri::PhysicalPosition { x, y }));
} else {
let _ = window.set_size(tauri::Size::Logical(tauri::LogicalSize {
width: 800.0,
height: 70.0,
}));
let _ = window.center();
}

let _ = window.center();
let _ = window.set_position(tauri::Position::Physical(tauri::PhysicalPosition {
x: window.outer_position().unwrap().x,
y: 100,
}));
let _ = window.unminimize();
let _ = window.show();
let _ = window.set_focus();
Expand Down Expand Up @@ -898,16 +956,26 @@ fn open_url_private(app: tauri::AppHandle, url: String) {

#[tauri::command]
fn reset_window(window: tauri::Window) {
let win_width = 800.0 as f64;
let _ = window.set_size(tauri::Size::Logical(tauri::LogicalSize {
width: 800.0,
width: win_width,
height: 70.0,
}));

let _ = window.center();
let _ = window.set_position(tauri::Position::Physical(tauri::PhysicalPosition {
x: window.outer_position().unwrap().x,
y: 100,
}));
if let Ok(Some(monitor)) = window.current_monitor() {
let scale = monitor.scale_factor();
let mon_pos = monitor.position();
let mon_sz = monitor.size();

let win_w_phys = (win_width * scale).round() as i32;
let x = mon_pos.x + ((mon_sz.width as i32 - win_w_phys) / 2).max(0);
let y_percent = *WINDOW_Y_PERCENT.lock().unwrap();
let y = mon_pos.y + compute_window_y_phys(mon_sz.height, y_percent);

let _ = window.set_position(tauri::Position::Physical(tauri::PhysicalPosition { x, y }));
} else {
let _ = window.center();
}
}

#[tauri::command]
Expand All @@ -918,6 +986,30 @@ fn resize_window(window: tauri::Window, height: f64) {
}));
}

#[tauri::command]
fn get_window_y_percent() -> u8 {
*WINDOW_Y_PERCENT.lock().unwrap()
}

/// Saves percentage, updates memory, and repositions window live.
#[tauri::command]
fn set_window_y_percent(app: AppHandle, percent: u8) {
let percent = percent.clamp(5, 85);
*WINDOW_Y_PERCENT.lock().unwrap() = percent;
save_window_y_percent(&app, percent);

if let Some(window) = app.get_webview_window("main") {
if let Ok(Some(monitor)) = window.current_monitor() {
let mon_pos = monitor.position();
let y = mon_pos.y + compute_window_y_phys(monitor.size().height, percent);
let x = window.outer_position().map(|p| p.x).unwrap_or(mon_pos.x);
let _ = window.set_position(tauri::Position::Physical(
tauri::PhysicalPosition { x, y },
));
}
}
}

#[tauri::command]
fn get_available_drives() -> Vec<String> {
let mut drives = Vec::new();
Expand Down Expand Up @@ -1730,7 +1822,9 @@ fn main() {
quit_app,
close_active_window,
open_url_private,
execute_nox_command
execute_nox_command,
get_window_y_percent,
set_window_y_percent
])
.setup(|app| {
let mut has_binfile = false;
Expand Down Expand Up @@ -1829,6 +1923,7 @@ fn main() {
}
}

*WINDOW_Y_PERCENT.lock().unwrap() = load_window_y_percent(app.handle());
show_main_window(app.handle());
REFOCUS_ON_BLUR.store(true, Ordering::SeqCst);

Expand Down
6 changes: 6 additions & 0 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@
<button id="reset-pos-btn" class="btn btn-secondary btn-sm">Reset Settings</button>
</div>

<div class="settings-row">
<label class="setting-label no-margin">Vertical Position</label>
<input type="range" id="position-slider" min="5" max="85" value="30">
<span id="position-display">30% from top</span>
</div>

<div class="settings-row">
<label class="setting-label no-margin">Global Shortcut</label>
<div class="relative">
Expand Down
26 changes: 26 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const updateBtn = document.getElementById("update-btn");
const memoryDisplay = document.getElementById("memory-usage");
const helpBtn = document.getElementById("help-btn");
const searchWrapper = document.querySelector(".search-wrapper");
const positionSlider = document.getElementById("position-slider");
const positionDisplay = document.getElementById("position-display");
const loaderHtml = `
<div id="search-loader" class="loader-dots hidden">
<div class="loader-dot"></div>
Expand Down Expand Up @@ -93,6 +95,7 @@ function getSettingsFocusables() {
startupToggle.parentElement,
clearRecentsBtn,
resetPosBtn,
positionSlider,
shortcutDisplay,
updateBtn,
helpBtn,
Expand Down Expand Up @@ -238,10 +241,32 @@ resetPosBtn.onclick = async () => {
const defaultShortcut = PRESET_SHORTCUTS[0];
await applyShortcut(defaultShortcut);

await invoke("set_window_y_percent", { percent: 30 });
if (positionSlider) positionSlider.value = 30;
if (positionDisplay) positionDisplay.textContent = "30% from top";

await invoke("resize_window", { height: WINDOW_MAX_HEIGHT });
lastWindowHeight = WINDOW_MAX_HEIGHT;
};

if (positionSlider && positionDisplay) {
positionSlider.addEventListener("input", async () => {
const percentage = parseInt(positionSlider.value, 10);
positionDisplay.textContent = `${percentage}% from top`;
await invoke("set_window_y_percent", { percent: percentage });
});
}

async function initPositionSetting() {
try {
const percentage = await invoke("get_window_y_percent");
if (positionSlider) positionSlider.value = percentage;
if (positionDisplay) positionDisplay.textContent = `${percentage}% from top`;
} catch (err) {
console.error("Position setting init error:", err);
}
}

async function checkUpdates(isAuto = false) {
try {
if (!isAuto && updateBtn) {
Expand Down Expand Up @@ -1519,6 +1544,7 @@ if (startupToggle) {
}

initAutostart();
initPositionSetting();

if (helpBtn) {
helpBtn.onclick = async (e) => {
Expand Down