use super::*;
impl App {
pub(super) fn mouse_cell(&self) -> Option<(usize, usize)> {
let s = self.session.as_ref()?;
let overlay = self.login.is_some()
|| self.save_prompt.is_some()
|| self.upload_prompt.is_some()
|| s.transfer().is_some();
if self.dialer_open || overlay {
return None;
}
let (ox, oy, w, h) = self.view_rect;
let (mx, my) = (self.mouse.0.max(0.0) as usize, self.mouse.1.max(0.0) as usize);
let (cols, rows) = (self.renderer.cols(), self.renderer.rows());
let x = (mx.saturating_sub(ox) * cols / w.max(1)).min(cols - 1);
let y = (my.saturating_sub(oy) * rows / h.max(1)).min(s.term.screen.rows() - 1);
Some((x, y))
}
fn mouse_report_at(&self) -> Option<((usize, usize), (usize, usize))> {
if self.scroll != 0 || self.mods.shift_key() {
return None;
}
let cell = self.mouse_cell()?;
let s = self.session.as_ref().filter(|s| s.state == State::Connected && s.term.mouse.tracking())?;
let (ox, oy, w, h) = self.view_rect;
let (mx, my) = (self.mouse.0.max(0.0) as usize, self.mouse.1.max(0.0) as usize);
let nx = mx.saturating_sub(ox) * self.renderer.native_width() / w.max(1);
let ny = my.saturating_sub(oy) * self.renderer.native_height() / h.max(1);
let sc = &s.term.screen;
let (pw, ph) = sc.size_px();
let text_h = self.renderer.native_height() * sc.rows() / self.renderer.rows().max(1);
let px = (nx * pw / self.renderer.native_width().max(1)).min(pw - 1);
let py = (ny * ph / text_h.max(1)).min(ph - 1);
Some((cell, (px, py)))
}
fn mouse_report(&mut self, event: badterm_core::mouse::MouseEvent) -> bool {
let Some((cell, pixel)) = self.mouse_report_at() else { return false };
let mods =
badterm_core::mouse::MouseMods { shift: false, alt: self.mods.alt_key(), ctrl: self.mods.control_key() };
let Some(session) = self.session.as_mut() else { return false };
if let Some(bytes) = session.term.mouse.report(event, cell, pixel, mods) {
session.send(&bytes);
self.mouse_reported = Some((cell, pixel));
}
true
}
pub(super) fn on_mouse_button(&mut self, button: MouseButton, state: ElementState) {
use badterm_core::mouse::MouseEvent;
let code = match button {
MouseButton::Left => Some(0),
MouseButton::Middle => Some(1),
MouseButton::Right => Some(2),
_ => None,
};
if let Some(b) = code
&& !self.dragging
{
let event = match state {
ElementState::Pressed => MouseEvent::Press(b),
ElementState::Released => MouseEvent::Release(b),
};
if self.mouse_report(event) {
self.mouse_held = (state == ElementState::Pressed).then_some(b);
return;
}
}
if button != MouseButton::Left {
return;
}
match state {
ElementState::Pressed if self.scroll == 0 && (self.rip_click() || self.igs_click()) => {}
ElementState::Pressed => {
self.selection = self.mouse_cell().map(Selection::at);
self.dragging = self.selection.is_some();
}
ElementState::Released if self.dragging => {
self.dragging = false;
if let Some(sel) = self.selection.filter(|s| !s.is_empty()) {
self.copy_selection(sel, self.mods.shift_key());
}
}
ElementState::Released => {}
}
self.request_redraw();
}
fn rip_click(&mut self) -> bool {
if self.mouse_cell().is_none() {
return false;
}
let Some(session) = self.session.as_mut().filter(|s| s.state == State::Connected) else { return false };
let Some(rip) = session.term.rip() else { return false };
let (ox, oy, w, h) = self.view_rect;
let (mx, my) = (self.mouse.0.max(0.0) as usize, self.mouse.1.max(0.0) as usize);
let x = mx.saturating_sub(ox) * self.renderer.native_width() / w.max(1);
let y = my.saturating_sub(oy) * self.renderer.native_height() / h.max(1);
let rows = session.term.screen.rows();
let Some((gx, gy)) =
self.renderer.graphics_point(x, y, rows, badterm_core::rip::WIDTH, badterm_core::rip::HEIGHT)
else {
return false;
};
let Some(text) = rip.click(gx, gy) else { return false };
session.send(&text);
true
}
fn igs_click(&mut self) -> bool {
if self.mouse_cell().is_none() {
return false;
}
let (ox, oy, w, h) = self.view_rect;
let (mx, my) = (self.mouse.0.max(0.0) as usize, self.mouse.1.max(0.0) as usize);
let x = mx.saturating_sub(ox) * self.renderer.native_width() / w.max(1);
let y = my.saturating_sub(oy) * self.renderer.native_height() / h.max(1);
let Some(session) = self.session.as_mut().filter(|s| s.state == State::Connected) else { return false };
let rows = session.term.screen.rows();
if let Some(sky) = session.term.skypix() {
let (sw, sh) = (badterm_core::skypix::WIDTH, badterm_core::skypix::HEIGHT);
let Some((gx, gy)) = self.renderer.graphics_point(x, y, rows, sw, sh) else { return false };
let text = sky.click(gx as i32, gy as i32);
session.send(&text);
return true;
}
let Some(igs) = session.term.igs_mut() else { return false };
let (iw, ih) = (igs.width(), igs.height());
let Some((gx, gy)) = self.renderer.graphics_point(x, y, rows, iw, ih) else { return false };
let Some(text) = igs.click(gx as i32, gy as i32) else { return false };
session.send(&text);
true
}
pub(super) fn on_mouse_move(&mut self, x: f64, y: f64) {
self.mouse = (x, y);
if !self.dragging
&& let Some((cell, pixel)) = self.mouse_report_at()
{
let pixels = self.session.as_ref().is_some_and(|s| s.term.mouse.pixels);
let moved = self.mouse_reported.is_none_or(|(c, p)| if pixels { p != pixel } else { c != cell });
if moved {
self.mouse_report(badterm_core::mouse::MouseEvent::Move(self.mouse_held));
}
return;
}
if self.dragging
&& let Some(cell) = self.mouse_cell()
&& let Some(sel) = self.selection.as_mut()
&& sel.head != cell
{
sel.head = cell;
self.request_redraw();
}
}
pub(super) fn copy_selection(&mut self, sel: Selection, as_ansi: bool) {
let Some(session) = &self.session else { return };
let rows = terminal_rows(session, self.scroll);
let lines = sel.lines(&rows);
let text = if as_ansi {
let opts =
AnsiOptions { ice: session.term.screen.ice_colors, unicode: true, codepage: session.term.codepage() };
String::from_utf8_lossy(&export::ansi(&lines, opts)).into_owned()
} else {
export::text_in(&lines, session.term.codepage()).trim_end_matches('\n').to_string()
};
let msg = match arboard::Clipboard::new().and_then(|mut c| c.set_text(text)) {
Ok(()) if as_ansi => "Copied as ANSI".to_string(),
Ok(()) => "Copied".to_string(),
Err(e) => format!("Copy failed: {e}"),
};
self.notify(msg);
}
pub(super) fn on_wheel(&mut self, lines: isize) {
if let Some(v) = self.art.as_mut().filter(|_| self.art_picker.is_none()) {
v.scroll(lines);
return self.request_redraw();
}
if lines != 0 && !self.dialer_open && self.mouse_report(badterm_core::mouse::MouseEvent::Wheel(lines > 0)) {
return;
}
if let Some(s) = self.session.as_ref().filter(|_| !self.dialer_open) {
self.selection = None;
let max = s.term.screen.scrollback_len() as isize;
self.scroll = (self.scroll as isize + lines).clamp(0, max) as usize;
self.request_redraw();
}
}
}