1
0
Fork 0
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:
Mark Andrus Roberts 2017-02-03 15:34:52 -08:00 committed by Joe Wilm
parent 92e1cec088
commit fbc7b72271
8 changed files with 276 additions and 33 deletions

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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 {

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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]