mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
Add visual bell support
This commit adds support for a visual bell. Although the Handler in src/ansi.rs warns "Hopefully this is never implemented", I wanted to give it a try. A new config option is added, `visual_bell`, which sets the `duration` and `animation` function of the visual bell. The default `duration` is 150 ms, and the default `animation` is `EaseOutExpo`. To disable the visual bell, set its duration to 0. The visual bell is modeled by VisualBell in src/term/mod.rs. It has a method to ring the bell, `ring`, and another method, `intensity`. Both return the "intensity" of the bell, which ramps down from 1.0 to 0.0 at a rate set by `duration` and `animation`. Whether or not the Processor waits for events is now configurable in order to allow for smooth drawing of the visual bell.
This commit is contained in:
parent
92e1cec088
commit
fbc7b72271
8 changed files with 276 additions and 33 deletions
|
@ -118,6 +118,32 @@ colors:
|
|||
# cyan: '0x93a1a1'
|
||||
# white: '0xfdf6e3'
|
||||
|
||||
# Visual Bell
|
||||
#
|
||||
# Any time the BEL code is received, Alacritty "rings" the visual bell. Once
|
||||
# rung, the terminal background will be set to white and transition back to the
|
||||
# default background color. You can control the rate of this transition by
|
||||
# setting the `duration` property (represented in milliseconds). You can also
|
||||
# configure the transition function by setting the `animation` property.
|
||||
#
|
||||
# Possible values for `animation`
|
||||
# `Ease`
|
||||
# `EaseOut`
|
||||
# `EaseOutSine`
|
||||
# `EaseOutQuad`
|
||||
# `EaseOutCubic`
|
||||
# `EaseOutQuart`
|
||||
# `EaseOutQuint`
|
||||
# `EaseOutExpo`
|
||||
# `EaseOutCirc`
|
||||
# `Linear`
|
||||
#
|
||||
# To completely disable the visual bell, set its duration to 0.
|
||||
#
|
||||
#visual_bell:
|
||||
# animation: EaseOutExpo
|
||||
# duration: 150
|
||||
|
||||
# Key bindings
|
||||
#
|
||||
# Each binding is defined as an object with some properties. Most of the
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
in vec2 TexCoords;
|
||||
in vec3 fg;
|
||||
in vec3 bg;
|
||||
flat in float vb;
|
||||
flat in int background;
|
||||
|
||||
layout(location = 0, index = 0) out vec4 color;
|
||||
|
@ -26,7 +27,7 @@ void main()
|
|||
{
|
||||
if (background != 0) {
|
||||
alphaMask = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
color = vec4(bg, 1.0);
|
||||
color = vec4(bg + vb, 1.0);
|
||||
} else {
|
||||
alphaMask = vec4(texture(mask, TexCoords).rgb, 1.0);
|
||||
color = vec4(fg, 1.0);
|
||||
|
|
|
@ -36,10 +36,12 @@ out vec3 bg;
|
|||
uniform vec2 termDim;
|
||||
uniform vec2 cellDim;
|
||||
|
||||
uniform float visualBell;
|
||||
uniform int backgroundPass;
|
||||
|
||||
// Orthographic projection
|
||||
uniform mat4 projection;
|
||||
flat out float vb;
|
||||
flat out int background;
|
||||
|
||||
void main()
|
||||
|
@ -72,6 +74,7 @@ void main()
|
|||
TexCoords = uvOffset + vec2(position.x, 1 - position.y) * uvSize;
|
||||
}
|
||||
|
||||
vb = visualBell;
|
||||
background = backgroundPass;
|
||||
bg = backgroundColor / vec3(255.0, 255.0, 255.0);
|
||||
fg = textColor / vec3(255.0, 255.0, 255.0);
|
||||
|
|
|
@ -4,10 +4,8 @@
|
|||
//! parameters including font family and style, font size, etc. In the future,
|
||||
//! the config file will also hold user and platform specific keybindings.
|
||||
use std::borrow::Cow;
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::fs;
|
||||
use std::{env, fmt};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::ops::{Index, IndexMut};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -221,6 +219,64 @@ impl IndexMut<usize> for ColorList {
|
|||
}
|
||||
}
|
||||
|
||||
/// VisulBellAnimations are modeled after a subset of CSS transitions and Robert
|
||||
/// Penner's Easing Functions.
|
||||
#[derive(Clone, Copy, Debug, Deserialize)]
|
||||
pub enum VisualBellAnimation {
|
||||
Ease, // CSS
|
||||
EaseOut, // CSS
|
||||
EaseOutSine, // Penner
|
||||
EaseOutQuad, // Penner
|
||||
EaseOutCubic, // Penner
|
||||
EaseOutQuart, // Penner
|
||||
EaseOutQuint, // Penner
|
||||
EaseOutExpo, // Penner
|
||||
EaseOutCirc, // Penner
|
||||
Linear,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct VisualBellConfig {
|
||||
/// Visual bell animation function
|
||||
#[serde(default="default_visual_bell_animation")]
|
||||
animation: VisualBellAnimation,
|
||||
|
||||
/// Visual bell duration in milliseconds
|
||||
#[serde(default="default_visual_bell_duration")]
|
||||
duration: u16,
|
||||
}
|
||||
|
||||
fn default_visual_bell_animation() -> VisualBellAnimation {
|
||||
VisualBellAnimation::EaseOutExpo
|
||||
}
|
||||
|
||||
fn default_visual_bell_duration() -> u16 {
|
||||
150
|
||||
}
|
||||
|
||||
impl VisualBellConfig {
|
||||
/// Visual bell animation
|
||||
#[inline]
|
||||
pub fn animation(&self) -> VisualBellAnimation {
|
||||
self.animation
|
||||
}
|
||||
|
||||
/// Visual bell duration in milliseconds
|
||||
#[inline]
|
||||
pub fn duration(&self) -> Duration {
|
||||
Duration::from_millis(self.duration as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VisualBellConfig {
|
||||
fn default() -> VisualBellConfig {
|
||||
VisualBellConfig {
|
||||
animation: default_visual_bell_animation(),
|
||||
duration: default_visual_bell_duration(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Shell<'a> {
|
||||
program: Cow<'a, str>,
|
||||
|
@ -307,6 +363,10 @@ pub struct Config {
|
|||
|
||||
/// Path where config was loaded from
|
||||
config_path: Option<PathBuf>,
|
||||
|
||||
/// Visual bell configuration
|
||||
#[serde(default)]
|
||||
visual_bell: VisualBellConfig,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os="macos"))]
|
||||
|
@ -351,6 +411,7 @@ impl Default for Config {
|
|||
mouse: Default::default(),
|
||||
shell: None,
|
||||
config_path: None,
|
||||
visual_bell: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1058,6 +1119,12 @@ impl Config {
|
|||
&self.dpi
|
||||
}
|
||||
|
||||
/// Get visual bell config
|
||||
#[inline]
|
||||
pub fn visual_bell(&self) -> &VisualBellConfig {
|
||||
&self.visual_bell
|
||||
}
|
||||
|
||||
/// Should show render timer
|
||||
#[inline]
|
||||
pub fn render_timer(&self) -> bool {
|
||||
|
|
|
@ -272,7 +272,7 @@ impl Display {
|
|||
/// This call may block if vsync is enabled
|
||||
pub fn draw(&mut self, mut terminal: MutexGuard<Term>, config: &Config, selection: &Selection) {
|
||||
// Clear dirty flag
|
||||
terminal.dirty = false;
|
||||
terminal.dirty = !terminal.visual_bell.completed();
|
||||
|
||||
if let Some(title) = terminal.get_next_title() {
|
||||
self.window.set_title(&title);
|
||||
|
@ -291,6 +291,8 @@ impl Display {
|
|||
// mutable borrow
|
||||
let size_info = *terminal.size_info();
|
||||
self.renderer.with_api(config, &size_info, |mut api| {
|
||||
api.set_visual_bell(terminal.visual_bell.intensity() as f32);
|
||||
|
||||
api.clear();
|
||||
|
||||
// Draw the grid
|
||||
|
|
50
src/event.rs
50
src/event.rs
|
@ -135,6 +135,7 @@ pub struct Processor<N> {
|
|||
mouse_bindings: Vec<MouseBinding>,
|
||||
mouse_config: config::Mouse,
|
||||
print_events: bool,
|
||||
wait_for_event: bool,
|
||||
notifier: N,
|
||||
mouse: Mouse,
|
||||
resize_tx: mpsc::Sender<(u32, u32)>,
|
||||
|
@ -170,6 +171,7 @@ impl<N: Notify> Processor<N> {
|
|||
mouse_bindings: config.mouse_bindings().to_vec(),
|
||||
mouse_config: config.mouse().to_owned(),
|
||||
print_events: options.print_events,
|
||||
wait_for_event: true,
|
||||
notifier: notifier,
|
||||
resize_tx: resize_tx,
|
||||
ref_test: ref_test,
|
||||
|
@ -245,7 +247,8 @@ impl<N: Notify> Processor<N> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Process at least one event and handle any additional queued events.
|
||||
/// Process events. When `wait_for_event` is set, this method is guaranteed
|
||||
/// to process at least one event.
|
||||
pub fn process_events<'a>(
|
||||
&mut self,
|
||||
term: &'a FairMutex<Term>,
|
||||
|
@ -276,28 +279,31 @@ impl<N: Notify> Processor<N> {
|
|||
}
|
||||
}
|
||||
|
||||
match window.wait_events().next() {
|
||||
Some(event) => {
|
||||
terminal = term.lock();
|
||||
context = ActionContext {
|
||||
terminal: &mut terminal,
|
||||
notifier: &mut self.notifier,
|
||||
selection: &mut self.selection,
|
||||
mouse: &mut self.mouse,
|
||||
size_info: &self.size_info,
|
||||
};
|
||||
let event = if self.wait_for_event {
|
||||
window.wait_events().next()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
processor = input::Processor {
|
||||
ctx: context,
|
||||
mouse_config: &self.mouse_config,
|
||||
key_bindings: &self.key_bindings[..],
|
||||
mouse_bindings: &self.mouse_bindings[..],
|
||||
};
|
||||
terminal = term.lock();
|
||||
|
||||
process!(event);
|
||||
},
|
||||
// Glutin guarantees the WaitEventsIterator never returns None.
|
||||
None => unreachable!(),
|
||||
context = ActionContext {
|
||||
terminal: &mut terminal,
|
||||
notifier: &mut self.notifier,
|
||||
selection: &mut self.selection,
|
||||
mouse: &mut self.mouse,
|
||||
size_info: &self.size_info,
|
||||
};
|
||||
|
||||
processor = input::Processor {
|
||||
ctx: context,
|
||||
mouse_config: &self.mouse_config,
|
||||
key_bindings: &self.key_bindings[..],
|
||||
mouse_bindings: &self.mouse_bindings[..]
|
||||
};
|
||||
|
||||
if let Some(event) = event {
|
||||
process!(event);
|
||||
}
|
||||
|
||||
for event in window.poll_events() {
|
||||
|
@ -305,6 +311,8 @@ impl<N: Notify> Processor<N> {
|
|||
}
|
||||
}
|
||||
|
||||
self.wait_for_event = !terminal.dirty;
|
||||
|
||||
terminal
|
||||
}
|
||||
|
||||
|
|
|
@ -110,6 +110,9 @@ pub struct ShaderProgram {
|
|||
/// Cell dimensions (pixels)
|
||||
u_cell_dim: GLint,
|
||||
|
||||
/// Visual bell
|
||||
u_visual_bell: GLint,
|
||||
|
||||
/// Background pass flag
|
||||
///
|
||||
/// Rendering is split into two passes; 1 for backgrounds, and one for text
|
||||
|
@ -321,6 +324,7 @@ pub struct RenderApi<'a> {
|
|||
atlas: &'a mut Vec<Atlas>,
|
||||
program: &'a mut ShaderProgram,
|
||||
colors: &'a ColorList,
|
||||
visual_bell: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -646,6 +650,7 @@ impl QuadRenderer {
|
|||
atlas: &mut self.atlas,
|
||||
program: &mut self.program,
|
||||
colors: config.color_list(),
|
||||
visual_bell: 0.0,
|
||||
});
|
||||
|
||||
unsafe {
|
||||
|
@ -708,13 +713,18 @@ impl QuadRenderer {
|
|||
}
|
||||
|
||||
impl<'a> RenderApi<'a> {
|
||||
pub fn set_visual_bell(&mut self, visual_bell: f32) {
|
||||
self.visual_bell = visual_bell;
|
||||
self.program.set_visual_bell(visual_bell);
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
let color = self.colors[NamedColor::Background];
|
||||
unsafe {
|
||||
gl::ClearColor(
|
||||
color.r as f32 / 255.0,
|
||||
color.g as f32 / 255.0,
|
||||
color.b as f32 / 255.0,
|
||||
(self.visual_bell + color.r as f32 / 255.0).min(1.0),
|
||||
(self.visual_bell + color.g as f32 / 255.0).min(1.0),
|
||||
(self.visual_bell + color.b as f32 / 255.0).min(1.0),
|
||||
1.0
|
||||
);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
|
@ -736,7 +746,6 @@ impl<'a> RenderApi<'a> {
|
|||
}
|
||||
|
||||
unsafe {
|
||||
|
||||
self.program.set_background_pass(true);
|
||||
gl::DrawElementsInstanced(gl::TRIANGLES,
|
||||
6, gl::UNSIGNED_INT, ptr::null(),
|
||||
|
@ -941,11 +950,12 @@ impl ShaderProgram {
|
|||
}
|
||||
|
||||
// get uniform locations
|
||||
let (projection, term_dim, cell_dim, background) = unsafe {
|
||||
let (projection, term_dim, cell_dim, visual_bell, background) = unsafe {
|
||||
(
|
||||
gl::GetUniformLocation(program, cptr!(b"projection\0")),
|
||||
gl::GetUniformLocation(program, cptr!(b"termDim\0")),
|
||||
gl::GetUniformLocation(program, cptr!(b"cellDim\0")),
|
||||
gl::GetUniformLocation(program, cptr!(b"visualBell\0")),
|
||||
gl::GetUniformLocation(program, cptr!(b"backgroundPass\0")),
|
||||
)
|
||||
};
|
||||
|
@ -957,6 +967,7 @@ impl ShaderProgram {
|
|||
u_projection: projection,
|
||||
u_term_dim: term_dim,
|
||||
u_cell_dim: cell_dim,
|
||||
u_visual_bell: visual_bell,
|
||||
u_background: background,
|
||||
};
|
||||
|
||||
|
@ -988,6 +999,12 @@ impl ShaderProgram {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_visual_bell(&self, visual_bell: f32) {
|
||||
unsafe {
|
||||
gl::Uniform1f(self.u_visual_bell, visual_bell);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_background_pass(&self, background_pass: bool) {
|
||||
let value = if background_pass {
|
||||
1
|
||||
|
|
121
src/term/mod.rs
121
src/term/mod.rs
|
@ -18,12 +18,13 @@ use std::ops::{Deref, Range, Index, IndexMut};
|
|||
use std::ptr;
|
||||
use std::cmp::min;
|
||||
use std::io;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use ansi::{self, Color, NamedColor, Attr, Handler, CharsetIndex, StandardCharset};
|
||||
use grid::{BidirectionalIterator, Grid, ClearRegion, ToRange};
|
||||
use index::{self, Point, Column, Line, Linear, IndexRange, Contains, RangeInclusive, Side};
|
||||
use selection::{Span, Selection};
|
||||
use config::{Config};
|
||||
use config::{Config, VisualBellAnimation};
|
||||
|
||||
pub mod cell;
|
||||
pub use self::cell::Cell;
|
||||
|
@ -299,6 +300,119 @@ pub struct Cursor {
|
|||
charsets: Charsets,
|
||||
}
|
||||
|
||||
pub struct VisualBell {
|
||||
/// Visual bell animation
|
||||
animation: VisualBellAnimation,
|
||||
|
||||
/// Visual bell duration
|
||||
duration: Duration,
|
||||
|
||||
/// The last time the visual bell rang, if at all
|
||||
start_time: Option<Instant>,
|
||||
}
|
||||
|
||||
fn cubic_bezier(p0: f64, p1: f64, p2: f64, p3: f64, x: f64) -> f64 {
|
||||
(1.0 - x).powi(3) * p0 +
|
||||
3.0 * (1.0 - x).powi(2) * x * p1 +
|
||||
3.0 * (1.0 - x) * x.powi(2) * p2 +
|
||||
x.powi(3) * p3
|
||||
}
|
||||
|
||||
impl VisualBell {
|
||||
pub fn new(config: &Config) -> VisualBell {
|
||||
let visual_bell_config = config.visual_bell();
|
||||
VisualBell {
|
||||
animation: visual_bell_config.animation(),
|
||||
duration: visual_bell_config.duration(),
|
||||
start_time: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Ring the visual bell, and return its intensity.
|
||||
pub fn ring(&mut self) -> f64 {
|
||||
let now = Instant::now();
|
||||
self.start_time = Some(now);
|
||||
self.intensity_at_instant(now)
|
||||
}
|
||||
|
||||
/// Get the currenty intensity of the visual bell. The bell's intensity
|
||||
/// ramps down from 1.0 to 0.0 at a rate determined by the bell's duration.
|
||||
pub fn intensity(&self) -> f64 {
|
||||
self.intensity_at_instant(Instant::now())
|
||||
}
|
||||
|
||||
/// Check whether or not the visual bell has completed "ringing".
|
||||
pub fn completed(&self) -> bool {
|
||||
match self.start_time {
|
||||
Some(earlier) => Instant::now().duration_since(earlier) > self.duration,
|
||||
None => true
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the intensity of the visual bell at a particular instant. The bell's
|
||||
/// intensity ramps down from 1.0 to 0.0 at a rate determined by the bell's
|
||||
/// duration.
|
||||
pub fn intensity_at_instant(&self, instant: Instant) -> f64 {
|
||||
// If `duration` is zero, then the VisualBell is disabled; therefore,
|
||||
// its `intensity` is zero.
|
||||
if self.duration == Duration::from_secs(0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
match self.start_time {
|
||||
// Similarly, if `start_time` is `None`, then the VisualBell has not
|
||||
// been "rung"; therefore, its `intensity` is zero.
|
||||
None => 0.0,
|
||||
|
||||
Some(earlier) => {
|
||||
// Finally, if the `instant` at which we wish to compute the
|
||||
// VisualBell's `intensity` occurred before the VisualBell was
|
||||
// "rung", then its `intensity` is also zero.
|
||||
if instant < earlier {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let elapsed = instant.duration_since(earlier);
|
||||
let elapsed_f = elapsed.as_secs() as f64 +
|
||||
elapsed.subsec_nanos() as f64 / 1e9f64;
|
||||
let duration_f = self.duration.as_secs() as f64 +
|
||||
self.duration.subsec_nanos() as f64 / 1e9f64;
|
||||
|
||||
// Otherwise, we compute a value `time` from 0.0 to 1.0
|
||||
// inclusive that represents the ratio of `elapsed` time to the
|
||||
// `duration` of the VisualBell.
|
||||
let time = (elapsed_f / duration_f).min(1.0);
|
||||
|
||||
// We use this to compute the inverse `intensity` of the
|
||||
// VisualBell. When `time` is 0.0, `inverse_intensity` is 0.0,
|
||||
// and when `time` is 1.0, `inverse_intensity` is 1.0.
|
||||
let inverse_intensity = match self.animation {
|
||||
VisualBellAnimation::Ease => cubic_bezier(0.25, 0.1, 0.25, 1.0, time),
|
||||
VisualBellAnimation::EaseOut => cubic_bezier(0.25, 0.1, 0.25, 1.0, time),
|
||||
VisualBellAnimation::EaseOutSine => cubic_bezier(0.39, 0.575, 0.565, 1.0, time),
|
||||
VisualBellAnimation::EaseOutQuad => cubic_bezier(0.25, 0.46, 0.45, 0.94, time),
|
||||
VisualBellAnimation::EaseOutCubic => cubic_bezier(0.215, 0.61, 0.355, 1.0, time),
|
||||
VisualBellAnimation::EaseOutQuart => cubic_bezier(0.165, 0.84, 0.44, 1.0, time),
|
||||
VisualBellAnimation::EaseOutQuint => cubic_bezier(0.23, 1.0, 0.32, 1.0, time),
|
||||
VisualBellAnimation::EaseOutExpo => cubic_bezier(0.19, 1.0, 0.22, 1.0, time),
|
||||
VisualBellAnimation::EaseOutCirc => cubic_bezier(0.075, 0.82, 0.165, 1.0, time),
|
||||
VisualBellAnimation::Linear => time,
|
||||
};
|
||||
|
||||
// Since we want the `intensity` of the VisualBell to decay over
|
||||
// `time`, we subtract the `inverse_intensity` from 1.0.
|
||||
1.0 - inverse_intensity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_config(&mut self, config: &Config) {
|
||||
let visual_bell_config = config.visual_bell();
|
||||
self.animation = visual_bell_config.animation();
|
||||
self.duration = visual_bell_config.duration();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Term {
|
||||
/// The grid
|
||||
grid: Grid<Cell>,
|
||||
|
@ -345,6 +459,8 @@ pub struct Term {
|
|||
|
||||
pub dirty: bool,
|
||||
|
||||
pub visual_bell: VisualBell,
|
||||
|
||||
custom_cursor_colors: bool,
|
||||
|
||||
/// Saved cursor from main grid
|
||||
|
@ -424,6 +540,7 @@ impl Term {
|
|||
Term {
|
||||
next_title: None,
|
||||
dirty: false,
|
||||
visual_bell: VisualBell::new(config),
|
||||
input_needs_wrap: false,
|
||||
grid: grid,
|
||||
alt_grid: alt,
|
||||
|
@ -445,6 +562,7 @@ impl Term {
|
|||
pub fn update_config(&mut self, config: &Config) {
|
||||
self.custom_cursor_colors = config.custom_cursor_colors();
|
||||
self.semantic_escape_chars = config.selection().semantic_escape_chars.clone();
|
||||
self.visual_bell.update_config(config);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -1027,6 +1145,7 @@ impl ansi::Handler for Term {
|
|||
#[inline]
|
||||
fn bell(&mut self) {
|
||||
trace!("bell");
|
||||
self.visual_bell.ring();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
Loading…
Reference in a new issue