mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
Add mouse controls to fork popup overlay
This commit is contained in:
@@ -155,6 +155,7 @@ mod app_server_adapter;
|
||||
mod app_server_requests;
|
||||
mod loaded_threads;
|
||||
mod fork_session_overlay;
|
||||
mod fork_session_overlay_mouse;
|
||||
mod fork_session_terminal;
|
||||
mod pending_interactive_replay;
|
||||
|
||||
@@ -3909,6 +3910,7 @@ impl App {
|
||||
TuiEvent::Key(key_event) => {
|
||||
self.handle_key_event(tui, app_server, key_event).await;
|
||||
}
|
||||
TuiEvent::Mouse(_) => {}
|
||||
TuiEvent::Paste(pasted) => {
|
||||
// Many terminals convert newlines to \r when pasting (e.g., iTerm2),
|
||||
// but tui-textarea expects \n. Normalize CR to LF.
|
||||
|
||||
@@ -28,6 +28,9 @@ use crate::tui::TuiEvent;
|
||||
use crate::vt100_backend::VT100Backend;
|
||||
use crate::vt100_render::render_screen;
|
||||
|
||||
use super::fork_session_overlay_mouse::OverlayMouseAction;
|
||||
use super::fork_session_overlay_mouse::PopupDragState;
|
||||
use super::fork_session_overlay_mouse::overlay_mouse_action;
|
||||
use super::fork_session_terminal::ForkSessionTerminal;
|
||||
|
||||
const DEFAULT_POPUP_WIDTH_NUMERATOR: u16 = 2;
|
||||
@@ -60,6 +63,7 @@ pub(crate) struct ForkSessionOverlayState {
|
||||
popup: Rect,
|
||||
command_state: OverlayCommandState,
|
||||
focused_pane: OverlayFocusedPane,
|
||||
drag_state: Option<PopupDragState>,
|
||||
}
|
||||
|
||||
fn popup_size_bounds(area: Rect) -> Rect {
|
||||
@@ -209,9 +213,7 @@ fn focus_toggle_shortcut(key_event: KeyEvent) -> bool {
|
||||
|
||||
fn popup_hint(command_state: OverlayCommandState) -> Vec<Span<'static>> {
|
||||
match command_state {
|
||||
OverlayCommandState::PassThrough => {
|
||||
vec!["^O switch".cyan(), " ".into(), "ctrl+] prefix".dim()]
|
||||
}
|
||||
OverlayCommandState::PassThrough => vec!["ctrl+] prefix".dim()],
|
||||
OverlayCommandState::AwaitingPrefix => {
|
||||
vec![
|
||||
"m move".cyan(),
|
||||
@@ -259,17 +261,11 @@ fn popup_block(
|
||||
Some(code) => format!("exited {code}").red().bold(),
|
||||
None => "running".green().bold(),
|
||||
};
|
||||
let focus = match focused_pane {
|
||||
OverlayFocusedPane::Background => "background focus".cyan().bold(),
|
||||
OverlayFocusedPane::Popup => "popup focus".cyan().bold(),
|
||||
};
|
||||
let mut title = vec![
|
||||
" fork session ".bold().cyan(),
|
||||
" ".into(),
|
||||
" fork session".bold().cyan(),
|
||||
" ".into(),
|
||||
status,
|
||||
" ".into(),
|
||||
focus,
|
||||
" ".into(),
|
||||
" ".into(),
|
||||
];
|
||||
title.extend(popup_hint(command_state));
|
||||
let title = Line::from(title);
|
||||
@@ -364,13 +360,16 @@ impl App {
|
||||
popup,
|
||||
command_state: OverlayCommandState::PassThrough,
|
||||
focused_pane: OverlayFocusedPane::Popup,
|
||||
drag_state: None,
|
||||
});
|
||||
tui.set_mouse_capture_enabled(true)?;
|
||||
tui.frame_requester().schedule_frame();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn close_fork_session_overlay(&mut self, tui: &mut tui::Tui) -> Result<()> {
|
||||
self.fork_session_overlay = None;
|
||||
tui.set_mouse_capture_enabled(false)?;
|
||||
self.restore_inline_view_after_fork_overlay_close(tui)?;
|
||||
tui.frame_requester().schedule_frame();
|
||||
Ok(())
|
||||
@@ -627,6 +626,36 @@ impl App {
|
||||
}
|
||||
}
|
||||
}
|
||||
TuiEvent::Mouse(mouse_event) => {
|
||||
let viewport = tui.terminal.size()?;
|
||||
let area = Rect::new(0, 0, viewport.width, viewport.height);
|
||||
if let Some(state) = self.fork_session_overlay.as_mut() {
|
||||
match overlay_mouse_action(area, state.popup, state.drag_state, mouse_event) {
|
||||
OverlayMouseAction::Ignore => {}
|
||||
OverlayMouseAction::FocusBackground => {
|
||||
state.focused_pane = OverlayFocusedPane::Background;
|
||||
state.command_state = OverlayCommandState::PassThrough;
|
||||
state.drag_state = None;
|
||||
tui.frame_requester().schedule_frame();
|
||||
}
|
||||
OverlayMouseAction::FocusPopup(drag_state) => {
|
||||
state.focused_pane = OverlayFocusedPane::Popup;
|
||||
state.command_state = OverlayCommandState::PassThrough;
|
||||
state.drag_state = Some(drag_state);
|
||||
tui.frame_requester().schedule_frame();
|
||||
}
|
||||
OverlayMouseAction::MovePopup(popup) => {
|
||||
state.focused_pane = OverlayFocusedPane::Popup;
|
||||
state.command_state = OverlayCommandState::PassThrough;
|
||||
state.popup = popup;
|
||||
tui.frame_requester().schedule_frame();
|
||||
}
|
||||
OverlayMouseAction::EndDrag => {
|
||||
state.drag_state = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TuiEvent::Draw => {
|
||||
if self
|
||||
.fork_session_overlay
|
||||
@@ -1086,6 +1115,7 @@ ready for a fresh turn\r\n",
|
||||
popup: default_popup_rect(Rect::new(0, 0, 100, 28)),
|
||||
command_state: OverlayCommandState::PassThrough,
|
||||
focused_pane: OverlayFocusedPane::Popup,
|
||||
drag_state: None,
|
||||
});
|
||||
|
||||
let area = Rect::new(0, 0, 100, 28);
|
||||
@@ -1125,6 +1155,7 @@ ready for a fresh turn\r\n",
|
||||
popup: default_popup_rect(Rect::new(0, 0, 100, 28)),
|
||||
command_state: OverlayCommandState::PassThrough,
|
||||
focused_pane: OverlayFocusedPane::Background,
|
||||
drag_state: None,
|
||||
});
|
||||
|
||||
let area = Rect::new(0, 0, 100, 28);
|
||||
@@ -1168,6 +1199,7 @@ ready for a fresh turn\r\n",
|
||||
popup: default_popup_rect(Rect::new(0, 0, 80, 18)),
|
||||
command_state: OverlayCommandState::PassThrough,
|
||||
focused_pane: OverlayFocusedPane::Background,
|
||||
drag_state: None,
|
||||
});
|
||||
|
||||
let area = Rect::new(0, 0, 80, 18);
|
||||
|
||||
150
codex-rs/tui/src/app/fork_session_overlay_mouse.rs
Normal file
150
codex-rs/tui/src/app/fork_session_overlay_mouse.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use crossterm::event::MouseButton;
|
||||
use crossterm::event::MouseEvent;
|
||||
use crossterm::event::MouseEventKind;
|
||||
use ratatui::layout::Rect;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct PopupDragState {
|
||||
column_offset: u16,
|
||||
row_offset: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum OverlayMouseAction {
|
||||
Ignore,
|
||||
FocusBackground,
|
||||
FocusPopup(PopupDragState),
|
||||
MovePopup(Rect),
|
||||
EndDrag,
|
||||
}
|
||||
|
||||
pub(crate) fn overlay_mouse_action(
|
||||
area: Rect,
|
||||
popup: Rect,
|
||||
drag_state: Option<PopupDragState>,
|
||||
mouse_event: MouseEvent,
|
||||
) -> OverlayMouseAction {
|
||||
match mouse_event.kind {
|
||||
MouseEventKind::Down(MouseButton::Left) => {
|
||||
if popup_contains_position(popup, mouse_event.column, mouse_event.row) {
|
||||
OverlayMouseAction::FocusPopup(PopupDragState {
|
||||
column_offset: mouse_event.column.saturating_sub(popup.x),
|
||||
row_offset: mouse_event.row.saturating_sub(popup.y),
|
||||
})
|
||||
} else {
|
||||
OverlayMouseAction::FocusBackground
|
||||
}
|
||||
}
|
||||
MouseEventKind::Drag(MouseButton::Left) => {
|
||||
if let Some(drag_state) = drag_state {
|
||||
let max_x = area.right().saturating_sub(popup.width);
|
||||
let max_y = area.bottom().saturating_sub(popup.height);
|
||||
let x = mouse_event
|
||||
.column
|
||||
.saturating_sub(drag_state.column_offset)
|
||||
.clamp(area.x, max_x);
|
||||
let y = mouse_event
|
||||
.row
|
||||
.saturating_sub(drag_state.row_offset)
|
||||
.clamp(area.y, max_y);
|
||||
OverlayMouseAction::MovePopup(Rect::new(x, y, popup.width, popup.height))
|
||||
} else {
|
||||
OverlayMouseAction::Ignore
|
||||
}
|
||||
}
|
||||
MouseEventKind::Up(MouseButton::Left) => OverlayMouseAction::EndDrag,
|
||||
MouseEventKind::Down(_)
|
||||
| MouseEventKind::Up(_)
|
||||
| MouseEventKind::Drag(_)
|
||||
| MouseEventKind::Moved
|
||||
| MouseEventKind::ScrollDown
|
||||
| MouseEventKind::ScrollUp
|
||||
| MouseEventKind::ScrollLeft
|
||||
| MouseEventKind::ScrollRight => OverlayMouseAction::Ignore,
|
||||
}
|
||||
}
|
||||
|
||||
fn popup_contains_position(popup: Rect, column: u16, row: u16) -> bool {
|
||||
column >= popup.x && column < popup.right() && row >= popup.y && row < popup.bottom()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn mouse_down_outside_popup_focuses_background() {
|
||||
let popup = Rect::new(20, 8, 40, 16);
|
||||
let area = Rect::new(0, 0, 120, 40);
|
||||
let mouse_event = MouseEvent {
|
||||
kind: MouseEventKind::Down(MouseButton::Left),
|
||||
column: 4,
|
||||
row: 3,
|
||||
modifiers: crossterm::event::KeyModifiers::NONE,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
overlay_mouse_action(area, popup, None, mouse_event),
|
||||
OverlayMouseAction::FocusBackground
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_down_inside_popup_starts_drag() {
|
||||
let popup = Rect::new(20, 8, 40, 16);
|
||||
let area = Rect::new(0, 0, 120, 40);
|
||||
let mouse_event = MouseEvent {
|
||||
kind: MouseEventKind::Down(MouseButton::Left),
|
||||
column: 27,
|
||||
row: 10,
|
||||
modifiers: crossterm::event::KeyModifiers::NONE,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
overlay_mouse_action(area, popup, None, mouse_event),
|
||||
OverlayMouseAction::FocusPopup(PopupDragState {
|
||||
column_offset: 7,
|
||||
row_offset: 2,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_drag_moves_popup_and_clamps_to_viewport() {
|
||||
let area = Rect::new(0, 0, 120, 40);
|
||||
let popup = Rect::new(20, 8, 40, 16);
|
||||
let drag_state = PopupDragState {
|
||||
column_offset: 7,
|
||||
row_offset: 2,
|
||||
};
|
||||
let mouse_event = MouseEvent {
|
||||
kind: MouseEventKind::Drag(MouseButton::Left),
|
||||
column: 118,
|
||||
row: 39,
|
||||
modifiers: crossterm::event::KeyModifiers::NONE,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
overlay_mouse_action(area, popup, Some(drag_state), mouse_event),
|
||||
OverlayMouseAction::MovePopup(Rect::new(80, 24, 40, 16))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_up_ends_drag() {
|
||||
let popup = Rect::new(20, 8, 40, 16);
|
||||
let area = Rect::new(0, 0, 120, 40);
|
||||
let mouse_event = MouseEvent {
|
||||
kind: MouseEventKind::Up(MouseButton::Left),
|
||||
column: 27,
|
||||
row: 10,
|
||||
modifiers: crossterm::event::KeyModifiers::NONE,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
overlay_mouse_action(area, popup, None, mouse_event),
|
||||
OverlayMouseAction::EndDrag
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -215,6 +215,7 @@ async fn fork_session_overlay_open_from_inline_viewport_snapshot() {
|
||||
popup: default_popup_rect(Rect::new(0, 0, width, height)),
|
||||
command_state: OverlayCommandState::PassThrough,
|
||||
focused_pane: OverlayFocusedPane::Popup,
|
||||
drag_state: None,
|
||||
});
|
||||
|
||||
let mut pending_history_lines = Vec::new();
|
||||
|
||||
@@ -8,7 +8,7 @@ expression: snapshot_buffer(&buf)
|
||||
│ model: gpt-5.3-codex /model to change │
|
||||
│ directory: ~/code/codex/codex-rs/tui │
|
||||
╰─────────────────────────────────────────────╯
|
||||
╭ fork session running popup focus ^O switch ctrl+] prefix╮
|
||||
╭ fork session running ctrl+] prefix───────────────────────────╮
|
||||
│Independent Codex session │
|
||||
› background sessi│ │
|
||||
│> /tmp/worktree │
|
||||
|
||||
@@ -8,7 +8,7 @@ expression: snapshot_buffer(&buf)
|
||||
│ model: gpt-5.3-codex /model to change │
|
||||
│ directory: ~/code/codex/codex-rs/tui │
|
||||
╰─────────────────────────────────────────────╯
|
||||
╭ fork session running background focus ^O switch ctrl+] p╮
|
||||
╭ fork session running ctrl+] prefix───────────────────────────╮
|
||||
│Independent Codex session │
|
||||
› background sessi│ │
|
||||
│> /tmp/worktree │
|
||||
|
||||
@@ -96,6 +96,7 @@ pub(crate) async fn run_cwd_selection_prompt(
|
||||
if let Some(event) = events.next().await {
|
||||
match event {
|
||||
TuiEvent::Key(key_event) => screen.handle_key(key_event),
|
||||
TuiEvent::Mouse(_) => {}
|
||||
TuiEvent::Paste(_) => {}
|
||||
TuiEvent::Draw => {
|
||||
tui.draw(u16::MAX, |frame| {
|
||||
|
||||
@@ -152,6 +152,7 @@ pub(crate) async fn run_model_migration_prompt(
|
||||
if let Some(event) = events.next().await {
|
||||
match event {
|
||||
TuiEvent::Key(key_event) => screen.handle_key(key_event),
|
||||
TuiEvent::Mouse(_) => {}
|
||||
TuiEvent::Paste(_) => {}
|
||||
TuiEvent::Draw => {
|
||||
let _ = alt.tui.draw(u16::MAX, |frame| {
|
||||
|
||||
@@ -457,6 +457,7 @@ pub(crate) async fn run_onboarding_app(
|
||||
TuiEvent::Key(key_event) => {
|
||||
onboarding_screen.handle_key_event(key_event);
|
||||
}
|
||||
TuiEvent::Mouse(_) => {}
|
||||
TuiEvent::Paste(text) => {
|
||||
onboarding_screen.handle_paste(text);
|
||||
}
|
||||
|
||||
@@ -16,10 +16,13 @@ use crossterm::Command;
|
||||
use crossterm::SynchronizedUpdate;
|
||||
use crossterm::event::DisableBracketedPaste;
|
||||
use crossterm::event::DisableFocusChange;
|
||||
use crossterm::event::DisableMouseCapture;
|
||||
use crossterm::event::EnableBracketedPaste;
|
||||
use crossterm::event::EnableFocusChange;
|
||||
use crossterm::event::EnableMouseCapture;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyboardEnhancementFlags;
|
||||
use crossterm::event::MouseEvent;
|
||||
use crossterm::event::PopKeyboardEnhancementFlags;
|
||||
use crossterm::event::PushKeyboardEnhancementFlags;
|
||||
use crossterm::terminal::EnterAlternateScreen;
|
||||
@@ -129,6 +132,7 @@ fn restore_common(should_disable_raw_mode: bool) -> Result<()> {
|
||||
let _ = execute!(stdout(), PopKeyboardEnhancementFlags);
|
||||
execute!(stdout(), DisableBracketedPaste)?;
|
||||
let _ = execute!(stdout(), DisableFocusChange);
|
||||
let _ = execute!(stdout(), DisableMouseCapture);
|
||||
if should_disable_raw_mode {
|
||||
disable_raw_mode()?;
|
||||
}
|
||||
@@ -234,6 +238,7 @@ fn set_panic_hook() {
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TuiEvent {
|
||||
Key(KeyEvent),
|
||||
Mouse(MouseEvent),
|
||||
Paste(String),
|
||||
Draw,
|
||||
}
|
||||
@@ -255,6 +260,7 @@ pub struct Tui {
|
||||
notification_backend: Option<DesktopNotificationBackend>,
|
||||
// When false, enter_alt_screen() becomes a no-op (for Zellij scrollback support)
|
||||
alt_screen_enabled: bool,
|
||||
mouse_capture_enabled: bool,
|
||||
}
|
||||
|
||||
impl Tui {
|
||||
@@ -283,6 +289,7 @@ impl Tui {
|
||||
enhanced_keys_supported,
|
||||
notification_backend: Some(detect_backend(NotificationMethod::default())),
|
||||
alt_screen_enabled: true,
|
||||
mouse_capture_enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +298,11 @@ impl Tui {
|
||||
self.alt_screen_enabled = enabled;
|
||||
}
|
||||
|
||||
pub fn set_mouse_capture_enabled(&mut self, enabled: bool) -> Result<()> {
|
||||
self.mouse_capture_enabled = enabled;
|
||||
self.apply_mouse_capture_state()
|
||||
}
|
||||
|
||||
pub fn set_notification_method(&mut self, method: NotificationMethod) {
|
||||
self.notification_backend = Some(detect_backend(method));
|
||||
}
|
||||
@@ -333,6 +345,7 @@ impl Tui {
|
||||
|
||||
// Leave alt screen if active to avoid conflicts with external program `f`.
|
||||
let was_alt_screen = self.is_alt_screen_active();
|
||||
let mouse_capture_enabled = self.mouse_capture_enabled;
|
||||
if was_alt_screen {
|
||||
let _ = self.leave_alt_screen();
|
||||
}
|
||||
@@ -346,6 +359,9 @@ impl Tui {
|
||||
if let Err(err) = set_modes() {
|
||||
tracing::warn!("failed to re-enable terminal modes after external program: {err}");
|
||||
}
|
||||
if mouse_capture_enabled && let Err(err) = self.apply_mouse_capture_state() {
|
||||
tracing::warn!("failed to re-enable mouse capture after external program: {err}");
|
||||
}
|
||||
// After the external program `f` finishes, reset terminal state and flush any buffered keypresses.
|
||||
flush_terminal_input_buffer();
|
||||
|
||||
@@ -357,6 +373,15 @@ impl Tui {
|
||||
output
|
||||
}
|
||||
|
||||
fn apply_mouse_capture_state(&mut self) -> Result<()> {
|
||||
if self.mouse_capture_enabled {
|
||||
execute!(self.terminal.backend_mut(), EnableMouseCapture)?;
|
||||
} else {
|
||||
execute!(self.terminal.backend_mut(), DisableMouseCapture)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Emit a desktop notification now if the terminal is unfocused.
|
||||
/// Returns true if a notification was posted.
|
||||
pub fn notify(&mut self, message: impl AsRef<str>) -> bool {
|
||||
|
||||
@@ -172,11 +172,11 @@ impl<S: EventSource + Default + Unpin> TuiEventStream<S> {
|
||||
|
||||
/// Poll the shared crossterm stream for the next mapped `TuiEvent`.
|
||||
///
|
||||
/// This skips events we don't use (mouse events, etc.) and keeps polling until it yields
|
||||
/// This skips events we don't use and keeps polling until it yields
|
||||
/// a mapped event, hits `Pending`, or sees EOF/error. When the broker is paused, it drops
|
||||
/// the underlying stream and returns `Pending` to fully release stdin.
|
||||
pub fn poll_crossterm_event(&mut self, cx: &mut Context<'_>) -> Poll<Option<TuiEvent>> {
|
||||
// Some crossterm events map to None (e.g. FocusLost, mouse); loop so we keep polling
|
||||
// Some crossterm events map to None (e.g. FocusLost); loop so we keep polling
|
||||
// until we return a mapped event, hit Pending, or see EOF/error.
|
||||
loop {
|
||||
let poll_result = {
|
||||
@@ -233,7 +233,7 @@ impl<S: EventSource + Default + Unpin> TuiEventStream<S> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a crossterm event to a [`TuiEvent`], skipping events we don't use (mouse events, etc.).
|
||||
/// Map a crossterm event to a [`TuiEvent`], skipping events we don't use.
|
||||
fn map_crossterm_event(&mut self, event: Event) -> Option<TuiEvent> {
|
||||
match event {
|
||||
Event::Key(key_event) => {
|
||||
@@ -244,6 +244,7 @@ impl<S: EventSource + Default + Unpin> TuiEventStream<S> {
|
||||
}
|
||||
Some(TuiEvent::Key(key_event))
|
||||
}
|
||||
Event::Mouse(mouse_event) => Some(TuiEvent::Mouse(mouse_event)),
|
||||
Event::Resize(_, _) => Some(TuiEvent::Draw),
|
||||
Event::Paste(pasted) => Some(TuiEvent::Paste(pasted)),
|
||||
Event::FocusGained => {
|
||||
@@ -255,7 +256,6 @@ impl<S: EventSource + Default + Unpin> TuiEventStream<S> {
|
||||
self.terminal_focused.store(false, Ordering::Relaxed);
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,6 +297,9 @@ mod tests {
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyModifiers;
|
||||
use crossterm::event::MouseButton;
|
||||
use crossterm::event::MouseEvent;
|
||||
use crossterm::event::MouseEventKind;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
@@ -408,6 +411,26 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn mouse_event_is_forwarded() {
|
||||
let (broker, handle, _draw_tx, draw_rx, terminal_focused) = setup();
|
||||
let mut stream = make_stream(broker, draw_rx, terminal_focused);
|
||||
|
||||
let expected_mouse = MouseEvent {
|
||||
kind: MouseEventKind::Down(MouseButton::Left),
|
||||
column: 12,
|
||||
row: 7,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
};
|
||||
handle.send(Ok(Event::Mouse(expected_mouse)));
|
||||
|
||||
let next = stream.next().await.unwrap();
|
||||
match next {
|
||||
TuiEvent::Mouse(mouse) => assert_eq!(mouse, expected_mouse),
|
||||
other => panic!("expected mouse event, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn draw_and_key_events_yield_both() {
|
||||
let (broker, handle, draw_tx, draw_rx, terminal_focused) = setup();
|
||||
|
||||
@@ -56,6 +56,7 @@ pub(crate) async fn run_update_prompt_if_needed(
|
||||
if let Some(event) = events.next().await {
|
||||
match event {
|
||||
TuiEvent::Key(key_event) => screen.handle_key(key_event),
|
||||
TuiEvent::Mouse(_) => {}
|
||||
TuiEvent::Paste(_) => {}
|
||||
TuiEvent::Draw => {
|
||||
tui.draw(u16::MAX, |frame| {
|
||||
|
||||
Reference in New Issue
Block a user