use super::*;
pub(super) fn mouse_to(app_state: &mut AppState, mouse_event: MouseEvent) -> bool {
if !app_state.popup_is_open() {
return handle_mouse(app_state, mouse_event);
}
if mouse_event.kind == MouseEventKind::Down(MouseButton::Left) && app_state.error.is_none() && app_state.job.is_none() {
match app_state.dialog {
Some(Dialog::Attributes(_)) => app_state.attributes_click(mouse_event.column, mouse_event.row),
Some(Dialog::Branches(_)) => app_state.click_branch(mouse_event.column, mouse_event.row),
_ => {}
}
}
if app_state.error.is_none() && app_state.job.is_none() {
let down = match mouse_event.kind {
MouseEventKind::ScrollDown => Some(true),
MouseEventKind::ScrollUp => Some(false),
_ => None,
};
match (&app_state.dialog, down) {
(Some(Dialog::Options { editing: None }), Some(down)) => app_state.options_move(down),
(Some(Dialog::Help), Some(down)) => scroll_help(app_state, if down { 3 } else { -3 }),
_ => {}
}
}
true
}
pub(super) fn handle_mouse(app_state: &mut AppState, mouse_event: MouseEvent) -> bool {
let (column, row) = (mouse_event.column, mouse_event.row);
let (viewing, editing) = (app_state.is_viewing(), app_state.is_editing());
if mouse_event.kind == MouseEventKind::Down(MouseButton::Left) && matches!(app_state.dialog, Some(Dialog::Prompt(PromptKind::GoTo, _))) {
let on_left = column < app_state.table_area_right.x;
if !app_state.path_bar_area.contains(Position::new(column, row)) || on_left != app_state.is_left_active {
app_state.dialog = None;
}
}
if mouse_event.kind == MouseEventKind::Down(MouseButton::Left)
&& app_state.dialog.is_none()
&& let Some(&(_, key)) = app_state.fkey_hits.iter().find(|(cell, _)| cell.contains(Position::new(column, row)))
{
return handle_key(app_state, KeyEvent::new(KeyCode::F(key), KeyModifiers::NONE));
}
if matches!(app_state.screen, Screen::Diff(_)) {
match mouse_event.kind {
MouseEventKind::ScrollDown => app_state.diff_scroll(3, 0),
MouseEventKind::ScrollUp => app_state.diff_scroll(-3, 0),
MouseEventKind::ScrollRight => app_state.diff_scroll(0, 4),
MouseEventKind::ScrollLeft => app_state.diff_scroll(0, -4),
_ => {}
}
return true;
}
if (viewing || editing) && scroll_bar_mouse(app_state, mouse_event.kind, column, row) {
return true;
}
match mouse_event.kind {
MouseEventKind::Down(MouseButton::Middle) if !viewing && !editing => {
if let Some((is_left, StripHit::Item(index))) = app_state.tab_at(column, row) {
app_state.close_tab(is_left, index);
}
}
MouseEventKind::Down(MouseButton::Left) => {
if editing {
handle_editor_click(app_state, column, row, false);
} else if viewing {
if app_state.viewer_shows_image() {
app_state.viewer_pan_start(column, row);
} else {
handle_viewer_click(app_state, column, row, false);
}
} else {
handle_mouse_click(app_state, column, row);
}
}
MouseEventKind::Drag(MouseButton::Left) => {
if editing {
handle_editor_click(app_state, column, row, true);
} else if viewing {
if app_state.viewer_shows_image() {
app_state.viewer_pan_to(column, row);
} else {
handle_viewer_click(app_state, column, row, true);
}
}
}
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => {
let down = mouse_event.kind == MouseEventKind::ScrollDown;
if viewing {
if down { app_state.viewer_scroll_down() } else { app_state.viewer_scroll_up() }
} else if editing {
if down { app_state.editor_scroll_down() } else { app_state.editor_scroll_up() }
} else if let Some(is_left) = app_state.drive_strip_at(column, row) {
app_state.scroll_drive_strip(is_left, down);
} else if let Some(is_left) = app_state.tab_strip_at(column, row) {
app_state.scroll_tab_strip(is_left, down);
} else if down {
move_cursor(app_state, |state, len| {
state.select(state.selected().map_or(Some(0), |i| Some((i + 1).min(len.saturating_sub(1)))));
});
} else {
move_cursor(app_state, |state, _len| {
state.select(state.selected().map_or(Some(0), |i| Some(i.saturating_sub(1))));
});
}
}
MouseEventKind::ScrollLeft => {
if viewing {
app_state.viewer_scroll_left();
} else if editing {
app_state.editor_scroll_left();
} else {
app_state.is_left_active = true;
}
}
MouseEventKind::ScrollRight => {
if viewing {
app_state.viewer_scroll_right();
} else if editing {
app_state.editor_scroll_right();
} else {
app_state.is_left_active = false;
}
}
_ => {}
}
true
}
fn scroll_bar_mouse(app_state: &mut AppState, kind: MouseEventKind, column: u16, row: u16) -> bool {
use crate::scroll_bar::BarPress;
let scroll_to = |app_state: &mut AppState, vertical: bool, offset: usize| {
if app_state.is_editing() {
app_state.editor_scroll_to(vertical, offset);
} else {
app_state.viewer_scroll_to(vertical, offset);
}
};
match kind {
MouseEventKind::Down(MouseButton::Left) => {
app_state.bar_drag = None;
let Some((bar, press)) = app_state.scroll_bars.iter().find_map(|bar| bar.press(column, row).map(|press| (*bar, press))) else {
return false;
};
match press {
BarPress::Thumb(grab) => app_state.bar_drag = Some((bar, grab)),
BarPress::Page { forward } => {
let crate::scroll_bar::Extent { offset, view, .. } = bar.extent;
scroll_to(app_state, bar.vertical, if forward { offset + view } else { offset.saturating_sub(view) });
}
}
true
}
MouseEventKind::Drag(MouseButton::Left) => {
let Some((bar, grab)) = app_state.bar_drag else {
return false;
};
scroll_to(app_state, bar.vertical, bar.drag_to(column, row, grab));
true
}
MouseEventKind::Up(MouseButton::Left) => app_state.bar_drag.take().is_some(),
_ => false,
}
}
fn handle_mouse_click(app_state: &mut AppState, column: u16, row: u16) {
if matches!(app_state.dialog, Some(Dialog::Rename(_))) {
app_state.dialog = None;
}
if let Some((is_left, hit)) = app_state.drive_at(column, row) {
match hit {
StripHit::Item(index) => {
if let Some(path) = app_state.mounts.get(index).map(|mount| mount.path.clone()) {
app_state.is_left_active = is_left;
app_state.open_dir(is_left, path, None);
}
}
StripHit::Back => app_state.scroll_drive_strip(is_left, false),
StripHit::Forward => app_state.scroll_drive_strip(is_left, true),
}
return;
}
if let Some((is_left, hit)) = app_state.tab_at(column, row) {
match hit {
StripHit::Item(index) => {
app_state.is_left_active = is_left;
app_state.select_tab(is_left, index);
}
StripHit::Back => app_state.scroll_tab_strip(is_left, false),
StripHit::Forward => app_state.scroll_tab_strip(is_left, true),
}
return;
}
if let Some(is_left) = [true, false].into_iter().find(|&is_left| app_state.branch_hits[usize::from(!is_left)].is_some_and(|cell| cell.contains(Position::new(column, row)))) {
if app_state.job.is_none() && !app_state.panel_busy(is_left) {
app_state.is_left_active = is_left;
app_state.open_branches(is_left);
}
return;
}
if app_state.path_bar_area.contains(Position::new(column, row)) {
let is_left = column < app_state.table_area_right.x;
let editing = matches!(app_state.dialog, Some(Dialog::Prompt(PromptKind::GoTo, _)));
if app_state.job.is_none() && !app_state.panel_busy(is_left) && !(editing && is_left == app_state.is_left_active) {
app_state.is_left_active = is_left;
app_state.open_go_to();
}
return;
}
let now = Instant::now();
let is_double_click = if let Some(last_time) = app_state.last_click_time {
let elapsed = now.duration_since(last_time);
elapsed < Duration::from_millis(500) && app_state.last_click_pos == (column, row)
} else {
false
};
app_state.last_click_time = Some(now);
app_state.last_click_pos = (column, row);
let position = Position::new(column, row);
let clicked_left = app_state.table_area_left.contains(position);
if !clicked_left && !app_state.table_area_right.contains(position) {
return;
}
let area = if clicked_left { app_state.table_area_left } else { app_state.table_area_right };
if app_state.panel_busy(clicked_left) {
app_state.is_left_active = clicked_left;
return;
}
if row <= area.y {
if let Some(&(_, key)) = app_state.sort_headers.iter().find(|(cell, _)| cell.contains(position)) {
app_state.sort_panels_by(key);
}
return;
}
let clicked_table_row = (row - area.y - 1) as usize;
app_state.is_left_active = clicked_left;
let children = if clicked_left { &app_state.children_left } else { &app_state.children_right };
let total = children.len();
if total == 0 {
return;
}
let start = if clicked_left { app_state.viewport_start_left } else { app_state.viewport_start_right };
let actual_index = start + clicked_table_row;
if actual_index < total {
let state = if clicked_left { &mut app_state.state_left } else { &mut app_state.state_right };
state.select(Some(actual_index));
if is_double_click {
let children = if clicked_left { &app_state.children_left } else { &app_state.children_right };
if actual_index < children.len() {
if children[actual_index].is_dir {
enter_directory_panel(app_state);
} else {
let dir = if clicked_left { &app_state.dir_left } else { &app_state.dir_right };
let file_path = children[actual_index].path_in(dir);
if let Err(e) = open_with_default(&file_path) {
app_state.display_error(format!("Cannot open file: {}", e));
}
}
}
}
}
}
fn handle_viewer_click(app_state: &mut AppState, column: u16, row: u16, extend: bool) {
let area = app_state.viewer_content_area;
if !area.contains(Position::new(column, row)) {
return;
}
if let Screen::Viewer(state) = &mut app_state.screen {
if state.from_edit {
return;
}
let position = state.position_at(state.scroll_offset + (row - area.y) as usize, (column - area.x) as usize);
state.selection = match (extend, state.selection) {
(true, Some((anchor, _))) => Some((anchor, position)),
_ => Some((position, position)),
};
}
}
fn handle_editor_click(app_state: &mut AppState, column: u16, row: u16, extend: bool) {
let area = app_state.editor_content_area;
if !area.contains(Position::new(column, row)) {
return;
}
if let Screen::Editor(state) = &mut app_state.screen {
let visual_row = (row - area.y) as usize;
let target_line = (state.scroll_offset + visual_row).min(state.lines.len().saturating_sub(1));
let visual_col = (column - area.x) as usize + state.horizontal_offset;
let line = &state.lines[target_line];
let mut char_col = 0;
let mut current_visual = 0;
for character in line.chars() {
if current_visual >= visual_col {
break;
}
current_visual += crate::utils::char_display_width(character);
char_col += 1;
}
state.cursor_line = target_line;
state.cursor_col = char_col;
state.auto_scroll = true;
if !extend {
state.selection_anchor = Some((target_line, char_col));
}
}
}