diff --git a/CHANGELOG.md b/CHANGELOG.md index 54c5ebbe..b5abe418 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Font fallback on Windows - Support for Fontconfig embolden and matrix options - Opt-out compilation flag `winpty` to disable WinPTY support +- Scrolling during selection when mouse is at top/bottom of window ### Changed diff --git a/alacritty/src/config/monitor.rs b/alacritty/src/config/monitor.rs index d91b2e4b..42603c7e 100644 --- a/alacritty/src/config/monitor.rs +++ b/alacritty/src/config/monitor.rs @@ -4,10 +4,9 @@ use std::time::Duration; use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; -use alacritty_terminal::event::{Event, EventListener}; use alacritty_terminal::util; -use crate::event::EventProxy; +use crate::event::{Event, EventProxy}; pub struct Monitor { _thread: ::std::thread::JoinHandle<()>, diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs index 71c98559..d61a5bbd 100644 --- a/alacritty/src/display.rs +++ b/alacritty/src/display.rs @@ -23,7 +23,7 @@ use font::set_font_smoothing; use font::{self, Rasterize}; use alacritty_terminal::config::{Font, StartupMode}; -use alacritty_terminal::event::{Event, OnResize}; +use alacritty_terminal::event::OnResize; use alacritty_terminal::index::Line; use alacritty_terminal::message_bar::MessageBuffer; use alacritty_terminal::meter::Meter; @@ -119,7 +119,7 @@ pub struct Display { } impl Display { - pub fn new(config: &Config, event_loop: &EventLoop) -> Result { + pub fn new(config: &Config, event_loop: &EventLoop) -> Result { // Guess DPR based on first monitor. let estimated_dpr = event_loop.available_monitors().next().map(|m| m.scale_factor()).unwrap_or(1.); diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 4e2a0578..084ebe1e 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -30,7 +30,7 @@ use font::{self, Size}; use alacritty_terminal::config::Font; use alacritty_terminal::config::LOG_TARGET_CONFIG; use alacritty_terminal::event::OnResize; -use alacritty_terminal::event::{Event, EventListener, Notify}; +use alacritty_terminal::event::{Event as TerminalEvent, EventListener, Notify}; use alacritty_terminal::grid::Scroll; use alacritty_terminal::index::{Column, Line, Point, Side}; use alacritty_terminal::message_bar::{Message, MessageBuffer}; @@ -40,7 +40,7 @@ use alacritty_terminal::term::cell::Cell; use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode}; #[cfg(not(windows))] use alacritty_terminal::tty; -use alacritty_terminal::util::{limit, start_daemon}; +use alacritty_terminal::util::start_daemon; use crate::cli::Options; use crate::clipboard::Clipboard; @@ -48,9 +48,32 @@ use crate::config; use crate::config::Config; use crate::display::Display; use crate::input::{self, ActionContext as _, FONT_SIZE_STEP}; +use crate::scheduler::Scheduler; use crate::url::{Url, Urls}; use crate::window::Window; +/// Events dispatched through the UI event loop. +#[derive(Debug, Clone)] +pub enum Event { + TerminalEvent(TerminalEvent), + DPRChanged(f64, (u32, u32)), + Scroll(Scroll), + ConfigReload(PathBuf), + Message(Message), +} + +impl From for GlutinEvent<'_, Event> { + fn from(event: Event) -> Self { + GlutinEvent::UserEvent(event) + } +} + +impl From for Event { + fn from(event: TerminalEvent) -> Self { + Event::TerminalEvent(event) + } +} + #[derive(Default, Clone, Debug, PartialEq)] pub struct DisplayUpdate { pub dimensions: Option>, @@ -80,6 +103,7 @@ pub struct ActionContext<'a, N, T> { pub config: &'a mut Config, pub event_loop: &'a EventLoopWindowTarget, pub urls: &'a Urls, + pub scheduler: &'a mut Scheduler, font_size: &'a mut Size, } @@ -248,6 +272,25 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon } } + /// Spawn URL launcher when clicking on URLs. + fn launch_url(&self, url: Url) { + if self.mouse.block_url_launcher { + return; + } + + if let Some(ref launcher) = self.config.ui_config.mouse.url.launcher { + let mut args = launcher.args().to_vec(); + let start = self.terminal.visible_to_buffer(url.start()); + let end = self.terminal.visible_to_buffer(url.end()); + args.push(self.terminal.bounds_to_string(start, end)); + + match start_daemon(launcher.program(), &args) { + Ok(_) => debug!("Launched {} with args {:?}", launcher.program(), args), + Err(_) => warn!("Unable to launch {} with args {:?}", launcher.program(), args), + } + } + } + fn change_font_size(&mut self, delta: f32) { *self.font_size = max(*self.font_size + delta, Size::new(FONT_SIZE_STEP)); let font = self.config.font.clone().with_size(*self.font_size); @@ -282,27 +325,12 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon self.urls } - fn clipboard(&mut self) -> &mut Clipboard { + fn clipboard_mut(&mut self) -> &mut Clipboard { self.clipboard } - /// Spawn URL launcher when clicking on URLs. - fn launch_url(&self, url: Url) { - if self.mouse.block_url_launcher { - return; - } - - if let Some(ref launcher) = self.config.ui_config.mouse.url.launcher { - let mut args = launcher.args().to_vec(); - let start = self.terminal.visible_to_buffer(url.start()); - let end = self.terminal.visible_to_buffer(url.end()); - args.push(self.terminal.bounds_to_string(start, end)); - - match start_daemon(launcher.program(), &args) { - Ok(_) => debug!("Launched {} with args {:?}", launcher.program(), args), - Err(_) => warn!("Unable to launch {} with args {:?}", launcher.program(), args), - } - } + fn scheduler_mut(&mut self) -> &mut Scheduler { + self.scheduler } } @@ -369,7 +397,7 @@ pub struct Processor { message_buffer: MessageBuffer, display: Display, font_size: Size, - event_queue: Vec>, + event_queue: Vec>, } impl Processor { @@ -432,6 +460,8 @@ impl Processor { where T: EventListener, { + let mut scheduler = Scheduler::new(); + event_loop.run_return(|event, event_loop, control_flow| { if self.config.debug.print_events { info!("glutin event: {:?}", event); @@ -444,13 +474,16 @@ impl Processor { match event { // Check for shutdown. - GlutinEvent::UserEvent(Event::Exit) => { + GlutinEvent::UserEvent(Event::TerminalEvent(TerminalEvent::Exit)) => { *control_flow = ControlFlow::Exit; return; }, // Process events. GlutinEvent::RedrawEventsCleared => { - *control_flow = ControlFlow::Wait; + *control_flow = match scheduler.update(&mut self.event_queue) { + Some(instant) => ControlFlow::WaitUntil(instant), + None => ControlFlow::Wait, + }; if self.event_queue_empty() { return; @@ -463,8 +496,7 @@ impl Processor { } => { *control_flow = ControlFlow::Poll; let size = (new_inner_size.width, new_inner_size.height); - let event = GlutinEvent::UserEvent(Event::DPRChanged(scale_factor, size)); - self.event_queue.push(event); + self.event_queue.push(Event::DPRChanged(scale_factor, size).into()); return; }, // Transmute to extend lifetime, which exists only for `ScaleFactorChanged` event. @@ -495,6 +527,7 @@ impl Processor { font_size: &mut self.font_size, config: &mut self.config, urls: &self.display.urls, + scheduler: &mut scheduler, event_loop, }; let mut processor = input::Processor::new(context, &self.display.highlighted_url); @@ -529,7 +562,8 @@ impl Processor { // Request immediate re-draw if visual bell animation is not finished yet. if !terminal.visual_bell.completed() { - self.event_queue.push(GlutinEvent::UserEvent(Event::Wakeup)); + let event: Event = TerminalEvent::Wakeup.into(); + self.event_queue.push(event.into()); } // Redraw screen. @@ -571,26 +605,29 @@ impl Processor { processor.ctx.size_info.dpr = scale_factor; processor.ctx.terminal.dirty = true; }, - Event::Title(title) => processor.ctx.window.set_title(&title), - Event::Wakeup => processor.ctx.terminal.dirty = true, - Event::Urgent => { - processor.ctx.window.set_urgent(!processor.ctx.terminal.is_focused) - }, - Event::ConfigReload(path) => Self::reload_config(&path, processor), Event::Message(message) => { processor.ctx.message_buffer.push(message); processor.ctx.display_update_pending.message_buffer = true; processor.ctx.terminal.dirty = true; }, - Event::ClipboardStore(clipboard_type, content) => { - processor.ctx.clipboard.store(clipboard_type, content); + Event::ConfigReload(path) => Self::reload_config(&path, processor), + Event::Scroll(scroll) => processor.ctx.scroll(scroll), + Event::TerminalEvent(event) => match event { + TerminalEvent::Title(title) => processor.ctx.window.set_title(&title), + TerminalEvent::Wakeup => processor.ctx.terminal.dirty = true, + TerminalEvent::Urgent => { + processor.ctx.window.set_urgent(!processor.ctx.terminal.is_focused) + }, + TerminalEvent::ClipboardStore(clipboard_type, content) => { + processor.ctx.clipboard.store(clipboard_type, content); + }, + TerminalEvent::ClipboardLoad(clipboard_type, format) => { + let text = format(processor.ctx.clipboard.load(clipboard_type).as_str()); + processor.ctx.write_to_pty(text.into_bytes()); + }, + TerminalEvent::MouseCursorDirty => processor.reset_mouse_cursor(), + TerminalEvent::Exit => (), }, - Event::ClipboardLoad(clipboard_type, format) => { - let text = format(processor.ctx.clipboard.load(clipboard_type).as_str()); - processor.ctx.write_to_pty(text.into_bytes()); - }, - Event::MouseCursorDirty => processor.reset_mouse_cursor(), - Event::Exit => (), }, GlutinEvent::RedrawRequested(_) => processor.ctx.terminal.dirty = true, GlutinEvent::WindowEvent { event, window_id, .. } => { @@ -624,12 +661,8 @@ impl Processor { processor.modifiers_input(modifiers) }, WindowEvent::CursorMoved { position, .. } => { - let (x, y) = position.into(); - let x = limit(x, 0, processor.ctx.size_info.width as i32); - let y = limit(y, 0, processor.ctx.size_info.height as i32); - processor.ctx.window.set_mouse_visible(true); - processor.mouse_moved(x as usize, y as usize); + processor.mouse_moved(position); }, WindowEvent::MouseWheel { delta, phase, .. } => { processor.ctx.window.set_mouse_visible(true); @@ -796,10 +829,15 @@ impl EventProxy { pub fn new(proxy: EventLoopProxy) -> Self { EventProxy(proxy) } -} -impl EventListener for EventProxy { - fn send_event(&self, event: Event) { + /// Send an event to the event loop. + pub fn send_event(&self, event: Event) { let _ = self.0.send_event(event); } } + +impl EventListener for EventProxy { + fn send_event(&self, event: TerminalEvent) { + let _ = self.0.send_event(Event::TerminalEvent(event)); + } +} diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 2025f108..d43bb26f 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -6,12 +6,13 @@ //! determine what to do when a non-modifier key is pressed. use std::borrow::Cow; -use std::cmp::{min, Ordering}; +use std::cmp::{max, min, Ordering}; use std::marker::PhantomData; -use std::time::Instant; +use std::time::{Duration, Instant}; use log::{debug, trace, warn}; +use glutin::dpi::PhysicalPosition; use glutin::event::{ ElementState, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase, }; @@ -21,7 +22,7 @@ use glutin::platform::macos::EventLoopWindowTargetExtMacOS; use glutin::window::CursorIcon; use alacritty_terminal::ansi::{ClearMode, Handler}; -use alacritty_terminal::event::{Event, EventListener}; +use alacritty_terminal::event::EventListener; use alacritty_terminal::grid::Scroll; use alacritty_terminal::index::{Column, Line, Point, Side}; use alacritty_terminal::message_bar::{self, Message}; @@ -33,13 +34,23 @@ use alacritty_terminal::vi_mode::ViMotion; use crate::clipboard::Clipboard; use crate::config::{Action, Binding, Config, Key, ViAction}; -use crate::event::{ClickState, Mouse}; +use crate::event::{ClickState, Event, Mouse}; +use crate::scheduler::{Scheduler, TimerId}; use crate::url::{Url, Urls}; use crate::window::Window; /// Font size change interval. pub const FONT_SIZE_STEP: f32 = 0.5; +/// Interval for mouse scrolling during selection outside of the boundaries. +const SELECTION_SCROLLING_INTERVAL: Duration = Duration::from_millis(15); + +/// Minimum number of pixels at the bottom/top where selection scrolling is performed. +const MIN_SELECTION_SCROLLING_HEIGHT: f64 = 5.; + +/// Number of pixels for increasing the selection scrolling speed factor by one. +const SELECTION_SCROLLING_STEP: f64 = 20.; + /// Processes input from glutin. /// /// An escape sequence may be emitted in case specific keys or key combinations @@ -77,10 +88,11 @@ pub trait ActionContext { fn message(&self) -> Option<&Message>; fn config(&self) -> &Config; fn event_loop(&self) -> &EventLoopWindowTarget; - fn clipboard(&mut self) -> &mut Clipboard; fn urls(&self) -> &Urls; fn launch_url(&self, url: Url); fn mouse_mode(&self) -> bool; + fn clipboard_mut(&mut self) -> &mut Clipboard; + fn scheduler_mut(&mut self) -> &mut Scheduler; } trait Execute { @@ -128,11 +140,11 @@ impl Execute for Action { #[cfg(not(any(target_os = "macos", windows)))] Action::CopySelection => ctx.copy_selection(ClipboardType::Selection), Action::Paste => { - let text = ctx.clipboard().load(ClipboardType::Clipboard); + let text = ctx.clipboard_mut().load(ClipboardType::Clipboard); paste(ctx, &text); }, Action::PasteSelection => { - let text = ctx.clipboard().load(ClipboardType::Selection); + let text = ctx.clipboard_mut().load(ClipboardType::Selection); paste(ctx, &text); }, Action::Command(ref program) => { @@ -304,9 +316,19 @@ impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { } #[inline] - pub fn mouse_moved(&mut self, x: usize, y: usize) { + pub fn mouse_moved(&mut self, position: PhysicalPosition) { let size_info = self.ctx.size_info(); + let (x, y) = position.into(); + + let lmb_pressed = self.ctx.mouse().left_button_state == ElementState::Pressed; + if !self.ctx.selection_is_empty() && lmb_pressed { + self.update_selection_scrolling(y); + } + + let x = min(max(x, 0), size_info.width as i32 - 1) as usize; + let y = min(max(y, 0), size_info.height as i32 - 1) as usize; + self.ctx.mouse_mut().x = x; self.ctx.mouse_mut().y = y; @@ -339,9 +361,7 @@ impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { self.ctx.window_mut().set_mouse_cursor(mouse_state.into()); let last_term_line = self.ctx.terminal().grid().num_lines() - 1; - if self.ctx.mouse().left_button_state == ElementState::Pressed - && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode()) - { + if lmb_pressed && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode()) { // Treat motion over message bar like motion over the last line. let line = min(point.line, last_term_line); @@ -356,7 +376,7 @@ impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { && point.line <= last_term_line && self.ctx.terminal().mode().intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) { - if self.ctx.mouse().left_button_state == ElementState::Pressed { + if lmb_pressed { self.mouse_report(32, ElementState::Pressed); } else if self.ctx.mouse().middle_button_state == ElementState::Pressed { self.mouse_report(33, ElementState::Pressed); @@ -548,6 +568,7 @@ impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { self.ctx.launch_url(url); } + self.ctx.scheduler_mut().unschedule(TimerId::SelectionScrolling); self.copy_selection(); } @@ -870,32 +891,61 @@ impl<'a, T: EventListener, A: ActionContext> Processor<'a, T, A> { MouseState::Text } } + + /// Handle automatic scrolling when selecting above/below the window. + fn update_selection_scrolling(&mut self, mouse_y: i32) { + let size_info = self.ctx.size_info(); + let scheduler = self.ctx.scheduler_mut(); + + // Scale constants by DPI. + let min_height = (MIN_SELECTION_SCROLLING_HEIGHT * size_info.dpr) as i32; + let step = (SELECTION_SCROLLING_STEP * size_info.dpr) as i32; + + // Compute the height of the scrolling areas. + let end_top = max(min_height, size_info.padding_y as i32); + let height_bottom = max(min_height, size_info.padding_bottom() as i32); + let start_bottom = size_info.height as i32 - height_bottom; + + // Get distance from closest window boundary. + let delta = if mouse_y < end_top { + end_top - mouse_y + step + } else if mouse_y >= start_bottom { + start_bottom - mouse_y - step + } else { + scheduler.unschedule(TimerId::SelectionScrolling); + return; + }; + + // Scale number of lines scrolled based on distance to boundary. + let delta = delta as isize / step as isize; + let event = Event::Scroll(Scroll::Lines(delta)); + + // Schedule event. + match scheduler.get_mut(TimerId::SelectionScrolling) { + Some(timer) => timer.event = event.into(), + None => { + scheduler.schedule( + event.into(), + SELECTION_SCROLLING_INTERVAL, + true, + TimerId::SelectionScrolling, + ); + }, + } + } } #[cfg(test)] mod tests { - use std::borrow::Cow; - use std::time::Duration; + use super::*; - use glutin::event::{ - ElementState, Event, ModifiersState, MouseButton, VirtualKeyCode, WindowEvent, - }; - use glutin::event_loop::EventLoopWindowTarget; + use glutin::event::{Event as GlutinEvent, VirtualKeyCode, WindowEvent}; - use alacritty_terminal::event::{Event as TerminalEvent, EventListener}; - use alacritty_terminal::grid::Scroll; - use alacritty_terminal::index::{Point, Side}; - use alacritty_terminal::message_bar::{Message, MessageBuffer}; - use alacritty_terminal::selection::{Selection, SelectionType}; - use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode}; + use alacritty_terminal::event::Event as TerminalEvent; + use alacritty_terminal::message_bar::MessageBuffer; + use alacritty_terminal::selection::Selection; - use crate::clipboard::Clipboard; - use crate::config::{ClickHandler, Config}; - use crate::event::{ClickState, Mouse}; - use crate::url::{Url, Urls}; - use crate::window::Window; - - use super::{Action, Binding, Processor}; + use crate::config::ClickHandler; const KEY: VirtualKeyCode = VirtualKeyCode::Key0; @@ -1014,7 +1064,11 @@ mod tests { self.config } - fn event_loop(&self) -> &EventLoopWindowTarget { + fn clipboard_mut(&mut self) -> &mut Clipboard { + self.clipboard + } + + fn event_loop(&self) -> &EventLoopWindowTarget { unimplemented!(); } @@ -1022,11 +1076,11 @@ mod tests { unimplemented!(); } - fn clipboard(&mut self) -> &mut Clipboard { - self.clipboard + fn launch_url(&self, _: Url) { + unimplemented!(); } - fn launch_url(&self, _: Url) { + fn scheduler_mut (&mut self) -> &mut Scheduler { unimplemented!(); } } @@ -1089,8 +1143,8 @@ mod tests { let mut processor = Processor::new(context, &None); - let event: Event::<'_, TerminalEvent> = $input; - if let Event::WindowEvent { + let event: GlutinEvent::<'_, TerminalEvent> = $input; + if let GlutinEvent::WindowEvent { event: WindowEvent::MouseInput { state, button, @@ -1130,7 +1184,7 @@ mod tests { name: single_click, initial_state: ClickState::None, initial_button: MouseButton::Other(0), - input: Event::WindowEvent { + input: GlutinEvent::WindowEvent { event: WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left, @@ -1146,7 +1200,7 @@ mod tests { name: single_right_click, initial_state: ClickState::None, initial_button: MouseButton::Other(0), - input: Event::WindowEvent { + input: GlutinEvent::WindowEvent { event: WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Right, @@ -1162,7 +1216,7 @@ mod tests { name: single_middle_click, initial_state: ClickState::None, initial_button: MouseButton::Other(0), - input: Event::WindowEvent { + input: GlutinEvent::WindowEvent { event: WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Middle, @@ -1178,7 +1232,7 @@ mod tests { name: double_click, initial_state: ClickState::Click, initial_button: MouseButton::Left, - input: Event::WindowEvent { + input: GlutinEvent::WindowEvent { event: WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left, @@ -1194,7 +1248,7 @@ mod tests { name: triple_click, initial_state: ClickState::DoubleClick, initial_button: MouseButton::Left, - input: Event::WindowEvent { + input: GlutinEvent::WindowEvent { event: WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left, @@ -1210,7 +1264,7 @@ mod tests { name: multi_click_separate_buttons, initial_state: ClickState::DoubleClick, initial_button: MouseButton::Left, - input: Event::WindowEvent { + input: GlutinEvent::WindowEvent { event: WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Right, diff --git a/alacritty/src/logging.rs b/alacritty/src/logging.rs index 1e56f3cb..a6fcf3fb 100644 --- a/alacritty/src/logging.rs +++ b/alacritty/src/logging.rs @@ -15,11 +15,11 @@ use std::sync::{Arc, Mutex}; use glutin::event_loop::EventLoopProxy; use log::{self, Level}; -use alacritty_terminal::event::Event; use alacritty_terminal::message_bar::Message; use alacritty_terminal::term::color; use crate::cli::Options; +use crate::event::Event; const ALACRITTY_LOG_ENV: &str = "ALACRITTY_LOG"; diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs index 9587aec3..ab4acaa7 100644 --- a/alacritty/src/main.rs +++ b/alacritty/src/main.rs @@ -23,7 +23,6 @@ use log::{error, info}; #[cfg(windows)] use winapi::um::wincon::{AttachConsole, FreeConsole, ATTACH_PARENT_PROCESS}; -use alacritty_terminal::event::Event; use alacritty_terminal::event_loop::{self, EventLoop, Msg}; #[cfg(target_os = "macos")] use alacritty_terminal::locale; @@ -42,6 +41,7 @@ mod event; mod input; mod logging; mod renderer; +mod scheduler; mod url; mod window; @@ -57,7 +57,7 @@ use crate::cli::Options; use crate::config::monitor::Monitor; use crate::config::Config; use crate::display::Display; -use crate::event::{EventProxy, Processor}; +use crate::event::{Event, EventProxy, Processor}; fn main() { panic::attach_handler(); diff --git a/alacritty/src/scheduler.rs b/alacritty/src/scheduler.rs new file mode 100644 index 00000000..a6559acc --- /dev/null +++ b/alacritty/src/scheduler.rs @@ -0,0 +1,102 @@ +//! Scheduler for emitting events at a specific time in the future. + +use std::collections::VecDeque; +use std::time::{Duration, Instant}; + +use glutin::event::Event as GlutinEvent; + +use crate::event::Event as AlacrittyEvent; + +type Event = GlutinEvent<'static, AlacrittyEvent>; + +/// Scheduler tracking all pending timers. +pub struct Scheduler { + timers: VecDeque, +} + +impl Default for Scheduler { + fn default() -> Self { + Self { timers: VecDeque::new() } + } +} + +impl Scheduler { + pub fn new() -> Self { + Self::default() + } + + /// Process all pending timers. + /// + /// If there are still timers pending after all ready events have been processed, the closest + /// pending deadline will be returned. + pub fn update(&mut self, event_queue: &mut Vec) -> Option { + let now = Instant::now(); + while !self.timers.is_empty() && self.timers[0].deadline <= now { + if let Some(timer) = self.timers.pop_front() { + // Automatically repeat the event. + if let Some(interval) = timer.interval { + self.schedule(timer.event.clone(), interval, true, timer.id); + } + + event_queue.push(timer.event); + } + } + + self.timers.get(0).map(|timer| timer.deadline) + } + + /// Schedule a new event. + pub fn schedule( + &mut self, + event: Event, + interval: Duration, + repeat: bool, + timer_id: TimerId, + ) { + let deadline = Instant::now() + interval; + + // Get insert position in the schedule. + let mut index = self.timers.len(); + loop { + if index == 0 { + break; + } + index -= 1; + + if self.timers[index].deadline < deadline { + break; + } + } + + // Set the automatic event repeat rate. + let interval = if repeat { Some(interval) } else { None }; + + self.timers.insert(index, Timer { interval, deadline, event, id: timer_id }); + } + + /// Cancel a scheduled event. + pub fn unschedule(&mut self, id: TimerId) -> Option { + let index = self.timers.iter().position(|timer| timer.id == id)?; + self.timers.remove(index).map(|timer| timer.event) + } + + /// Access a staged event by ID. + pub fn get_mut(&mut self, id: TimerId) -> Option<&mut Timer> { + self.timers.iter_mut().find(|timer| timer.id == id) + } +} + +/// ID uniquely identifying a timer. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum TimerId { + SelectionScrolling, +} + +/// Event scheduled to be emitted at a specific time. +pub struct Timer { + pub deadline: Instant, + pub event: Event, + + interval: Option, + id: TimerId, +} diff --git a/alacritty/src/window.rs b/alacritty/src/window.rs index 22e84780..4275f859 100644 --- a/alacritty/src/window.rs +++ b/alacritty/src/window.rs @@ -34,7 +34,6 @@ use glutin::{self, ContextBuilder, PossiblyCurrent, WindowedContext}; use winapi::shared::minwindef::WORD; use alacritty_terminal::config::{Decorations, StartupMode, WindowConfig}; -use alacritty_terminal::event::Event; #[cfg(not(windows))] use alacritty_terminal::term::{SizeInfo, Term}; @@ -103,9 +102,9 @@ impl From for Error { } } -fn create_gl_window( +fn create_gl_window( mut window: WindowBuilder, - event_loop: &EventLoop, + event_loop: &EventLoop, srgb: bool, vsync: bool, dimensions: Option>, @@ -147,8 +146,8 @@ impl Window { /// Create a new window. /// /// This creates a window and fully initializes a window. - pub fn new( - event_loop: &EventLoop, + pub fn new( + event_loop: &EventLoop, config: &Config, size: Option>, #[cfg(not(any(target_os = "macos", windows)))] wayland_event_queue: Option<&EventQueue>, diff --git a/alacritty_terminal/src/event.rs b/alacritty_terminal/src/event.rs index 20b105cd..296e3bb0 100644 --- a/alacritty_terminal/src/event.rs +++ b/alacritty_terminal/src/event.rs @@ -1,17 +1,12 @@ use std::borrow::Cow; use std::fmt::{self, Debug, Formatter}; -use std::path::PathBuf; use std::sync::Arc; -use crate::message_bar::Message; use crate::term::{ClipboardType, SizeInfo}; #[derive(Clone)] pub enum Event { - DPRChanged(f64, (u32, u32)), - ConfigReload(PathBuf), MouseCursorDirty, - Message(Message), Title(String), ClipboardStore(ClipboardType, String), ClipboardLoad(ClipboardType, Arc String + Sync + Send + 'static>), @@ -23,10 +18,7 @@ pub enum Event { impl Debug for Event { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - Event::DPRChanged(scale, size) => write!(f, "DPRChanged({}, {:?})", scale, size), - Event::ConfigReload(path) => write!(f, "ConfigReload({:?})", path), Event::MouseCursorDirty => write!(f, "MouseCursorDirty"), - Event::Message(msg) => write!(f, "Message({:?})", msg), Event::Title(title) => write!(f, "Title({})", title), Event::ClipboardStore(ty, text) => write!(f, "ClipboardStore({:?}, {})", ty, text), Event::ClipboardLoad(ty, _) => write!(f, "ClipboardLoad({:?})", ty), diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs index 0c338fba..d5932639 100644 --- a/alacritty_terminal/src/grid/mod.rs +++ b/alacritty_terminal/src/grid/mod.rs @@ -148,7 +148,7 @@ pub struct Grid { max_scroll_limit: usize, } -#[derive(Copy, Clone)] +#[derive(Debug, Copy, Clone)] pub enum Scroll { Lines(isize), PageUp, diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index 6aac05d9..33d2e14e 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -691,13 +691,24 @@ impl SizeInfo { Column(((self.width - 2. * self.padding_x) / self.cell_width) as usize) } + #[inline] + pub fn padding_right(&self) -> usize { + (self.padding_x + (self.width - 2. * self.padding_x) % self.cell_width) as usize + } + + #[inline] + pub fn padding_bottom(&self) -> usize { + (self.padding_y + (self.height - 2. * self.padding_y) % self.cell_height) as usize + } + /// Check if coordinates are inside the terminal grid. /// /// The padding is not counted as part of the grid. + #[inline] pub fn contains_point(&self, x: usize, y: usize) -> bool { - x < (self.width - self.padding_x) as usize + x < (self.width as usize - self.padding_right()) && x >= self.padding_x as usize - && y < (self.height - self.padding_y) as usize + && y < (self.height as usize - self.padding_bottom()) && y >= self.padding_y as usize } diff --git a/alacritty_terminal/src/util.rs b/alacritty_terminal/src/util.rs index fde06966..f996359a 100644 --- a/alacritty_terminal/src/util.rs +++ b/alacritty_terminal/src/util.rs @@ -1,6 +1,6 @@ use std::ffi::OsStr; +use std::io; use std::process::{Command, Stdio}; -use std::{cmp, io}; #[cfg(not(windows))] use std::os::unix::process::CommandExt; @@ -13,22 +13,18 @@ use winapi::um::winbase::{CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW}; /// Threading utilities. pub mod thread { /// Like `thread::spawn`, but with a `name` argument. - pub fn spawn_named(name: S, f: F) -> ::std::thread::JoinHandle + pub fn spawn_named(name: S, f: F) -> std::thread::JoinHandle where F: FnOnce() -> T + Send + 'static, T: Send + 'static, S: Into, { - ::std::thread::Builder::new().name(name.into()).spawn(f).expect("thread spawn works") + std::thread::Builder::new().name(name.into()).spawn(f).expect("thread spawn works") } pub use std::thread::*; } -pub fn limit(value: T, min: T, max: T) -> T { - cmp::min(cmp::max(value, min), max) -} - #[cfg(not(windows))] pub fn start_daemon(program: &str, args: I) -> io::Result<()> where @@ -79,15 +75,3 @@ where .spawn() .map(|_| ()) } - -#[cfg(test)] -mod tests { - use super::limit; - - #[test] - fn limit_works() { - assert_eq!(10, limit(10, 0, 100)); - assert_eq!(10, limit(5, 10, 100)); - assert_eq!(100, limit(1000, 10, 100)); - } -}