Fix nested-terminal behavior: track size from frame/resize events
- Add App::term_size, updated from the drawn frame area and Event::Resize instead of polling crossterm::terminal::size() in hit-testing and dialog positioning (correct inside TUI-WM/tmux) - Handle Event::Resize explicitly - Fix can_paste tautology (|| true) - Drop unused import Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
12
src/app.rs
12
src/app.rs
@@ -1,6 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::config::{AppliesTo, Config, CustomMenuItem};
|
||||
use crate::config::{AppliesTo, Config};
|
||||
use crate::file_ops::{self, FileEntry};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -107,6 +107,11 @@ pub struct App {
|
||||
pub settings_hover_button: Option<u8>,
|
||||
// Terminal capabilities
|
||||
pub unicode_support: bool,
|
||||
/// Senast kända terminalstorlek (kolumner, rader). Uppdateras från
|
||||
/// frame-arean vid varje draw och från Event::Resize — pollar aldrig
|
||||
/// terminalen, vilket gör hit-testing korrekt även i nästlade
|
||||
/// terminaler (t.ex. inne i TUI-WM).
|
||||
pub term_size: (u16, u16),
|
||||
}
|
||||
|
||||
// Which field inside the settings editor is being edited
|
||||
@@ -181,6 +186,7 @@ impl App {
|
||||
settings_custom_selected: 0,
|
||||
settings_hover_button: None,
|
||||
unicode_support: detect_unicode_support(),
|
||||
term_size: crossterm::terminal::size().unwrap_or((80, 24)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +429,9 @@ impl App {
|
||||
pub fn build_context_menu(&self, x: u16, y: u16, is_sidebar_fav: bool, fav_path: Option<String>) -> ContextMenu {
|
||||
let has_selection = !self.selected_indices.is_empty();
|
||||
let has_clipboard = self.clipboard.is_some();
|
||||
let can_paste = has_clipboard && (self.selected_target_dir().is_some() || true);
|
||||
// Inklistring går till markerad katalog om en sådan finns, annars
|
||||
// aktuell katalog — så clipboard-innehåll räcker.
|
||||
let can_paste = has_clipboard;
|
||||
|
||||
// Determine if selected items are files/dirs for custom item filtering
|
||||
let selected_is_dir = self.selected_indices.iter().all(|&i| {
|
||||
|
||||
@@ -32,6 +32,10 @@ fn handle_event(app: &mut App, ev: Event) -> bool {
|
||||
handle_mouse(app, mouse);
|
||||
false
|
||||
}
|
||||
Event::Resize(w, h) => {
|
||||
app.term_size = (w, h);
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -744,7 +748,7 @@ fn handle_left_click(app: &mut App, col: u16, row: u16) {
|
||||
// Check if click lands in the submenu (only visible when NewSubmenu is highlighted)
|
||||
let ns_idx = m.items.iter().position(|i| matches!(i, ContextMenuItem::NewSubmenu));
|
||||
let in_sub = if let Some(ns_pos) = ns_idx {
|
||||
let (term_w, _) = crossterm::terminal::size().unwrap_or((120, 40));
|
||||
let (term_w, _) = app.term_size;
|
||||
let sub_x = (mx + MENU_W).min(term_w.saturating_sub(SUB_W));
|
||||
let sub_y = my + 1 + ns_pos as u16;
|
||||
col >= sub_x && col < sub_x + SUB_W
|
||||
@@ -800,7 +804,7 @@ fn handle_left_click(app: &mut App, col: u16, row: u16) {
|
||||
if row == gear_row && col >= gear_col && col < gear_col + 3 {
|
||||
// If settings is already open and we click inside the dialog – handled by settings_click
|
||||
if app.dialog == DialogMode::Settings {
|
||||
let dlg = ui::settings_dialog_rect(sb.x + sb.width);
|
||||
let dlg = ui::settings_dialog_rect(sb.x + sb.width, app.term_size.1);
|
||||
settings_click(app, col, row, dlg);
|
||||
} else {
|
||||
app.dialog = DialogMode::Settings;
|
||||
@@ -810,7 +814,7 @@ fn handle_left_click(app: &mut App, col: u16, row: u16) {
|
||||
|
||||
// If settings dialog is open, route all clicks into it
|
||||
if app.dialog == DialogMode::Settings {
|
||||
let dlg = ui::settings_dialog_rect(sb.x + sb.width);
|
||||
let dlg = ui::settings_dialog_rect(sb.x + sb.width, app.term_size.1);
|
||||
settings_click(app, col, row, dlg);
|
||||
return;
|
||||
}
|
||||
@@ -920,7 +924,7 @@ fn handle_mouse_move(app: &mut App, col: u16, row: u16) {
|
||||
// Settings hover: highlight rows when mouse moves over the dialog
|
||||
if app.dialog == DialogMode::Settings {
|
||||
let layout = ui::compute_layout(app);
|
||||
let dlg = ui::settings_dialog_rect(layout.status_bar.x + layout.status_bar.width);
|
||||
let dlg = ui::settings_dialog_rect(layout.status_bar.x + layout.status_bar.width, app.term_size.1);
|
||||
if col > dlg.x && col < dlg.x + dlg.width.saturating_sub(1)
|
||||
&& row > dlg.y && row < dlg.y + dlg.height.saturating_sub(1)
|
||||
{
|
||||
@@ -983,7 +987,7 @@ fn handle_mouse_move(app: &mut App, col: u16, row: u16) {
|
||||
// When NewSubmenu is highlighted, check if the mouse moves into the flyout
|
||||
if let Some(ns_pos) = menu.items.iter().position(|i| matches!(i, ContextMenuItem::NewSubmenu)) {
|
||||
if menu.selected == ns_pos {
|
||||
let (term_w, _) = crossterm::terminal::size().unwrap_or((120, 40));
|
||||
let (term_w, _) = app.term_size;
|
||||
let sub_x = (mx + MENU_W).min(term_w.saturating_sub(SUB_W));
|
||||
let sub_y = my + 1 + ns_pos as u16;
|
||||
|
||||
|
||||
14
src/ui.rs
14
src/ui.rs
@@ -56,9 +56,11 @@ pub struct LayoutAreas {
|
||||
pub path_input: Rect,
|
||||
}
|
||||
|
||||
/// Compute the layout areas for hit testing (without a frame)
|
||||
pub fn compute_layout(_app: &App) -> LayoutAreas {
|
||||
let (width, height) = crossterm::terminal::size().unwrap_or((120, 40));
|
||||
/// Compute the layout areas for hit testing (without a frame).
|
||||
/// Uses the last size seen by draw() instead of querying the terminal,
|
||||
/// so it stays correct inside nested terminals.
|
||||
pub fn compute_layout(app: &App) -> LayoutAreas {
|
||||
let (width, height) = app.term_size;
|
||||
let full = Rect::new(0, 0, width, height);
|
||||
split_layout(full)
|
||||
}
|
||||
@@ -97,6 +99,7 @@ fn split_layout(area: Rect) -> LayoutAreas {
|
||||
|
||||
pub fn draw(f: &mut Frame, app: &mut App) {
|
||||
let area = f.area();
|
||||
app.term_size = (area.width, area.height);
|
||||
let layout = split_layout(area);
|
||||
|
||||
draw_sidebar(f, app, layout.sidebar);
|
||||
@@ -644,17 +647,16 @@ fn draw_context_menu(f: &mut Frame, app: &App, menu: crate::app::ContextMenu) {
|
||||
}
|
||||
|
||||
/// Compute the settings dialog rect anchored to the bottom-right corner.
|
||||
pub fn settings_dialog_rect(right_edge: u16) -> ratatui::layout::Rect {
|
||||
pub fn settings_dialog_rect(right_edge: u16, term_h: u16) -> ratatui::layout::Rect {
|
||||
let dlg_width: u16 = 72;
|
||||
let dlg_height: u16 = 26;
|
||||
let x = right_edge.saturating_sub(dlg_width + 1);
|
||||
let (_, term_h) = crossterm::terminal::size().unwrap_or((120, 40));
|
||||
let y = term_h.saturating_sub(dlg_height + 3);
|
||||
Rect::new(x, y, dlg_width, dlg_height)
|
||||
}
|
||||
|
||||
fn draw_settings(f: &mut Frame, app: &App, right_edge: u16) {
|
||||
let area = settings_dialog_rect(right_edge);
|
||||
let area = settings_dialog_rect(right_edge, f.area().height);
|
||||
let term_area = f.area();
|
||||
let area = Rect::new(
|
||||
area.x.min(term_area.width.saturating_sub(area.width)),
|
||||
|
||||
Reference in New Issue
Block a user