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 basic dimming functionality #2751

Closed
wants to merge 12 commits into from
21 changes: 21 additions & 0 deletions book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,25 @@ Example:
[editor.indent-guides]
render = true
character = "╎"

### `[editor.dim]` Section

Enable dimming in certain areas. Disabled by default. Enable by assigning:
- 0 = set text modifier flag DIM
- negative value = darken colors (-127 = 100% darker)
- positive value = lighten colors (+127 = 100% lighter)

> darken/lighten colors only works with themes that set rgb colors, not indexed colors.

| Key | Description | Default |
|--|--|---------|
| `overlay-backdrops` | Enable dimming and set shade of content behind overlays. | `None` |
| `unfocused-views`| Enable dimming and set shade of unfocused editor views. | `None` |

```toml
[editor.dim]
# darken backdrop of overlays
overlay-backdrops = -25
# dim fg color of unfocused views
unfocused-views = 0
```
16 changes: 15 additions & 1 deletion helix-term/src/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ pub trait Component: Any + AnyComponent {
fn id(&self) -> Option<&'static str> {
None
}

fn is_opaque(&self) -> bool {
true
}
}

use anyhow::Context as AnyhowContext;
Expand Down Expand Up @@ -197,8 +201,18 @@ impl Compositor {

let area = *surface.area();

for layer in &mut self.layers {
let dim_backdrops = cx.editor.config().dim.overlay_backdrops;
let mut layers = self.layers.iter_mut();
while let Some(layer) = layers.next() {
// begin dimming if enabled and any overlay layers follow
let dimmed = dim_backdrops.filter(|_| layers.as_slice().iter().any(|l| !l.is_opaque()));
if let Some(shade) = dimmed {
surface.begin_dimmed(shade);
}
layer.render(area, surface, cx);
if dimmed.is_some() {
surface.end_dimmed();
}
}

let (pos, kind) = self.cursor(area, cx.editor);
Expand Down
9 changes: 9 additions & 0 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ impl EditorView {
let area = view.area;
let theme = &editor.theme;

let dimmed = editor.config().dim.unfocused_views.filter(|_| !is_focused);
if let Some(shade) = dimmed {
surface.begin_dimmed(shade);
}

// DAP: Highlight current stack frame position
let stack_frame = editor.debugger.as_ref().and_then(|debugger| {
if let (Some(frame), Some(thread_id)) = (debugger.active_frame, debugger.thread_id) {
Expand Down Expand Up @@ -162,6 +167,10 @@ impl EditorView {
.clip_top(view.area.height.saturating_sub(1))
.clip_bottom(1); // -1 from bottom to remove commandline
self.render_statusline(editor, doc, view, statusline_area, surface, is_focused);

if dimmed.is_some() {
surface.end_dimmed();
}
}

pub fn render_rulers(
Expand Down
4 changes: 4 additions & 0 deletions helix-term/src/ui/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,8 @@ impl<T: Component + 'static> Component for Overlay<T> {
let dimensions = (self.calc_child_size)(area);
self.content.cursor(dimensions, ctx)
}

fn is_opaque(&self) -> bool {
false
}
}
36 changes: 31 additions & 5 deletions helix-tui/src/buffer.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::text::{Span, Spans};
use helix_core::unicode::width::UnicodeWidthStr;
use helix_view::graphics::{Color, Modifier, Rect, Style};
use std::cmp::min;
use std::collections::VecDeque;
use unicode_segmentation::UnicodeSegmentation;

use helix_view::graphics::{Color, Modifier, Rect, Style};

/// A buffer cell
#[derive(Debug, Clone, PartialEq)]
pub struct Cell {
Expand Down Expand Up @@ -109,6 +109,8 @@ pub struct Buffer {
/// The content of the buffer. The length of this Vec should always be equal to area.width *
/// area.height
pub content: Vec<Cell>,
/// Stack of dimming operations that is applied to all set_style invocations
dim_operations: VecDeque<i8>,
}

impl Buffer {
Expand All @@ -125,7 +127,11 @@ impl Buffer {
for _ in 0..size {
content.push(cell.clone());
}
Buffer { area, content }
Buffer {
area,
content,
dim_operations: VecDeque::new(),
}
}

/// Returns a Buffer containing the given lines
Expand Down Expand Up @@ -328,7 +334,8 @@ impl Buffer {
}

self.content[index].set_symbol(s);
self.content[index].set_style(style(byte_offset));
let style = self.applied_style(style(byte_offset));
self.content[index].set_style(style);
// Reset following cells if multi-width (they would be hidden by the grapheme),
for i in index + 1..index + width {
self.content[i].reset();
Expand Down Expand Up @@ -362,7 +369,8 @@ impl Buffer {
break;
}
self.content[start].set_symbol(s);
self.content[start].set_style(style(byte_offset));
let style = self.applied_style(style(byte_offset));
self.content[start].set_style(style);
for i in start + 1..index {
self.content[i].reset();
}
Expand All @@ -387,6 +395,7 @@ impl Buffer {
return (x, y);
}

let style = self.applied_style(style);
let mut index = self.index_of(x, y);
let mut x_offset = x as usize;
let max_x_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
Expand Down Expand Up @@ -452,7 +461,24 @@ impl Buffer {
}
}

/// Begin dimming. Apply shade to all set_style calls, until end_dimmed is called
pub fn begin_dimmed(&mut self, shade: i8) {
self.dim_operations.push_back(shade);
}

/// Remove last dimming operation
pub fn end_dimmed(&mut self) {
assert!(!self.dim_operations.is_empty());
self.dim_operations.pop_back();
}

#[inline]
fn applied_style(&self, style: Style) -> Style {
self.dim_operations.iter().fold(style, |a, s| a.dimmed(*s))
}

pub fn set_style(&mut self, area: Rect, style: Style) {
let style = self.applied_style(style);
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
self[(x, y)].set_style(style);
Expand Down
13 changes: 13 additions & 0 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ impl Default for FilePickerConfig {
}
}

#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct DimConfig {
/// shade for margin around content of overlays
pub overlay_backdrops: Option<i8>,
/// shade for unfocused editor views
pub unfocused_views: Option<i8>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct Config {
Expand Down Expand Up @@ -163,6 +172,9 @@ pub struct Config {
pub indent_guides: IndentGuidesConfig,
/// Whether to color modes with different colors. Defaults to `false`.
pub color_modes: bool,
/// Dim configuration
#[serde(default)]
pub dim: DimConfig,
}

#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -417,6 +429,7 @@ impl Default for Config {
whitespace: WhitespaceConfig::default(),
indent_guides: IndentGuidesConfig::default(),
color_modes: false,
dim: DimConfig::default(),
}
}
}
Expand Down
25 changes: 25 additions & 0 deletions helix-view/src/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,31 @@ impl Style {

self
}

/// Return new Style with applied shade.
///
/// - `shade = 0`: set text modifier `DIM`
/// - `shade < 0`: darken rgb color
/// - `shade > 0`: lighten rgb color
pub fn dimmed(self, shade: i8) -> Style {
let alpha = i32::from(shade).unsigned_abs() << 1;
let (src_factor, dst_factor) = (alpha, 256 - alpha);
let src = if shade > 0 { 255u32 } else { 0u32 } * src_factor;
let shaded = |dst_color: u8| ((u32::from(dst_color) * dst_factor + src) >> 8) as u8;
let mut res = self;
if shade == 0 {
res.add_modifier.insert(Modifier::DIM);
res.sub_modifier.remove(Modifier::DIM);
} else {
if let Some(Color::Rgb(r, g, b)) = res.fg {
res.fg = Some(Color::Rgb(shaded(r), shaded(g), shaded(b)))
};
if let Some(Color::Rgb(r, g, b)) = res.bg {
res.bg = Some(Color::Rgb(shaded(r), shaded(g), shaded(b)))
}
}
res
}
}

#[cfg(test)]
Expand Down