mirror of https://github.com/microsoft/edit
Add color mapping for terminals without 24-bit color support
This commit is contained in:
parent
a3a6f5f8be
commit
1ac9636e13
|
|
@ -83,11 +83,7 @@ fn run() -> apperr::Result<()> {
|
|||
|
||||
let _restore = setup_terminal(&mut tui, &mut state, &mut vt_parser);
|
||||
|
||||
state.menubar_color_bg = tui.indexed(IndexedColor::Background).oklab_blend(tui.indexed_alpha(
|
||||
IndexedColor::BrightBlue,
|
||||
1,
|
||||
2,
|
||||
));
|
||||
state.menubar_color_bg = tui.indexed(IndexedColor::Blue);
|
||||
state.menubar_color_fg = tui.contrasted(state.menubar_color_bg);
|
||||
let floater_bg = tui
|
||||
.indexed_alpha(IndexedColor::Background, 2, 3)
|
||||
|
|
|
|||
|
|
@ -90,8 +90,10 @@ pub const DEFAULT_THEME: [StraightRgba; INDEXED_COLORS_COUNT] = [
|
|||
/// of `vim` for instance, you'll notice that it redraws unrelated parts of
|
||||
/// the screen all the time.
|
||||
pub struct Framebuffer {
|
||||
/// Store the color palette.
|
||||
/// The terminal color palette (or some fallback).
|
||||
indexed_colors: [StraightRgba; INDEXED_COLORS_COUNT],
|
||||
/// Whether the terminal supports 24-bit colors.
|
||||
supports_24bit_colors: bool,
|
||||
/// Front and back buffers. Indexed by `frame_counter & 1`.
|
||||
buffers: [Buffer; 2],
|
||||
/// The current frame counter. Increments on every `flip` call.
|
||||
|
|
@ -112,6 +114,7 @@ impl Framebuffer {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
indexed_colors: DEFAULT_THEME,
|
||||
supports_24bit_colors: false,
|
||||
buffers: Default::default(),
|
||||
frame_counter: 0,
|
||||
auto_colors: [
|
||||
|
|
@ -130,6 +133,9 @@ impl Framebuffer {
|
|||
/// If you call this method, [`Framebuffer`] expects that you
|
||||
/// successfully detect the light/dark mode of the terminal.
|
||||
pub fn set_indexed_colors(&mut self, colors: [StraightRgba; INDEXED_COLORS_COUNT]) {
|
||||
// If the terminal supports color queries, shouldn't it also support 24-bit colors?
|
||||
self.supports_24bit_colors = true;
|
||||
|
||||
self.indexed_colors = colors;
|
||||
self.background_fill = StraightRgba::zero();
|
||||
self.foreground_fill = StraightRgba::zero();
|
||||
|
|
@ -567,10 +573,82 @@ impl Framebuffer {
|
|||
color = dst.oklab_blend(color);
|
||||
}
|
||||
|
||||
let r = color.red();
|
||||
let g = color.green();
|
||||
let b = color.blue();
|
||||
_ = write!(dst, "\x1b[{typ}8;2;{r};{g};{b}m");
|
||||
if self.supports_24bit_colors {
|
||||
let r = color.red();
|
||||
let g = color.green();
|
||||
let b = color.blue();
|
||||
_ = write!(dst, "\x1b[{typ}8;2;{r};{g};{b}m");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut colors = [StraightRgba::zero(); 258];
|
||||
|
||||
// First 2 colors are the default background/foreground (indices 0..1)
|
||||
colors[0] = self.indexed(IndexedColor::Foreground);
|
||||
colors[1] = self.indexed(IndexedColor::Background);
|
||||
// Then the 16 indexed colors (indices 2..18)
|
||||
colors[2..18].copy_from_slice(&self.indexed_colors[..16]);
|
||||
// Build 6x6x6 cube (indices 18..234)
|
||||
let mut i = 18usize;
|
||||
for r in 0..6 {
|
||||
for g in 0..6 {
|
||||
for b in 0..6 {
|
||||
let vr = if r == 0 { 0 } else { 55 + 40 * r };
|
||||
let vg = if g == 0 { 0 } else { 55 + 40 * g };
|
||||
let vb = if b == 0 { 0 } else { 55 + 40 * b };
|
||||
let v =
|
||||
((vr as u32) << 24) | ((vg as u32) << 16) | ((vb as u32) << 8) | 0xffu32;
|
||||
colors[i] = StraightRgba::from_be(v);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Grayscale ramp (indices 234..258): 24 shades from 8 to 238 inclusive (step 10)
|
||||
for k in 0..24 {
|
||||
let gray = 8 + 10 * k;
|
||||
let v = ((gray as u32) << 24) | ((gray as u32) << 16) | ((gray as u32) << 8) | 0xffu32;
|
||||
colors[i] = StraightRgba::from_be(v);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// Find nearest color in the 256-color table (euclidean RGB distance)
|
||||
let color = color.as_oklab();
|
||||
let mut best_idx = 0usize;
|
||||
let mut best_dist = f32::INFINITY;
|
||||
for (i, c) in colors.iter().enumerate() {
|
||||
if i == fg as usize {
|
||||
// If `fg` is true, skip index 1, because emitting
|
||||
// "CSI 49 m" has no effect on the foreground color.
|
||||
// Vice versa for fg=false and index=0.
|
||||
continue;
|
||||
}
|
||||
|
||||
let c = c.as_oklab();
|
||||
let l = color.lightness() - c.lightness();
|
||||
let a = color.a() - c.a();
|
||||
let b = color.b() - c.b();
|
||||
let dist = l * l + a * a * 2.0 + b * b * 2.0;
|
||||
|
||||
if dist < best_dist {
|
||||
best_dist = dist;
|
||||
best_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
_ = match best_idx {
|
||||
0 => write!(dst, "\x1b[39m"),
|
||||
1 => write!(dst, "\x1b[49m"),
|
||||
2..18 => {
|
||||
let best_idx = best_idx - 2;
|
||||
let code = if best_idx < 8 {
|
||||
best_idx + (if fg { 30 } else { 40 })
|
||||
} else {
|
||||
best_idx + (if fg { 82 } else { 92 })
|
||||
};
|
||||
write!(dst, "\x1b[{code}m")
|
||||
}
|
||||
18.. => write!(dst, "\x1b[{typ}8;5;{}m", best_idx - 2),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,11 +22,16 @@ impl StraightRgba {
|
|||
StraightRgba(0)
|
||||
}
|
||||
|
||||
/// Expects a color in lower-endian RGBA format (R is the lowest and A the highest byte).
|
||||
#[inline]
|
||||
pub const fn from_le(color: u32) -> Self {
|
||||
StraightRgba(u32::from_le(color))
|
||||
}
|
||||
|
||||
/// Expects a color in big-endian RGBA format (A is the lowest and R the highest byte).
|
||||
///
|
||||
/// This is useful for color constants, because "#RRGGBBAA" is the notation used for CSS colors.
|
||||
/// It allows you to copy such colors between websites (e.g. color generators) and this source code.
|
||||
#[inline]
|
||||
pub const fn from_be(color: u32) -> Self {
|
||||
StraightRgba(u32::from_be(color))
|
||||
|
|
|
|||
Loading…
Reference in New Issue