From bc5c7f3a4884d8f605f8560f1602f2cc1baabe65 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Sun, 17 Dec 2023 22:51:50 -0700 Subject: [PATCH] Implement colors and resizing --- src/main.rs | 269 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 223 insertions(+), 46 deletions(-) diff --git a/src/main.rs b/src/main.rs index f0fac3b..bbda260 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,41 +1,60 @@ use alacritty_terminal::{ + ansi::{Color, NamedColor}, config::{Config, PtyConfig}, - event::{Event as TermEvent, EventListener, Notify, WindowSize}, + event::{Event as TermEvent, EventListener, Notify, OnResize, WindowSize}, event_loop::{EventLoop as PtyEventLoop, Notifier}, grid::Dimensions, index::{Column, Line, Point}, sync::FairMutex, - term::cell::Flags, + term::{ + cell::Flags, + color::{Colors, Rgb}, + }, tty, Term, }; use cosmic_text::{ Attrs, AttrsList, Buffer, BufferLine, Family, FontSystem, Metrics, Shaping, Style, SwashCache, - Weight, + Weight, Wrap, }; use std::{num::NonZeroU32, rc::Rc, slice, sync::Arc}; -use tiny_skia::{Color, Paint, PixmapMut, Rect, Transform}; +use tiny_skia::{Paint, PixmapMut, Rect, Transform}; use winit::{ event::{ElementState, Event as WinitEvent, KeyEvent, Modifiers, WindowEvent}, event_loop::{ControlFlow, EventLoopBuilder, EventLoopProxy}, + keyboard::ModifiersState, window::WindowBuilder, }; -struct TermSize { - rows: usize, - cols: usize, +#[derive(Clone, Copy, Debug)] +struct Size { + width: f32, + height: f32, + cell_width: f32, + cell_height: f32, } -impl Dimensions for TermSize { +impl Dimensions for Size { fn total_lines(&self) -> usize { - self.rows + self.screen_lines() } fn screen_lines(&self) -> usize { - self.rows + (self.height / self.cell_height).floor() as usize } fn columns(&self) -> usize { - self.cols + (self.width / self.cell_width).floor() as usize + } +} + +impl From for WindowSize { + fn from(size: Size) -> Self { + Self { + num_lines: size.screen_lines() as u16, + num_cols: size.columns() as u16, + cell_width: size.cell_width as u16, + cell_height: size.cell_height as u16, + } } } @@ -48,46 +67,119 @@ impl EventListener for EventProxy { } } +fn colors() -> Colors { + let mut colors = Colors::default(); + + // These colors come from `ransid`: https://gitlab.redox-os.org/redox-os/ransid/-/blob/master/src/color.rs + let encode_rgb = |r: u8, g: u8, b: u8| -> Rgb { Rgb { r, g, b } }; + for value in 0..=255 { + let color = match value { + 0 => encode_rgb(0x00, 0x00, 0x00), + 1 => encode_rgb(0x80, 0x00, 0x00), + 2 => encode_rgb(0x00, 0x80, 0x00), + 3 => encode_rgb(0x80, 0x80, 0x00), + 4 => encode_rgb(0x00, 0x00, 0x80), + 5 => encode_rgb(0x80, 0x00, 0x80), + 6 => encode_rgb(0x00, 0x80, 0x80), + 7 => encode_rgb(0xc0, 0xc0, 0xc0), + 8 => encode_rgb(0x80, 0x80, 0x80), + 9 => encode_rgb(0xff, 0x00, 0x00), + 10 => encode_rgb(0x00, 0xff, 0x00), + 11 => encode_rgb(0xff, 0xff, 0x00), + 12 => encode_rgb(0x00, 0x00, 0xff), + 13 => encode_rgb(0xff, 0x00, 0xff), + 14 => encode_rgb(0x00, 0xff, 0xff), + 15 => encode_rgb(0xff, 0xff, 0xff), + 16..=231 => { + let convert = |value: u8| -> u8 { + match value { + 0 => 0, + _ => value * 0x28 + 0x28, + } + }; + + let r = convert((value - 16) / 36 % 6); + let g = convert((value - 16) / 6 % 6); + let b = convert((value - 16) % 6); + encode_rgb(r, g, b) + } + 232..=255 => { + let gray = (value - 232) * 10 + 8; + encode_rgb(gray, gray, gray) + } + }; + colors[value as usize] = Some(color); + } + + // Set special colors + colors[NamedColor::Foreground] = colors[NamedColor::White]; + colors[NamedColor::Background] = colors[NamedColor::Black]; + /*TODO + colors[NamedColor::Cursor] = colors[NamedColor::]; + colors[NamedColor::DimBlack] = colors[NamedColor::]; + colors[NamedColor::DimRed] = colors[NamedColor::]; + colors[NamedColor::DimGreen] = colors[NamedColor::]; + colors[NamedColor::DimYellow] = colors[NamedColor::]; + colors[NamedColor::DimBlue] = colors[NamedColor::]; + colors[NamedColor::DimMagenta] = colors[NamedColor::]; + colors[NamedColor::DimCyan] = colors[NamedColor::]; + colors[NamedColor::DimWhite] = colors[NamedColor::]; + */ + colors[NamedColor::BrightForeground] = colors[NamedColor::BrightWhite]; + //TODO colors[NamedColor::DimForeground] = colors[NamedColor::]; + + colors +} + fn main() { let mut font_system = FontSystem::new(); let mut swash_cache = SwashCache::new(); let metrics = Metrics::new(14.0, 20.0); + //TODO: set color to default fg + let default_attrs = Attrs::new().family(Family::Monospace); let mut buffer = Buffer::new_empty(metrics); - let mut buffer = buffer.borrow_with(&mut font_system); + buffer.set_wrap(&mut font_system, Wrap::None); + let (cell_width, cell_height) = { + // Use size of space to determine cell size + buffer.set_text(&mut font_system, " ", default_attrs, Shaping::Advanced); + let layout = buffer.line_layout(&mut font_system, 0).unwrap(); + (layout[0].w, layout[0].max_ascent + layout[0].max_descent) + }; + println!("{}, {}", cell_width, cell_height); let event_loop = EventLoopBuilder::::with_user_event() .build() .unwrap(); let event_loop_proxy = event_loop.create_proxy(); let window = Rc::new(WindowBuilder::new().build(&event_loop).unwrap()); + let (width, height) = { + let inner_size = window.inner_size(); + (inner_size.width as f32, inner_size.height as f32) + }; let config = Config::default(); - let dimensions = TermSize { rows: 24, cols: 80 }; + let mut dimensions = Size { + width, + height, + cell_width, + cell_height, + }; let event_proxy = EventProxy(event_loop_proxy); let term = Arc::new(FairMutex::new(Term::new( &config, &dimensions, event_proxy.clone(), ))); + let colors = colors(); let pty_config = PtyConfig::default(); - let window_size = WindowSize { - num_lines: dimensions.rows as u16, - num_cols: dimensions.cols as u16, - cell_width: 8, /*TODO*/ - cell_height: 16, /*TODO*/ - }; let window_id = 0; - let pty = tty::new(&pty_config, window_size, window_id).unwrap(); + let pty = tty::new(&pty_config, dimensions.into(), window_id).unwrap(); let pty_event_loop = PtyEventLoop::new(term.clone(), event_proxy, pty, pty_config.hold, false); - let notifier = Notifier(pty_event_loop.channel()); + let mut notifier = Notifier(pty_event_loop.channel()); let pty_join_handle = pty_event_loop.spawn(); - for i in 0..dimensions.rows { - notifier.notify(format!("echo {}\r", i).into_bytes()); - } - let context = softbuffer::Context::new(window.clone()).unwrap(); let mut surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); let mut modifiers = Modifiers::default(); @@ -120,33 +212,65 @@ fn main() { }; let mut pixmap = PixmapMut::from_bytes(surface_buffer_u8, width, height).unwrap(); - pixmap.fill(Color::from_rgba8(0, 0, 0, 0xFF)); + pixmap.fill(tiny_skia::Color::from_rgba8(0, 0, 0, 0xFF)); // Set scroll to view scroll //TODO buffer.set_scroll(*scroll); // Set size, will relayout and shape until scroll if changed - buffer.set_size(width as f32, height as f32); + buffer.set_size(&mut font_system, width as f32, height as f32); // Shape until scroll, ensures scroll is clamped - buffer.shape_until_scroll(true); + buffer.shape_until_scroll(&mut font_system, true); // Update scroll after buffer clamps it //TODO *scroll = buffer.scroll(); let mut paint = Paint::default(); paint.anti_alias = false; let transform = Transform::identity(); - buffer.draw( - &mut swash_cache, - cosmic_text::Color::rgb(0xFF, 0xFF, 0xFF), - |x, y, w, h, color| { - paint.set_color_rgba8(color.r(), color.g(), color.b(), color.a()); - pixmap.fill_rect( - Rect::from_xywh(x as f32, y as f32, w as f32, h as f32).unwrap(), - &paint, - transform, - None, + let mut f = |x: f32, y: f32, w: f32, h: f32, color: cosmic_text::Color| { + // Have to swap RGB for BGR + paint.set_color_rgba8(color.b(), color.g(), color.r(), color.a()); + pixmap.fill_rect( + Rect::from_xywh(x, y, w, h).unwrap(), + &paint, + transform, + None, + ); + }; + for run in buffer.layout_runs() { + for glyph in run.glyphs.iter() { + let physical_glyph = glyph.physical((0., 0.), 1.0); + + let glyph_color = match glyph.color_opt { + Some(some) => some, + None => cosmic_text::Color::rgb(0xFF, 0xFF, 0xFF), + }; + + let background_color = cosmic_text::Color(glyph.metadata as u32); + + f( + glyph.x, + run.line_top, + glyph.w, + metrics.line_height, + background_color, ); - }, - ); + + swash_cache.with_pixels( + &mut font_system, + physical_glyph.cache_key, + glyph_color, + |x, y, color| { + f( + (physical_glyph.x + x) as f32, + run.line_y + (physical_glyph.y + y) as f32, + 1.0, + 1.0, + color, + ); + }, + ); + } + } buffer.set_redraw(false); surface_buffer.present().unwrap(); @@ -164,8 +288,32 @@ fn main() { }, window_id, } if window_id == window.id() => { - println!("{:?}", text); - notifier.notify(text.as_bytes().to_vec()); + println!("{:?} {:?}", modifiers, text); + match ( + modifiers.state().contains(ModifiersState::SUPER), + modifiers.state().contains(ModifiersState::CONTROL), + modifiers.state().contains(ModifiersState::ALT), + ) { + (true, _, _) => {} // Ignore super + (false, true, _) => { + // Control keys + if text.len() == 1 { + let c = text.chars().next().unwrap_or_default(); + if c >= 'a' && c <= 'z' { + notifier.notify(vec![(c as u8) - b'a' + 1]); + } + } + } + (false, false, true) => { + // Alt keys + let mut bytes = text.as_bytes().to_vec(); + bytes.insert(0, b'\x1B'); + notifier.notify(bytes); + } + (false, false, false) => { + notifier.notify(text.as_bytes().to_vec()); + } + } } WinitEvent::WindowEvent { event: WindowEvent::ModifiersChanged(new_modifiers), @@ -173,6 +321,15 @@ fn main() { } if window_id == window.id() => { modifiers = new_modifiers; } + WinitEvent::WindowEvent { + event: WindowEvent::Resized(physical_size), + window_id, + } if window_id == window.id() => { + dimensions.width = physical_size.width as f32; + dimensions.height = physical_size.height as f32; + notifier.on_resize(dimensions.into()); + term.lock().resize(dimensions); + } WinitEvent::WindowEvent { event: WindowEvent::CloseRequested, window_id, @@ -190,8 +347,6 @@ fn main() { //TODO: is redraw needed after all events? //TODO: use LineDamageBounds { - //TODO: set color to default fg - let default_attrs = Attrs::new().family(Family::Monospace); let mut last_point = Point::new(Line(0), Column(0)); let mut text = String::new(); let mut attrs_list = AttrsList::new(default_attrs); @@ -220,9 +375,31 @@ fn main() { text.push(indexed.cell.c); let end = text.len(); + let convert_color = |color| { + let rgb = match color { + Color::Named(named_color) => match colors[named_color] { + Some(rgb) => rgb, + None => { + eprintln!("missing named color {:?}", named_color); + Rgb::default() + } + }, + Color::Spec(rgb) => rgb, + Color::Indexed(index) => match colors[index as usize] { + Some(rgb) => rgb, + None => { + eprintln!("missing indexed color {}", index); + Rgb::default() + } + }, + }; + cosmic_text::Color::rgb(rgb.r, rgb.g, rgb.b) + }; + let mut attrs = default_attrs; - println!("{:?}", indexed.cell.fg); - //TODO: fg and bg + attrs = attrs.color(convert_color(indexed.cell.fg)); + // Use metadata as background color + attrs = attrs.metadata(convert_color(indexed.cell.bg).0 as usize); //TODO: more flags if indexed.cell.flags.contains(Flags::BOLD) { attrs = attrs.weight(Weight::BOLD);