Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scrollbar #7231

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3e532ce
Add non-interactive scrollbar
flash-freezing-lava Sep 11, 2023
31e6164
Reserve space in case of scrollbar mode `Always`
flash-freezing-lava Sep 16, 2023
487a53b
Fix tabs in documentation
flash-freezing-lava Sep 20, 2023
23d09ac
Write out let-else
flash-freezing-lava Sep 21, 2023
0b6edc0
Don't hide scrollbar when no scrollback exists
flash-freezing-lava Sep 21, 2023
9e9a7d7
Move scrollbar to own module
flash-freezing-lava Sep 21, 2023
39d6f4a
Only damage scrollbar on change
flash-freezing-lava Sep 21, 2023
4061124
Move rect calculation to Scrollbar
flash-freezing-lava Sep 23, 2023
ec16444
Add experimental scrollbar dragging
flash-freezing-lava Sep 23, 2023
2545c2c
Move scrollbar into padded area
flash-freezing-lava Sep 23, 2023
3c2f3f9
Only drag scrollbar on left mouse button
flash-freezing-lava Sep 23, 2023
e3aabd1
Don't continue select scrolling
flash-freezing-lava Sep 23, 2023
8433383
Indicate scrollbar state in cursor icon
flash-freezing-lava Sep 25, 2023
0747d76
Fix: Scrollbar position is incorrect if height gets corrected
flash-freezing-lava Sep 27, 2023
0b7f032
Fix overflow if scrollbar spans whole height
flash-freezing-lava Sep 27, 2023
1464f26
Support scrollbar dragging via touchpad
flash-freezing-lava Sep 28, 2023
5d02bbf
Clarify how scroll of scrollbar is applied
flash-freezing-lava Sep 30, 2023
0673899
Make waiting fraction of scrollbar fade animation constant
flash-freezing-lava Sep 30, 2023
a680acd
Set scrollbar width constant at the cell width
flash-freezing-lava Sep 30, 2023
e0dd6fa
Show cursor change immediately on scrollbar drag and hover
flash-freezing-lava Sep 30, 2023
e1151f3
Set scrollbar minimum height constant based the cell height
flash-freezing-lava Sep 30, 2023
447393d
Repeat window padding instead of separate scrollbar margin
flash-freezing-lava Sep 30, 2023
4d9b6dd
Update CHANGELOG
flash-freezing-lava Sep 30, 2023
60df73f
Don't call scrollbar functions if disabled in config
flash-freezing-lava Sep 30, 2023
4451296
Hide scrollbar in fading mode if full screen is covered
flash-freezing-lava Oct 1, 2023
e11e8b4
Don't redraw frame while waiting for scrollbar fading
flash-freezing-lava Oct 3, 2023
c378b5d
Don't fade out scrollbar while dragging
flash-freezing-lava Oct 4, 2023
15be6d5
Add common derives to scrollbar types
flash-freezing-lava Oct 5, 2023
4fec552
Fix: Scrollbar not fading when swapping windows in animation
flash-freezing-lava Oct 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### Added

- Scrollbar (enable via config `scrollbar.mode`)
- Warnings for unused configuration file options
- Config option `persist` in `hints` config section
- Support for dynamically loading conpty.dll on Windows
Expand Down
46 changes: 45 additions & 1 deletion alacritty/src/config/ui_config.rs
Expand Up @@ -10,7 +10,10 @@ use unicode_width::UnicodeWidthChar;
use winit::keyboard::{Key, KeyLocation, ModifiersState};

use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
use alacritty_terminal::config::{Config as TerminalConfig, Program, LOG_TARGET_CONFIG};
use alacritty_terminal::config::{
Config as TerminalConfig, Percentage, Program, LOG_TARGET_CONFIG,
};
use alacritty_terminal::term::color::Rgb;
use alacritty_terminal::term::search::RegexSearch;

use crate::config::bell::BellConfig;
Expand Down Expand Up @@ -74,6 +77,8 @@ pub struct UiConfig {
/// Keyboard configuration.
keyboard: Keyboard,

pub scrollbar: Scrollbar,

/// Should draw bold text with brighter colors instead of bold font.
#[config(deprecated = "use colors.draw_bold_text_with_bright_colors instead")]
draw_bold_text_with_bright_colors: bool,
Expand Down Expand Up @@ -106,6 +111,7 @@ impl Default for UiConfig {
key_bindings: Default::default(),
alt_send_esc: Default::default(),
keyboard: Default::default(),
scrollbar: Default::default(),
import: Default::default(),
window: Default::default(),
colors: Default::default(),
Expand Down Expand Up @@ -234,6 +240,44 @@ where
Ok(bindings)
}

#[derive(ConfigDeserialize, Clone, Debug, PartialEq)]
pub struct Scrollbar {
pub mode: ScrollbarMode,
pub color: Rgb,
/// Scrollbar opacity from 0.0 (invisible) to 1.0 (opaque).
pub opacity: Percentage,
/// Time (in seconds) the scrollbar fading takes.
pub fade_time_in_secs: f32,
}
impl Scrollbar {
pub fn additional_padding(&self, cell_width: f32, window_padding_x: f32) -> f32 {
if self.mode == ScrollbarMode::Always {
window_padding_x + cell_width
} else {
0.0
}
}
}

impl Default for Scrollbar {
fn default() -> Self {
Scrollbar {
mode: Default::default(),
color: Rgb::new(0x7f, 0x7f, 0x7f),
opacity: Percentage::new(0.5),
fade_time_in_secs: 2.0,
}
}
}

#[derive(ConfigDeserialize, Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ScrollbarMode {
#[default]
Never,
Fading,
Always,
}

/// A delta for a point in a 2 dimensional plane.
#[derive(ConfigDeserialize, Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Delta<T: Default> {
Expand Down
2 changes: 1 addition & 1 deletion alacritty/src/display/cursor.rs
Expand Up @@ -16,7 +16,7 @@ pub trait IntoRects {
impl IntoRects for RenderableCursor {
fn rects(self, size_info: &SizeInfo, thickness: f32) -> CursorRects {
let point = self.point();
let x = point.column.0 as f32 * size_info.cell_width() + size_info.padding_x();
let x = point.column.0 as f32 * size_info.cell_width() + size_info.padding_left();
let y = point.line as f32 * size_info.cell_height() + size_info.padding_y();

let mut width = size_info.cell_width();
Expand Down
3 changes: 2 additions & 1 deletion alacritty/src/display/damage.rs
Expand Up @@ -22,7 +22,7 @@ impl<'a> RenderDamageIterator<'a> {
fn rect_for_line(&self, line_damage: LineDamageBounds) -> Rect {
let size_info = &self.size_info;
let y_top = size_info.height() - size_info.padding_y();
let x = size_info.padding_x() + line_damage.left as u32 * size_info.cell_width();
let x = size_info.padding_left() + line_damage.left as u32 * size_info.cell_width();
let y = y_top - (line_damage.line + 1) as u32 * size_info.cell_height();
let width = (line_damage.right - line_damage.left + 1) as u32 * size_info.cell_width();
Rect::new(x as i32, y as i32, width as i32, size_info.cell_height() as i32)
Expand Down Expand Up @@ -110,6 +110,7 @@ mod tests {
cell_size as f32,
2.,
2.,
0.,
true,
)
.into();
Expand Down
123 changes: 107 additions & 16 deletions alacritty/src/display/mod.rs
Expand Up @@ -34,6 +34,7 @@ use alacritty_terminal::term::color::Rgb;
use alacritty_terminal::term::{self, Term, TermDamage, TermMode, MIN_COLUMNS, MIN_SCREEN_LINES};

use crate::config::font::Font;
use crate::config::ui_config::{Scrollbar as ScrollbarConfig, ScrollbarMode};
use crate::config::window::Dimensions;
#[cfg(not(windows))]
use crate::config::window::StartupMode;
Expand All @@ -45,6 +46,7 @@ use crate::display::cursor::IntoRects;
use crate::display::damage::RenderDamageIterator;
use crate::display::hint::{HintMatch, HintState};
use crate::display::meter::Meter;
use crate::display::scrollbar::Scrollbar;
use crate::display::window::Window;
use crate::event::{Event, EventType, Mouse, SearchState};
use crate::message_bar::{MessageBuffer, MessageType};
Expand All @@ -62,6 +64,7 @@ mod bell;
mod color;
mod damage;
mod meter;
mod scrollbar;

/// Label for the forward terminal search bar.
const FORWARD_SEARCH_LABEL: &str = "Search: ";
Expand Down Expand Up @@ -152,7 +155,10 @@ pub struct SizeInfo<T = f32> {
cell_height: T,

/// Horizontal window padding.
padding_x: T,
padding_left: T,

/// Horizontal window padding.
padding_right: T,

/// Vertical window padding.
padding_y: T,
Expand All @@ -171,7 +177,8 @@ impl From<SizeInfo<f32>> for SizeInfo<u32> {
height: size_info.height as u32,
cell_width: size_info.cell_width as u32,
cell_height: size_info.cell_height as u32,
padding_x: size_info.padding_x as u32,
padding_left: size_info.padding_left as u32,
padding_right: size_info.padding_right as u32,
padding_y: size_info.padding_y as u32,
screen_lines: size_info.screen_lines,
columns: size_info.screen_lines,
Expand Down Expand Up @@ -212,8 +219,13 @@ impl<T: Clone + Copy> SizeInfo<T> {
}

#[inline]
pub fn padding_x(&self) -> T {
self.padding_x
pub fn padding_left(&self) -> T {
self.padding_left
}

#[inline]
pub fn padding_right(&self) -> T {
self.padding_right
}

#[inline]
Expand All @@ -231,25 +243,31 @@ impl SizeInfo<f32> {
cell_height: f32,
mut padding_x: f32,
mut padding_y: f32,
scrollbar_width: f32,
dynamic_padding: bool,
) -> SizeInfo {
if dynamic_padding {
padding_x = Self::dynamic_padding(padding_x.floor(), width, cell_width);
padding_y = Self::dynamic_padding(padding_y.floor(), height, cell_height);
padding_x = Self::dynamic_padding(
padding_x.floor().mul_add(2., scrollbar_width),
width,
cell_width,
);
padding_y = Self::dynamic_padding(padding_y.floor() * 2., height, cell_height);
}

let lines = (height - 2. * padding_y) / cell_height;
let screen_lines = cmp::max(lines as usize, MIN_SCREEN_LINES);

let columns = (width - 2. * padding_x) / cell_width;
let columns = (width - 2. * padding_x - scrollbar_width) / cell_width;
let columns = cmp::max(columns as usize, MIN_COLUMNS);

SizeInfo {
width,
height,
cell_width,
cell_height,
padding_x: padding_x.floor(),
padding_left: padding_x.floor(),
padding_right: padding_x.floor() + scrollbar_width.floor(),
padding_y: padding_y.floor(),
screen_lines,
columns,
Expand All @@ -266,16 +284,16 @@ impl SizeInfo<f32> {
/// The padding, message bar or search are not counted as part of the grid.
#[inline]
pub fn contains_point(&self, x: usize, y: usize) -> bool {
x <= (self.padding_x + self.columns as f32 * self.cell_width) as usize
&& x > self.padding_x as usize
x <= (self.padding_left + self.columns as f32 * self.cell_width) as usize
&& x > self.padding_left as usize
&& y <= (self.padding_y + self.screen_lines as f32 * self.cell_height) as usize
&& y > self.padding_y as usize
}

/// Calculate padding to spread it evenly around the terminal content.
#[inline]
fn dynamic_padding(padding: f32, dimension: f32, cell_dimension: f32) -> f32 {
padding + ((dimension - 2. * padding) % cell_dimension) / 2.
padding + ((dimension - padding) % cell_dimension) / 2.
}
}

Expand Down Expand Up @@ -353,6 +371,8 @@ pub struct Display {

pub visual_bell: VisualBell,

pub scrollbar: Scrollbar,

/// Mapped RGB values for each terminal color.
pub colors: List,

Expand Down Expand Up @@ -440,11 +460,12 @@ impl Display {
cell_height,
padding.0,
padding.1,
config.scrollbar.additional_padding(cell_width, padding.0),
config.window.dynamic_padding && config.window.dimensions().is_none(),
);

info!("Cell size: {} x {}", cell_width, cell_height);
info!("Padding: {} x {}", size_info.padding_x(), size_info.padding_y());
info!("Padding: {} x {}", size_info.padding_left(), size_info.padding_y());
info!("Width: {}, Height: {}", size_info.width(), size_info.height());

// Update OpenGL projection.
Expand Down Expand Up @@ -513,6 +534,7 @@ impl Display {
cursor_hidden: false,
frame_timer: FrameTimer::new(),
visual_bell: VisualBell::from(&config.bell),
scrollbar: Scrollbar::from(&config.scrollbar),
colors: List::from(&config.colors),
pending_update: Default::default(),
pending_renderer_update: Default::default(),
Expand Down Expand Up @@ -636,6 +658,7 @@ impl Display {
cell_height,
padding.0,
padding.1,
config.scrollbar.additional_padding(cell_width, padding.0),
config.window.dynamic_padding,
);

Expand Down Expand Up @@ -699,7 +722,7 @@ impl Display {
}
}

info!("Padding: {} x {}", self.size_info.padding_x(), self.size_info.padding_y());
info!("Padding: {} x {}", self.size_info.padding_left(), self.size_info.padding_y());
info!("Width: {}, Height: {}", self.size_info.width(), self.size_info.height());

// Damage the entire screen after processing update.
Expand Down Expand Up @@ -919,6 +942,16 @@ impl Display {
}
}

if config.scrollbar.mode != ScrollbarMode::Never {
self.draw_scrollbar(
&mut rects,
scheduler,
display_offset,
total_lines,
&config.scrollbar,
);
}

if self.debug_damage {
self.highlight_damage(&mut rects);
}
Expand Down Expand Up @@ -1004,6 +1037,7 @@ impl Display {
pub fn update_config(&mut self, config: &UiConfig) {
self.debug_damage = config.debug.highlight_damage;
self.visual_bell.update_config(&config.bell);
self.scrollbar.update_config(&config.scrollbar);
self.colors = List::from(&config.colors);
}

Expand Down Expand Up @@ -1061,6 +1095,53 @@ impl Display {
dirty
}

fn draw_scrollbar(
&mut self,
rects: &mut Vec<RenderRect>,
scheduler: &mut Scheduler,
display_offset: usize,
total_lines: usize,
config: &ScrollbarConfig,
) {
let did_position_change = self.scrollbar.update(display_offset, total_lines);
let opacity = match self.scrollbar.intensity(self.size_info) {
scrollbar::ScrollbarState::Show { opacity } => opacity,
scrollbar::ScrollbarState::WaitForFading { opacity, remaining_duration } => {
self.request_scrollbar_redraw(scheduler, remaining_duration);
opacity
},
scrollbar::ScrollbarState::Fading { opacity } => {
self.window.request_redraw();
opacity
},
scrollbar::ScrollbarState::Invisible { has_damage } => {
if !has_damage {
return;
}
0.
},
};
let bg_rect = self.scrollbar.bg_rect(self.size_info);
let scrollbar_rect = self.scrollbar.rect_from_bg_rect(bg_rect, self.size_info);
let y = self.size_info.height - (scrollbar_rect.y + scrollbar_rect.height) as f32;
if opacity != 0. {
rects.push(RenderRect::new(
scrollbar_rect.x as f32,
y,
scrollbar_rect.width as f32,
scrollbar_rect.height as f32,
config.color,
opacity,
));
}

if did_position_change {
self.damage_rects.push(bg_rect);
} else if config.mode == ScrollbarMode::Fading && opacity < config.opacity.as_f32() {
self.damage_rects.push(scrollbar_rect);
}
}

#[inline(never)]
fn draw_ime_preview(
&mut self,
Expand Down Expand Up @@ -1336,7 +1417,7 @@ impl Display {
/// This method also enqueues damage for the next frame automatically.
fn damage_from_point(&self, point: Point<usize>, len: u32) -> DamageRect {
let size_info: SizeInfo<u32> = self.size_info.into();
let x = size_info.padding_x() + point.column.0 as u32 * size_info.cell_width();
let x = size_info.padding_left() + point.column.0 as u32 * size_info.cell_width();
let y_top = size_info.height() - size_info.padding_y();
let y = y_top - (point.line as u32 + 1) * size_info.cell_height();
let width = len * size_info.cell_width();
Expand Down Expand Up @@ -1402,10 +1483,19 @@ impl Display {

let window_id = self.window.id();
let timer_id = TimerId::new(Topic::Frame, window_id);
let event = Event::new(EventType::Frame, window_id);
let event = Event::new(EventType::Frame { force: false }, window_id);

scheduler.schedule(event, swap_timeout, false, timer_id);
}

fn request_scrollbar_redraw(&mut self, scheduler: &mut Scheduler, wait_timeout: Duration) {
let window_id = self.window.id();
let timer_id = TimerId::new(Topic::ScrollbarRedraw, window_id);
let event = Event::new(EventType::Frame { force: true }, window_id);

scheduler.unschedule(timer_id);
scheduler.schedule(event, wait_timeout, false, timer_id);
}
}

impl Drop for Display {
Expand Down Expand Up @@ -1620,7 +1710,8 @@ fn window_size(
let grid_width = cell_width * dimensions.columns.0.max(MIN_COLUMNS) as f32;
let grid_height = cell_height * dimensions.lines.max(MIN_SCREEN_LINES) as f32;

let width = (padding.0).mul_add(2., grid_width).floor();
let width = (padding.0).mul_add(2., grid_width).floor()
+ config.scrollbar.additional_padding(cell_width, padding.0);
let height = (padding.1).mul_add(2., grid_height).floor();

PhysicalSize::new(width as u32, height as u32)
Expand Down