use super::*;
use ratatui::crossterm::event::{MouseButton, MouseEvent, MouseEventKind};
#[derive(Default, Clone, Copy)]
pub(super) struct Geom {
pub(super) relw: usize,
pub(super) gw: usize,
pub(super) ghw: usize,
pub(super) half: usize,
pub(super) ctw: usize,
pub(super) picker: Option<PickerGeom>,
}
#[derive(Clone, Copy)]
pub(super) struct PickerGeom {
pub(super) list: Rect,
pub(super) off: usize,
}
pub(crate) fn sideways(m: &MouseEvent) -> MouseEventKind {
let held = m.modifiers.intersects(KeyModifiers::SHIFT | KeyModifiers::ALT | KeyModifiers::CONTROL);
match m.kind {
MouseEventKind::ScrollDown if held => MouseEventKind::ScrollRight,
MouseEventKind::ScrollUp if held => MouseEventKind::ScrollLeft,
k => k,
}
}
impl App {
pub(super) fn mouse(&mut self, m: MouseEvent) {
let (x, y) = (m.column as usize, m.row as usize);
if x < self.files_w && self.picker.is_none() && self.popup.is_none() {
match m.kind {
MouseEventKind::Down(MouseButton::Left) => self.click_file(y, m.modifiers.contains(KeyModifiers::SHIFT)),
MouseEventKind::ScrollDown => self.scroll_files(3),
MouseEventKind::ScrollUp => self.scroll_files(-3),
_ => {}
}
return;
}
let x = if self.picker.is_some() || self.popup.is_some() { x } else { x.saturating_sub(self.files_w) };
let code = self.picker.is_none() && self.popup.is_none();
let kind = if code { sideways(&m) } else { m.kind };
match kind {
MouseEventKind::ScrollLeft | MouseEventKind::ScrollRight if code => {
let Some((row, col, _)) = self.at_screen(x, y) else { return };
if self.scroller(row).is_none() || kind == MouseEventKind::ScrollRight && self.col_of(row, self.len(row)) <= self.hscroll(row) + 1 && self.hscroll(row) > 0 {
return;
}
self.select = false;
self.goto(row, col);
self.hnudge = if kind == MouseEventKind::ScrollRight { 8 } else { -8 };
}
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => {
let down = m.kind == MouseEventKind::ScrollDown;
if let Some((_, _, scroll)) = &mut self.popup {
*scroll = if down { scroll.saturating_add(3) } else { scroll.saturating_sub(3) };
} else if self.picker.is_some() {
let n = self.matches().len();
if let Some(p) = &mut self.picker {
p.sel = if down { (p.sel + 1).min(n.saturating_sub(1)) } else { p.sel.saturating_sub(1) };
}
} else {
let d = if down { 3 } else { -3 };
let over_pane = self.margin_active && x > self.geom.relw + self.geom.gw + self.geom.ctw;
if !over_pane || self.comments_beyond(down) {
_ = self.scroll_code(d);
}
}
}
MouseEventKind::Down(MouseButton::Left) => {
if self.picker.is_some() {
return self.click_picker(y);
}
if self.popup.is_some() {
return (self.popup, self.help) = (None, None); }
let pane = self.geom.relw + self.geom.gw + self.geom.ctw;
if self.pin && y == 0 && self.margin_active && x >= pane {
if let Some((b, ..)) = self.pane_tabs().into_iter().find(|t| t.2.contains(&(x - pane))) {
self.bar_to(b);
}
return;
}
let Some((row, col, gutter)) = self.at_screen(x, y) else { return };
let first_of_thread = self.rows[row].thread.is_some_and(|t| row == 0 || self.rows[row - 1].thread != Some(t));
self.select = false;
self.goto(row, col);
match (gutter, self.rows[row].line.kind) {
(true, Kind::File) if !(row == 0 && self.pin) => self.toggle_collapse(), (true, Kind::Thread) if first_of_thread => self.toggle_thread(),
_ => {}
}
}
MouseEventKind::Drag(MouseButton::Left) if self.picker.is_none() && self.popup.is_none() => {
if let Some((row, col, _)) = self.at_screen(x, y) {
self.head = Pos { row, col: col.min(self.len(row).saturating_sub(1)) };
self.want_col = self.head.col;
}
}
_ => {}
}
}
fn click_picker(&mut self, y: usize) {
let Some(g) = self.geom.picker else { return };
let (top, h) = (g.list.y as usize, g.list.height as usize);
if y < top || y >= top + h {
return self.picker = None; }
let Some(p) = &mut self.picker else { return };
let Some(i) = p.lines.get(g.off + y - top).copied().flatten() else { return };
match p.sel == i {
true => self.picker_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)),
false => p.sel = i,
}
}
pub(super) fn pane_x(&self) -> usize {
let g = self.geom;
g.relw + g.gw + g.ctw + 1 + self.pane_pad() + self.pane_gw()
}
pub(super) fn right_x(&self) -> usize {
let g = self.geom;
g.relw + g.half + 1 + g.ghw
}
pub(super) fn at_screen(&self, x: usize, y: usize) -> Option<(usize, usize, bool)> {
let g = self.geom;
if self.pin && y == 0 {
let start = g.relw + g.gw; return Some((0, x.saturating_sub(start), x < start));
}
let y = y - self.pin as usize;
if y >= self.height {
return None; }
let d = self.top + y;
let (row, start) = match *self.drows.get(d)? {
DRow::Full(r) => (r, g.relw + g.gw),
DRow::Pair(l, r) if x < g.relw + g.half => (l.or(r)?, g.relw + g.ghw),
DRow::Pair(l, r) => (r.or(l)?, self.right_x()),
DRow::Margin(_, _, Some(note)) if x > g.relw + g.gw + g.ctw => (note, self.pane_x()),
DRow::Margin(l, r, _) if self.split && l.or(r).is_some_and(|c| self.rows[c].line.kind.is_code()) => {
match x < g.relw + g.half {
true => (l.or(r)?, g.relw + g.ghw),
false => (r.or(l)?, self.right_x()),
}
}
DRow::Margin(l, r, note) => (l.or(r).or(note)?, g.relw + g.gw),
};
Some((row, self.char_at(row, self.hscroll(row) + x.saturating_sub(start + self.rows[row].guide_w())), x < start))
}
}