summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMark Andrus Roberts <mroberts@twilio.com>2017-02-03 15:34:52 -0800
committerJoe Wilm <joe@jwilm.com>2017-02-07 21:12:56 -0800
commitfbc7b7227171b41d96ca52df52e4cf1833f5fc6f (patch)
tree8f33bd12933b129ec751f5dd643387eae254030f /src
parent92e1cec0880313d962d80bf16eca60cebb509eab (diff)
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.
Diffstat (limited to 'src')
-rw-r--r--src/config.rs75
-rw-r--r--src/display.rs4
-rw-r--r--src/event.rs54
-rw-r--r--src/renderer/mod.rs27
-rw-r--r--src/term/mod.rs121
5 files changed, 247 insertions, 34 deletions
diff --git a/src/config.rs b/src/config.rs
index c610f419..eb4a9e61 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -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 {
diff --git a/src/display.rs b/src/display.rs
index 0b242b33..9bfe7339 100644
--- a/src/display.rs
+++ b/src/display.rs
@@ -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
diff --git a/src/event.rs b/src/event.rs
index f643c115..9a7d2e8e 100644
--- a/src/event.rs
+++ b/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,
- };
-
- processor = input::Processor {
- ctx: context,
- mouse_config: &self.mouse_config,
- key_bindings: &self.key_bindings[..],
- mouse_bindings: &self.mouse_bindings[..],
- };
-
- process!(event);
- },
- // Glutin guarantees the WaitEventsIterator never returns None.
- None => unreachable!(),
+ let event = if self.wait_for_event {
+ window.wait_events().next()
+ } else {
+ None
+ };
+
+ 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,
+ };
+
+ 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
}
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index fe4ff29f..2d466f79 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -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
diff --git a/src/term/mod.rs b/src/term/mod.rs
index b0ca2a59..a7cda171 100644
--- a/src/term/mod.rs
+++ b/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]