diff --git a/src/components/panel.rs b/src/components/panel.rs index 5873e74..2fc4bbb 100644 --- a/src/components/panel.rs +++ b/src/components/panel.rs @@ -21,9 +21,36 @@ impl Component for Panel { let mut radio = use_radio(AppChannel::Tabs); let focus = Focus::new_for_id(self.panel_id); - let mut dimensions = use_state(|| (0.0, 0.0)); + let mut cell_size = use_state(Size2D::zero); + let mut terminal_area = use_state(Area::zero); + let mut is_pressed = use_state(|| false); let mut click_origin = use_state(|| None::<(usize, usize)>); + // Convert a global cursor point into fractional terminal cell + // coordinates, clamped to the terminal area so drags can continue + // outside the viewport. + let to_cell = move |global: CursorPoint| -> Option<(f32, f32)> { + let cell = cell_size.read().to_f64(); + if cell.is_empty() { + return None; + } + let area = terminal_area.read().to_f64(); + let local_x = + (global.x - area.min_x()).clamp(0.0, (area.width() - cell.width).max(0.0)); + let local_y = + (global.y - area.min_y()).clamp(0.0, (area.height() - cell.height).max(0.0)); + Some(( + (local_y / cell.height) as f32, + (local_x / cell.width) as f32, + )) + }; + + let to_button = |button: Option| match button { + Some(MouseButton::Middle) => TerminalMouseButton::Middle, + Some(MouseButton::Right) => TerminalMouseButton::Right, + _ => TerminalMouseButton::Left, + }; + let (is_active, has_multiple_panels) = { let state = radio.read(); let tab = state.tabs.iter().find(|t| t.id == self.tab_id).unwrap(); @@ -124,73 +151,71 @@ impl Component for Panel { }) .child( Terminal::new(handle.clone()) - .background(bg_color) + .background(bg_color) .font_size(font_size) - .on_measured(move |(char_width, line_height)| { - dimensions.set((char_width, line_height)); - }) + .on_measured(move |(char_width, line_height)| cell_size.set(Size2D::new(char_width, line_height))) + .on_sized(move |event: Event| terminal_area.set(event.area)) .on_mouse_down({ let handle = handle.clone(); - move |e: Event| { + move |event: Event| { focus.request_focus(); - radio.write_channel(AppChannel::Tabs).tabs.iter_mut().find(|t| t.id == tab_id).unwrap().active_panel = panel_id; - let (char_width, line_height) = *dimensions.read(); - let col = (e.element_location.x / char_width as f64).floor() as f32; - let row = (e.element_location.y / line_height as f64).floor() as f32; - click_origin.set(Some((row as usize, col as usize))); - let button = match e.button { - Some(MouseButton::Middle) => TerminalMouseButton::Middle, - Some(MouseButton::Right) => TerminalMouseButton::Right, - _ => TerminalMouseButton::Left, - }; - let selection_type = match EventsCombos::pressed(e.element_location) - { - PressEventType::Double => SelectionType::Semantic, - PressEventType::Triple => SelectionType::Lines, - _ => SelectionType::Simple, - }; - handle.mouse_down(row, col, button, selection_type); + radio.write_channel(AppChannel::Tabs).tabs.iter_mut() + .find(|tab| tab.id == tab_id).unwrap().active_panel = panel_id; + if let Some((row, col)) = to_cell(event.global_location) { + is_pressed.set(true); + click_origin.set(Some((row as usize, col as usize))); + let selection_type = + match EventsCombos::pressed(event.element_location) { + PressEventType::Double => SelectionType::Semantic, + PressEventType::Triple => SelectionType::Lines, + _ => SelectionType::Simple, + }; + handle.mouse_down(row, col, to_button(event.button), selection_type); + } } }) - .on_mouse_move({ + .on_global_pointer_move({ let handle = handle.clone(); - move |e: Event| { - let (char_width, line_height) = *dimensions.read(); - let col = (e.element_location.x / char_width as f64).floor() as f32; - let row = (e.element_location.y / line_height as f64).floor() as f32; - handle.mouse_move(row, col); + move |event: Event| { + let global = event.global_location(); + if !terminal_area.read().to_f64().contains(global) && !*is_pressed.read() { + return; + } + if let Some((row, col)) = to_cell(global) { + handle.mouse_move(row, col); + } } }) - .on_mouse_up({ + .on_global_pointer_press({ let handle = handle.clone(); - move |e: Event| { - let (char_width, line_height) = *dimensions.read(); - let col = (e.element_location.x / char_width as f64).floor() as f32; - let row = (e.element_location.y / line_height as f64).floor() as f32; - let button = match e.button { - Some(MouseButton::Middle) => TerminalMouseButton::Middle, - Some(MouseButton::Right) => TerminalMouseButton::Right, - _ => TerminalMouseButton::Left, - }; - handle.mouse_up(row, col, button); + move |event: Event| { + if !*is_pressed.read() { + return; + } + is_pressed.set(false); let origin = *click_origin.read(); click_origin.set(None); - if button == TerminalMouseButton::Left - && origin == Some((row as usize, col as usize)) - && let Some(url) = handle.hyperlink_at(row, col) - { - let _ = open::that(url); + match to_cell(event.global_location()) { + Some((row, col)) => { + let button = to_button(event.button()); + handle.mouse_up(row, col, button); + if button == TerminalMouseButton::Left + && origin == Some((row as usize, col as usize)) + && let Some(url) = handle.hyperlink_at(row, col) + { + let _ = open::that(url); + } + } + None => handle.release(), } } }) .on_wheel({ let handle = handle.clone(); - move |e: Event| { - let (char_width, line_height) = *dimensions.read(); - let (mouse_x, mouse_y) = e.element_location.to_tuple(); - let col = (mouse_x / char_width as f64).floor() as f32; - let row = (mouse_y / line_height as f64).floor() as f32; - handle.wheel(e.delta_y, row, col); + move |event: Event| { + if let Some((row, col)) = to_cell(event.global_location) { + handle.wheel(event.delta_y, row, col); + } } }), )