diff --git a/.vscode/settings.json b/.vscode/settings.json index 2e0cb08..f51ec10 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,17 +20,20 @@ "hitbox", "inlyne", "Interactor", + "JETBRAINS", "mult", "multisampled", "multiview", "rasterizer", "reqwest", + "rpass", "rsqrt", "shortcodes", "SSEDT", "striked", "Syntect", "tasklist", + "termwiz", "texel", "texels", "textbox", diff --git a/README.md b/README.md index 8824713..a9da36d 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ cargo run --release --example markdown ## Acronyms used in the code. +- DR: Decision Record. - OO: Optimization Opportunity - NI: Naming Issue - DI: Design Issue (e.g. something does not seem to belong here) diff --git a/examples/code/examples/code-viewer.rs b/examples/code/examples/code-viewer.rs index 6568517..16807d5 100644 --- a/examples/code/examples/code-viewer.rs +++ b/examples/code/examples/code-viewer.rs @@ -10,7 +10,7 @@ use massive_scene::PositionedShape; use massive_shell::{shell, ApplicationContext}; use shared::{ application::{Application, UpdateResponse}, - code_viewer::{self, AttributedCode}, + attributed_text::{self, AttributedText}, }; const CANVAS_ID: &str = "massive-code"; @@ -60,7 +60,7 @@ async fn code_viewer(mut ctx: ApplicationContext) -> Result<()> { // let code: AttributedCode = // serde_json::from_str(&fs::read_to_string("/tmp/code.json").unwrap()).unwrap(); - let code: AttributedCode = postcard::from_bytes(include_bytes!("code.postcard")).unwrap(); + let code: AttributedText = postcard::from_bytes(include_bytes!("code.postcard")).unwrap(); // Shape and layout text. @@ -69,12 +69,13 @@ async fn code_viewer(mut ctx: ApplicationContext) -> Result<()> { // let font_size = 16.; // let line_height = 20.; - let (glyph_runs, height) = code_viewer::shape_text( + let (glyph_runs, height) = attributed_text::shape_text( &mut font_system, &code.text, &code.attributes, font_size, line_height, + None, ); // Camera @@ -98,16 +99,17 @@ async fn code_viewer(mut ctx: ApplicationContext) -> Result<()> { ) .await?; - let mut application = Application::new(SizeI::new(1280, height as u64)); - let mut current_matrix = application.matrix(); + let page_size = SizeI::new(1280, height as u64); + let mut application = Application::default(); + let mut current_matrix = application.matrix(page_size); let matrix = director.cast(current_matrix); let position = director.cast(matrix.clone().into()); // Hold the positioned shapes in this context, otherwise they will disappear. - let _positioned_shapes: Vec<_> = glyph_runs - .into_iter() - .map(|run| director.cast(PositionedShape::new(position.clone(), run))) - .collect(); + let _positioned_shape = director.cast(PositionedShape::new( + position.clone(), + glyph_runs.into_iter().map(|m| m.into()).collect::>(), + )); director.action()?; @@ -123,7 +125,7 @@ async fn code_viewer(mut ctx: ApplicationContext) -> Result<()> { // DI: This check has to be done in the renderer and the renderer has to decide when it // needs to redraw. - let new_matrix = application.matrix(); + let new_matrix = application.matrix(page_size); if new_matrix != current_matrix { matrix.update(new_matrix); current_matrix = new_matrix; diff --git a/examples/code/examples/code.rs b/examples/code/examples/code.rs index c06f372..bcb3803 100644 --- a/examples/code/examples/code.rs +++ b/examples/code/examples/code.rs @@ -19,7 +19,7 @@ use load_cargo::{LoadCargoConfig, ProcMacroServerChoice}; use project_model::CargoConfig; use shared::{ application::{Application, UpdateResponse}, - code_viewer, + attributed_text, }; use syntax::{AstNode, SyntaxKind, WalkEvent}; use tracing::info; @@ -27,7 +27,7 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilte use vfs::VfsPath; use winit::dpi::LogicalSize; -use crate::code_viewer::TextAttribute; +use crate::attributed_text::TextAttribute; use massive_geometry::{Camera, Color, SizeI}; use massive_scene::PositionedShape; use massive_shapes::TextWeight; @@ -225,7 +225,7 @@ async fn application(mut ctx: ApplicationContext) -> Result<()> { // Store for the web viewer. - let attributed_code = code_viewer::AttributedCode { + let attributed_code = attributed_text::AttributedText { text: file_text.to_string(), attributes: attributes.clone(), }; @@ -249,12 +249,13 @@ async fn application(mut ctx: ApplicationContext) -> Result<()> { // let font_size = 16.; // let line_height = 20.; - let (glyph_runs, height) = code_viewer::shape_text( + let (glyph_runs, height) = attributed_text::shape_text( &mut font_system, &file_text, &attributes, font_size, line_height, + None, ); // Window @@ -272,7 +273,8 @@ async fn application(mut ctx: ApplicationContext) -> Result<()> { // Application - let mut application = Application::new(SizeI::new(1280, height as u64)); + let page_size = SizeI::new(1280, height as u64); + let mut application = Application::default(); let font_system = Arc::new(Mutex::new(font_system)); @@ -280,14 +282,17 @@ async fn application(mut ctx: ApplicationContext) -> Result<()> { .new_renderer(font_system, camera, initial_size) .await?; - let mut current_matrix = application.matrix(); + let mut current_matrix = application.matrix(page_size); let matrix = director.cast(current_matrix); let position = director.cast(matrix.clone().into()); - let _positioned_shapes: Vec<_> = glyph_runs - .into_iter() - .map(|run| director.cast(PositionedShape::new(position.clone(), run))) - .collect(); + let _positioned_shape = director.cast(PositionedShape::new( + position.clone(), + glyph_runs + .into_iter() + .map(|run| run.into()) + .collect::>(), + )); director.action()?; @@ -303,7 +308,7 @@ async fn application(mut ctx: ApplicationContext) -> Result<()> { // DI: This check has to be done in the renderer and the renderer has to decide when it // needs to redraw. - let new_matrix = application.matrix(); + let new_matrix = application.matrix(page_size); if new_matrix != current_matrix { matrix.update(new_matrix); current_matrix = new_matrix; diff --git a/examples/logs/Cargo.toml b/examples/logs/Cargo.toml new file mode 100644 index 0000000..5b1cc96 --- /dev/null +++ b/examples/logs/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "logs" +version = "0.1.0" +edition = "2021" + +[dev-dependencies] +termwiz = "0.22.0" + +shared = { path = "../shared" } + +massive-geometry = { workspace = true } +massive-shell = { workspace = true } +massive-scene = { workspace = true } +massive-shapes = { workspace = true } + +anyhow = { workspace = true } +cosmic-text = { workspace = true } +log = { workspace = true } +winit = { workspace = true } +env_logger = { workspace = true } +tokio = { workspace = true } diff --git a/examples/logs/examples/logs.rs b/examples/logs/examples/logs.rs new file mode 100644 index 0000000..c5edecc --- /dev/null +++ b/examples/logs/examples/logs.rs @@ -0,0 +1,410 @@ +use std::{ + collections::VecDeque, + io, iter, + ops::Range, + sync::{Arc, Mutex}, +}; + +use anyhow::Result; +use cosmic_text::{fontdb, FontSystem}; +use env_logger::{Builder, Target, WriteStyle}; +use log::error; +use termwiz::{ + cell::Intensity, + color::ColorSpec, + escape::{self, csi::Sgr, Action, ControlCode, CSI}, +}; +use tokio::{ + select, + sync::mpsc::{self, UnboundedReceiver}, +}; +use winit::dpi::LogicalSize; + +use logs::terminal::{color_schemes, Rgb}; +use massive_geometry::{Camera, Color, Identity, Vector3}; +use massive_scene::{Matrix, Position, PositionedShape}; +use massive_shapes::TextWeight; +use massive_shell::{shell, ApplicationContext}; +use shared::{ + application::{Application, UpdateResponse}, + attributed_text::{self, TextAttribute}, +}; + +const CANVAS_ID: &str = "massive-logs"; + +fn main() -> Result<()> { + let (sender, receiver) = mpsc::unbounded_channel(); + + Builder::default() + .filter(Some("massive_shell"), log::LevelFilter::Info) + .write_style(WriteStyle::Always) + .target(Target::Pipe(Box::new(Sender(sender)))) + .init(); + + shared::main(|| async_main(receiver)) +} + +struct Sender(mpsc::UnboundedSender>); + +impl io::Write for Sender { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0 + .send(buf.to_vec()) + .map_err(|_| io::Error::from(io::ErrorKind::BrokenPipe))?; + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +async fn async_main(receiver: UnboundedReceiver>) -> Result<()> { + shell::run(|ctx| logs(receiver, ctx)).await +} + +async fn logs(mut receiver: UnboundedReceiver>, mut ctx: ApplicationContext) -> Result<()> { + error!("TEST"); + + let font_system = { + let mut db = fontdb::Database::new(); + db.load_font_data(shared::fonts::JETBRAINS_MONO.to_vec()); + // Use an invariant locale. + FontSystem::new_with_locale_and_db("en-US".into(), db) + }; + + // Window + + let window_size = LogicalSize::new(1280., 800.); + + let window = ctx.new_window(window_size, Some(CANVAS_ID))?; + + // Camera + + let camera = { + let fovy: f64 = 45.0; + let camera_distance = 1.0 / (fovy / 2.0).to_radians().tan(); + Camera::new((0.0, 0.0, camera_distance), (0.0, 0.0, 0.0)) + }; + + let font_system = Arc::new(Mutex::new(font_system)); + + let (mut renderer, mut director) = window + .new_renderer(font_system.clone(), camera, window.inner_size()) + .await?; + + // Application + + let mut page_size = (1280u32, 1); + let mut application = Application::default(); + let mut current_matrix = application.matrix(page_size); + let page_matrix = director.cast(current_matrix); + let page_position = director.cast(Position::from(page_matrix.clone())); + // We move up the lines by their top position. + let move_up_matrix = director.cast(Matrix::identity()); + + // Final position for all lines (runs are y-translated, but only increasing). + let position = director.cast(Position { + parent: Some(page_position), + matrix: move_up_matrix.clone(), + }); + + let mut y = 0.; + + let max_lines = 100; + + // Hold the positioned lines, otherwise they will disappear. + let mut positioned_lines = VecDeque::new(); + + loop { + select! { + Some(bytes) = receiver + .recv() => { + let (new_runs, height) = { + let mut font_system = font_system.lock().unwrap(); + + shape_log_line(&bytes, y, &mut font_system) + }; + + let line = director.cast(PositionedShape::new(position.clone(), new_runs.into_iter().map( + |run| run.into()).collect::>())); + + positioned_lines.push_back((y, height, line)); + + while positioned_lines.len() > max_lines { + positioned_lines.pop_front(); + }; + + // Update page size. + + let top_line = positioned_lines.front().unwrap(); + move_up_matrix.update(Matrix::from_translation((0., -top_line.0, 0.).into())); + let last_line = positioned_lines.back().unwrap(); + page_size.1 = (last_line.0 + last_line.1 - top_line.0) as u32; + + director.action()?; + + y += height; + }, + + Ok(window_event) = ctx.wait_for_event(&mut renderer) => { + match application.update(window_event) { + UpdateResponse::Exit => return Ok(()), + UpdateResponse::Continue => {} + } + + // DI: This check has to be done in the renderer and the renderer has to decide when it + // needs to redraw. + let new_matrix = application.matrix(page_size); + if new_matrix != current_matrix { + page_matrix.update(new_matrix); + current_matrix = new_matrix; + director.action()?; + } + } + } + } +} + +fn shape_log_line( + bytes: &[u8], + y: f64, + font_system: &mut FontSystem, +) -> (Vec, f64) { + // OO: Share Parser between runs. + let mut parser = escape::parser::Parser::new(); + let parsed = parser.parse_as_vec(bytes); + + // OO: Share Processor between runs. + let mut processor = Processor::new(color_schemes::light::PAPER); + for action in parsed { + processor.process(action) + } + + let (text, attributes) = processor.into_text_and_attribute_ranges(); + + let font_size = 32.; + let line_height = 40.; + + let (runs, height) = attributed_text::shape_text( + font_system, + &text, + &attributes, + font_size, + line_height, + Vector3::new(0., y, 0.), + ); + (runs, height) +} + +#[derive(Debug)] +struct Processor { + default: Attributes, + current: Attributes, + color_scheme: color_schemes::Scheme, + text: String, + text_attributes: Vec, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +struct Attributes { + pub foreground_color: Color, + pub bold: bool, +} + +impl Processor { + pub fn new(color_scheme: color_schemes::Scheme) -> Self { + let default_attributes = Attributes { + foreground_color: rgb_to_color(color_scheme.primary.foreground), + bold: false, + }; + + Self { + default: default_attributes, + current: default_attributes, + color_scheme, + text: String::new(), + // TODO: Not quite efficient storing the attributes for each u8 inside a string. + text_attributes: Vec::new(), + } + } + + pub fn into_text_and_attribute_ranges(self) -> (String, Vec) { + // TODO: this is something like a slicetools candidate. AFAI(and ChatGPT)K all solutions to + // this problem are either inefficient (generate intermediate Vecs) or hard to read. + + let mut ranges: Vec = Vec::new(); + + if self.text_attributes.is_empty() { + return (self.text, Vec::new()); + } + + let mut current_start = 0; + + for i in 1..self.text_attributes.len() { + let prev = &self.text_attributes[i - 1]; + if *prev != self.text_attributes[i] { + ranges.push(ta(current_start..i, prev)); + current_start = i; + } + } + + ranges.push(ta( + current_start..self.text_attributes.len(), + &self.text_attributes[current_start], + )); + + return (self.text, ranges); + + fn ta(range: Range, attr: &Attributes) -> TextAttribute { + TextAttribute { + range, + color: attr.foreground_color, + weight: if attr.bold { + TextWeight::BOLD + } else { + TextWeight::NORMAL + }, + } + } + } + + pub fn process(&mut self, action: escape::Action) { + match action { + Action::Print(ch) => { + self.text.push(ch); + self.text_attributes.push(self.current) + } + Action::PrintString(string) => { + self.text.push_str(&string); + self.text_attributes + .extend(iter::repeat(self.current).take(string.len())) + } + Action::Control(control) => match control { + ControlCode::Null => {} + ControlCode::StartOfHeading => {} + ControlCode::StartOfText => {} + ControlCode::EndOfText => {} + ControlCode::EndOfTransmission => {} + ControlCode::Enquiry => {} + ControlCode::Acknowledge => {} + ControlCode::Bell => {} + ControlCode::Backspace => {} + ControlCode::HorizontalTab => {} + ControlCode::LineFeed => { + self.text.push('\n'); + self.text_attributes.push(self.current); + } + ControlCode::VerticalTab => {} + ControlCode::FormFeed => {} + ControlCode::CarriageReturn => { + self.text.push('\r'); + self.text_attributes.push(self.current); + } + ControlCode::ShiftOut => {} + ControlCode::ShiftIn => {} + ControlCode::DataLinkEscape => {} + ControlCode::DeviceControlOne => {} + ControlCode::DeviceControlTwo => {} + ControlCode::DeviceControlThree => {} + ControlCode::DeviceControlFour => {} + ControlCode::NegativeAcknowledge => {} + ControlCode::SynchronousIdle => {} + ControlCode::EndOfTransmissionBlock => {} + ControlCode::Cancel => {} + ControlCode::EndOfMedium => {} + ControlCode::Substitute => {} + ControlCode::Escape => {} + ControlCode::FileSeparator => {} + ControlCode::GroupSeparator => {} + ControlCode::RecordSeparator => {} + ControlCode::UnitSeparator => {} + ControlCode::BPH => {} + ControlCode::NBH => {} + ControlCode::IND => {} + ControlCode::NEL => {} + ControlCode::SSA => {} + ControlCode::ESA => {} + ControlCode::HTS => {} + ControlCode::HTJ => {} + ControlCode::VTS => {} + ControlCode::PLD => {} + ControlCode::PLU => {} + ControlCode::RI => {} + ControlCode::SS2 => {} + ControlCode::SS3 => {} + ControlCode::DCS => {} + ControlCode::PU1 => {} + ControlCode::PU2 => {} + ControlCode::STS => {} + ControlCode::CCH => {} + ControlCode::MW => {} + ControlCode::SPA => {} + ControlCode::EPA => {} + ControlCode::SOS => {} + ControlCode::SCI => {} + ControlCode::CSI => {} + ControlCode::ST => {} + ControlCode::OSC => {} + ControlCode::PM => {} + ControlCode::APC => {} + }, + Action::DeviceControl(_) => {} + Action::OperatingSystemCommand(_) => {} + Action::CSI(csi) => match csi { + CSI::Sgr(sgr) => match sgr { + Sgr::Reset => self.current = self.default, + Sgr::Intensity(intensity) => match intensity { + Intensity::Normal => self.current.bold = false, + Intensity::Bold => self.current.bold = true, + Intensity::Half => {} + }, + Sgr::Underline(_) => {} + Sgr::UnderlineColor(_) => {} + Sgr::Blink(_) => {} + Sgr::Italic(_) => {} + Sgr::Inverse(_) => {} + Sgr::Invisible(_) => {} + Sgr::StrikeThrough(_) => {} + Sgr::Font(_) => {} + Sgr::Foreground(foreground) => match foreground { + ColorSpec::Default => { + self.current.foreground_color = self.default.foreground_color + } + ColorSpec::PaletteIndex(index) => { + // TODO: this panics if the index is out of range. + let rgb = if index > 7 { + self.color_scheme.bright[(index - 8) as _] + } else { + self.color_scheme.normal[index as _] + }; + + self.current.foreground_color = rgb_to_color(rgb); + } + ColorSpec::TrueColor(_) => {} + }, + Sgr::Background(_) => {} + Sgr::Overline(_) => {} + Sgr::VerticalAlign(_) => {} + }, + CSI::Cursor(_) => {} + CSI::Edit(_) => {} + CSI::Mode(_) => {} + CSI::Device(_) => {} + CSI::Mouse(_) => {} + CSI::Window(_) => {} + CSI::Keyboard(_) => {} + CSI::SelectCharacterPath(_, _) => {} + CSI::Unspecified(_) => {} + }, + Action::Esc(_) => {} + Action::Sixel(_) => {} + Action::XtGetTcap(_) => {} + Action::KittyImage(_) => {} + } + } +} + +fn rgb_to_color(value: Rgb) -> Color { + (value.r, value.g, value.b).into() +} diff --git a/examples/logs/src/lib.rs b/examples/logs/src/lib.rs new file mode 100644 index 0000000..a566381 --- /dev/null +++ b/examples/logs/src/lib.rs @@ -0,0 +1 @@ +pub mod terminal; diff --git a/examples/logs/src/terminal/color.rs b/examples/logs/src/terminal/color.rs new file mode 100644 index 0000000..147f5f2 --- /dev/null +++ b/examples/logs/src/terminal/color.rs @@ -0,0 +1,273 @@ +use std::{ops::Mul, str::FromStr}; + +// use super::NamedColor; + +// pub const COUNT: usize = 269; + +// pub const RED: Rgb = Rgb { +// r: 0xff, +// g: 0x0, +// b: 0x0, +// }; +// pub const YELLOW: Rgb = Rgb { +// r: 0xff, +// g: 0xff, +// b: 0x0, +// }; + +#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)] +pub struct Rgb { + pub r: u8, + pub g: u8, + pub b: u8, +} + +// a multiply function for Rgb, as the default dim is just *2/3 +impl Mul for Rgb { + type Output = Rgb; + + fn mul(self, rhs: f32) -> Rgb { + Rgb { + r: (f32::from(self.r) * rhs).clamp(0.0, 255.0) as u8, + g: (f32::from(self.g) * rhs).clamp(0.0, 255.0) as u8, + b: (f32::from(self.b) * rhs).clamp(0.0, 255.0) as u8, + } + } +} + +impl FromStr for Rgb { + type Err = (); + + fn from_str(s: &str) -> ::std::result::Result { + let mut chars = s.chars(); + let mut rgb = Rgb::default(); + + macro_rules! component { + ($($c:ident),*) => { + $( + match chars.next().and_then(|c| c.to_digit(16)) { + Some(val) => rgb.$c = (val as u8) << 4, + None => return Err(()) + } + + match chars.next().and_then(|c| c.to_digit(16)) { + Some(val) => rgb.$c |= val as u8, + None => return Err(()) + } + )* + } + } + + match chars.next() { + Some('0') => { + if chars.next() != Some('x') { + return Err(()); + } + } + Some('#') => (), + _ => return Err(()), + } + + component!(r, g, b); + + Ok(rgb) + } +} + +// /// List of indexed colors +// /// +// /// The first 16 entries are the standard ansi named colors. Items 16..232 are +// /// the color cube. Items 233..256 are the grayscale ramp. Item 256 is +// /// the configured foreground color, item 257 is the configured background +// /// color, item 258 is the cursor color. Following that are 8 positions for dim colors. +// /// Item 267 is the bright foreground color, 268 the dim foreground. +// #[derive(Copy, Clone)] +// pub struct List([Rgb; COUNT]); + +// impl<'a> From<&'a Colors> for List { +// fn from(colors: &Colors) -> List { +// // Type inference fails without this annotation +// let mut list = List([Rgb::default(); COUNT]); + +// list.fill_named(colors); +// list.fill_cube(colors); +// list.fill_gray_ramp(colors); + +// list +// } +// } + +// impl List { +// pub fn fill_named(&mut self, colors: &Colors) { +// // Normals +// self[NamedColor::Black] = colors.normal().black; +// self[NamedColor::Red] = colors.normal().red; +// self[NamedColor::Green] = colors.normal().green; +// self[NamedColor::Yellow] = colors.normal().yellow; +// self[NamedColor::Blue] = colors.normal().blue; +// self[NamedColor::Magenta] = colors.normal().magenta; +// self[NamedColor::Cyan] = colors.normal().cyan; +// self[NamedColor::White] = colors.normal().white; + +// // Br +// self[NamedColor::BrightBlack] = colors.bright().black; +// self[NamedColor::BrightRed] = colors.bright().red; +// self[NamedColor::BrightGreen] = colors.bright().green; +// self[NamedColor::BrightYellow] = colors.bright().yellow; +// self[NamedColor::BrightBlue] = colors.bright().blue; +// self[NamedColor::BrightMagenta] = colors.bright().magenta; +// self[NamedColor::BrightCyan] = colors.bright().cyan; +// self[NamedColor::BrightWhite] = colors.bright().white; +// self[NamedColor::BrightForeground] = colors +// .primary +// .bright_foreground +// .unwrap_or(colors.primary.foreground); + +// // Foreground and background +// self[NamedColor::Foreground] = colors.primary.foreground; +// self[NamedColor::Background] = colors.primary.background; + +// // Background for custom cursor colors +// self[NamedColor::Cursor] = colors.cursor.cursor.unwrap_or_else(Rgb::default); + +// // Dims +// self[ansi::NamedColor::DimForeground] = colors +// .primary +// .dim_foreground +// .unwrap_or(colors.primary.foreground * 0.66); +// match colors.dim { +// Some(ref dim) => { +// self[ansi::NamedColor::DimBlack] = dim.black; +// self[ansi::NamedColor::DimRed] = dim.red; +// self[ansi::NamedColor::DimGreen] = dim.green; +// self[ansi::NamedColor::DimYellow] = dim.yellow; +// self[ansi::NamedColor::DimBlue] = dim.blue; +// self[ansi::NamedColor::DimMagenta] = dim.magenta; +// self[ansi::NamedColor::DimCyan] = dim.cyan; +// self[ansi::NamedColor::DimWhite] = dim.white; +// } +// None => { +// self[ansi::NamedColor::DimBlack] = colors.normal().black * 0.66; +// self[ansi::NamedColor::DimRed] = colors.normal().red * 0.66; +// self[ansi::NamedColor::DimGreen] = colors.normal().green * 0.66; +// self[ansi::NamedColor::DimYellow] = colors.normal().yellow * 0.66; +// self[ansi::NamedColor::DimBlue] = colors.normal().blue * 0.66; +// self[ansi::NamedColor::DimMagenta] = colors.normal().magenta * 0.66; +// self[ansi::NamedColor::DimCyan] = colors.normal().cyan * 0.66; +// self[ansi::NamedColor::DimWhite] = colors.normal().white * 0.66; +// } +// } +// } + +// pub fn fill_cube(&mut self, colors: &Colors) { +// let mut index: usize = 16; +// // Build colors +// for r in 0..6 { +// for g in 0..6 { +// for b in 0..6 { +// // Override colors 16..232 with the config (if present) +// if let Some(indexed_color) = colors +// .indexed_colors +// .iter() +// .find(|ic| ic.index == index as u8) +// { +// self[index] = indexed_color.color; +// } else { +// self[index] = Rgb { +// r: if r == 0 { 0 } else { r * 40 + 55 }, +// b: if b == 0 { 0 } else { b * 40 + 55 }, +// g: if g == 0 { 0 } else { g * 40 + 55 }, +// }; +// } +// index += 1; +// } +// } +// } + +// debug_assert!(index == 232); +// } + +// pub fn fill_gray_ramp(&mut self, colors: &Colors) { +// let mut index: usize = 232; + +// for i in 0..24 { +// // Index of the color is number of named colors + number of cube colors + i +// let color_index = 16 + 216 + i; + +// // Override colors 232..256 with the config (if present) +// if let Some(indexed_color) = colors +// .indexed_colors +// .iter() +// .find(|ic| ic.index == color_index) +// { +// self[index] = indexed_color.color; +// index += 1; +// continue; +// } + +// let value = i * 10 + 8; +// self[index] = Rgb { +// r: value, +// g: value, +// b: value, +// }; +// index += 1; +// } + +// debug_assert!(index == 256); +// } +// } + +// impl fmt::Debug for List { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// f.write_str("List[..]") +// } +// } + +// impl Index for List { +// type Output = Rgb; + +// #[inline] +// fn index(&self, idx: ansi::NamedColor) -> &Self::Output { +// &self.0[idx as usize] +// } +// } + +// impl IndexMut for List { +// #[inline] +// fn index_mut(&mut self, idx: ansi::NamedColor) -> &mut Self::Output { +// &mut self.0[idx as usize] +// } +// } + +// impl Index for List { +// type Output = Rgb; + +// #[inline] +// fn index(&self, idx: usize) -> &Self::Output { +// &self.0[idx] +// } +// } + +// impl IndexMut for List { +// #[inline] +// fn index_mut(&mut self, idx: usize) -> &mut Self::Output { +// &mut self.0[idx] +// } +// } + +// impl Index for List { +// type Output = Rgb; + +// #[inline] +// fn index(&self, idx: u8) -> &Self::Output { +// &self.0[idx as usize] +// } +// } + +// impl IndexMut for List { +// #[inline] +// fn index_mut(&mut self, idx: u8) -> &mut Self::Output { +// &mut self.0[idx as usize] +// } +// } diff --git a/examples/logs/src/terminal/color_schemes.rs b/examples/logs/src/terminal/color_schemes.rs new file mode 100644 index 0000000..2883d8b --- /dev/null +++ b/examples/logs/src/terminal/color_schemes.rs @@ -0,0 +1,103 @@ +use super::{AnsiColors, Rgb}; + +#[derive(Debug)] +pub struct Scheme { + pub primary: Primary, + pub normal: AnsiColors, + pub bright: AnsiColors, +} + +#[derive(Debug)] +pub struct Primary { + pub background: Rgb, + pub foreground: Rgb, +} + +pub mod light { + use super::{ac, rgb, Primary, Scheme}; + + pub const PENCIL: Scheme = Scheme { + primary: Primary { + background: rgb(0x424242), + foreground: rgb(0xf1f1f1), + }, + normal: ac([ + 0x212121, 0xc30771, 0x10a778, 0xa89c14, 0x008ec4, 0x523c79, 0x20a5ba, 0xe0e0e0, + ]), + bright: ac([ + 0x212121, 0xfb007a, 0x5fd7af, 0xf3e430, 0x20bbfc, 0x6855de, 0x4fb8cc, 0xf1f1f1, + ]), + }; + + pub const SEABIRD: Scheme = Scheme { + primary: Primary { + background: rgb(0xffffff), + foreground: rgb(0x61707a), + }, + normal: ac([ + 0x0b141a, 0xff4053, 0x11ab00, 0xbf8c00, 0x0099ff, 0x9854ff, 0x00a5ab, 0xffffff, + ]), + bright: ac([ + 0x0b141a, 0xff4053, 0x11ab00, 0xbf8c00, 0x0099ff, 0x9854ff, 0x00a5ab, 0xffffff, + ]), + }; + + pub const SOLARIZED: Scheme = Scheme { + primary: Primary { + background: rgb(0xfdf6e3), + foreground: rgb(0x657b83), + }, + normal: ac([ + 0x073642, 0xdc322f, 0x859900, 0xb58900, 0x268bd2, 0xd33682, 0x2aa198, 0xeee8d5, + ]), + bright: ac([ + 0x002b36, 0xcb4b16, 0x586e75, 0x657b83, 0x839496, 0x6c71c4, 0x93a1a1, 0xfdf6e3, + ]), + }; + + // https://github.com/NLKNguyen/papercolor-theme/blob/master/colors/PaperColor.vim + pub const PAPER: Scheme = Scheme { + primary: Primary { + background: rgb(0xffffff), + foreground: rgb(0x000000), + }, + normal: ac([ + 0xeeeeee, 0xaf0000, 0x008700, 0x5f8700, 0x0087af, 0x878787, 0x005f87, 0x444444, + ]), + bright: ac([ + 0xbcbcbc, 0xd70000, 0xd70087, 0x8700af, 0xd75f00, 0xd75f00, 0x005faf, 0x005f87, + ]), + }; +} + +const fn ac(colors: [u32; 8]) -> AnsiColors { + AnsiColors { + black: rgb(colors[0]), + red: rgb(colors[1]), + green: rgb(colors[2]), + yellow: rgb(colors[3]), + blue: rgb(colors[4]), + magenta: rgb(colors[5]), + cyan: rgb(colors[6]), + white: rgb(colors[7]), + } +} + +const fn rgb(x: u32) -> Rgb { + Rgb { + r: (x >> 16) as u8, + g: (x >> 8) as u8, + b: x as u8, + } +} + +// impl From for List { +// fn from(s: Scheme) -> Self { +// let mut colors = Colors::default(); +// colors.primary.foreground = s.primary.foreground; +// colors.primary.background = s.primary.background; +// colors.normal = NormalColors(s.normal); +// colors.bright = BrightColors(s.bright); +// Self::from(&colors) +// } +// } diff --git a/examples/logs/src/terminal/config.rs b/examples/logs/src/terminal/config.rs new file mode 100644 index 0000000..a722e95 --- /dev/null +++ b/examples/logs/src/terminal/config.rs @@ -0,0 +1,204 @@ +use std::ops::Index; + +use super::Rgb; + +#[derive(Debug, Default, PartialEq, Eq)] +pub struct Colors { + pub primary: PrimaryColors, + pub cursor: CursorColors, + pub selection: SelectionColors, + pub(crate) normal: NormalColors, + pub(crate) bright: BrightColors, + pub dim: Option, + pub indexed_colors: Vec, +} + +// impl Colors { +// pub fn normal(&self) -> &AnsiColors { +// &self.normal.0 +// } + +// pub fn bright(&self) -> &AnsiColors { +// &self.bright.0 +// } +// } + +#[derive(Default, Debug, PartialEq, Eq)] +pub struct IndexedColor { + pub index: u8, + pub color: Rgb, +} + +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] +pub struct CursorColors { + pub text: Option, + pub cursor: Option, +} + +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] +pub struct SelectionColors { + pub text: Option, + pub background: Option, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct PrimaryColors { + pub background: Rgb, + pub foreground: Rgb, + pub bright_foreground: Option, + pub dim_foreground: Option, +} + +impl Default for PrimaryColors { + fn default() -> Self { + PrimaryColors { + background: default_background(), + foreground: default_foreground(), + bright_foreground: Default::default(), + dim_foreground: Default::default(), + } + } +} + +fn default_background() -> Rgb { + Rgb { r: 0, g: 0, b: 0 } +} + +fn default_foreground() -> Rgb { + Rgb { + r: 0xea, + g: 0xea, + b: 0xea, + } +} + +/// The 8-colors sections of config +#[derive(Debug, PartialEq, Eq)] +pub struct AnsiColors { + pub black: Rgb, + pub red: Rgb, + pub green: Rgb, + pub yellow: Rgb, + pub blue: Rgb, + pub magenta: Rgb, + pub cyan: Rgb, + pub white: Rgb, +} + +impl Index for AnsiColors { + type Output = Rgb; + + fn index(&self, index: usize) -> &Self::Output { + match index { + 0 => &self.black, + 1 => &self.red, + 2 => &self.green, + 3 => &self.yellow, + 4 => &self.blue, + 5 => &self.magenta, + 6 => &self.cyan, + 7 => &self.white, + _ => panic!("Invalid color index: {index}"), + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct NormalColors(pub(crate) AnsiColors); + +impl Default for NormalColors { + fn default() -> Self { + NormalColors(AnsiColors { + black: Rgb { + r: 0x00, + g: 0x00, + b: 0x00, + }, + red: Rgb { + r: 0xd5, + g: 0x4e, + b: 0x53, + }, + green: Rgb { + r: 0xb9, + g: 0xca, + b: 0x4a, + }, + yellow: Rgb { + r: 0xe6, + g: 0xc5, + b: 0x47, + }, + blue: Rgb { + r: 0x7a, + g: 0xa6, + b: 0xda, + }, + magenta: Rgb { + r: 0xc3, + g: 0x97, + b: 0xd8, + }, + cyan: Rgb { + r: 0x70, + g: 0xc0, + b: 0xba, + }, + white: Rgb { + r: 0xea, + g: 0xea, + b: 0xea, + }, + }) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct BrightColors(pub(crate) AnsiColors); + +impl Default for BrightColors { + fn default() -> Self { + BrightColors(AnsiColors { + black: Rgb { + r: 0x66, + g: 0x66, + b: 0x66, + }, + red: Rgb { + r: 0xff, + g: 0x33, + b: 0x34, + }, + green: Rgb { + r: 0x9e, + g: 0xc4, + b: 0x00, + }, + yellow: Rgb { + r: 0xe7, + g: 0xc5, + b: 0x47, + }, + blue: Rgb { + r: 0x7a, + g: 0xa6, + b: 0xda, + }, + magenta: Rgb { + r: 0xb7, + g: 0x7e, + b: 0xe0, + }, + cyan: Rgb { + r: 0x54, + g: 0xce, + b: 0xd6, + }, + white: Rgb { + r: 0xff, + g: 0xff, + b: 0xff, + }, + }) + } +} diff --git a/examples/logs/src/terminal/mod.rs b/examples/logs/src/terminal/mod.rs new file mode 100644 index 0000000..1e8686d --- /dev/null +++ b/examples/logs/src/terminal/mod.rs @@ -0,0 +1,11 @@ +//! This module contains types and color schemes useful to process colored terminal output. +//! source: + +mod color; +pub mod color_schemes; +mod config; +mod named_color; + +pub use color::Rgb; +pub use config::AnsiColors; +pub use named_color::NamedColor; diff --git a/examples/logs/src/terminal/named_color.rs b/examples/logs/src/terminal/named_color.rs new file mode 100644 index 0000000..a703e74 --- /dev/null +++ b/examples/logs/src/terminal/named_color.rs @@ -0,0 +1,129 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Standard colors +/// +/// The order here matters since the enum should be castable to a `usize` for +/// indexing a color list. +#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] +pub enum NamedColor { + /// Black + Black = 0, + /// Red + Red, + /// Green + Green, + /// Yellow + Yellow, + /// Blue + Blue, + /// Magenta + Magenta, + /// Cyan + Cyan, + /// White + White, + /// Bright black + BrightBlack, + /// Bright red + BrightRed, + /// Bright green + BrightGreen, + /// Bright yellow + BrightYellow, + /// Bright blue + BrightBlue, + /// Bright magenta + BrightMagenta, + /// Bright cyan + BrightCyan, + /// Bright white + BrightWhite, + /// The foreground color + Foreground = 256, + /// The background color + Background, + /// Color for the cursor itself + Cursor, + /// Dim black + DimBlack, + /// Dim red + DimRed, + /// Dim green + DimGreen, + /// Dim yellow + DimYellow, + /// Dim blue + DimBlue, + /// Dim magenta + DimMagenta, + /// Dim cyan + DimCyan, + /// Dim white + DimWhite, + /// The bright foreground color + BrightForeground, + /// Dim foreground + DimForeground, +} + +impl NamedColor { + pub fn to_bright(self) -> Self { + match self { + NamedColor::Foreground => NamedColor::BrightForeground, + NamedColor::Black => NamedColor::BrightBlack, + NamedColor::Red => NamedColor::BrightRed, + NamedColor::Green => NamedColor::BrightGreen, + NamedColor::Yellow => NamedColor::BrightYellow, + NamedColor::Blue => NamedColor::BrightBlue, + NamedColor::Magenta => NamedColor::BrightMagenta, + NamedColor::Cyan => NamedColor::BrightCyan, + NamedColor::White => NamedColor::BrightWhite, + NamedColor::DimForeground => NamedColor::Foreground, + NamedColor::DimBlack => NamedColor::Black, + NamedColor::DimRed => NamedColor::Red, + NamedColor::DimGreen => NamedColor::Green, + NamedColor::DimYellow => NamedColor::Yellow, + NamedColor::DimBlue => NamedColor::Blue, + NamedColor::DimMagenta => NamedColor::Magenta, + NamedColor::DimCyan => NamedColor::Cyan, + NamedColor::DimWhite => NamedColor::White, + val => val, + } + } + + pub fn to_dim(self) -> Self { + match self { + NamedColor::Black => NamedColor::DimBlack, + NamedColor::Red => NamedColor::DimRed, + NamedColor::Green => NamedColor::DimGreen, + NamedColor::Yellow => NamedColor::DimYellow, + NamedColor::Blue => NamedColor::DimBlue, + NamedColor::Magenta => NamedColor::DimMagenta, + NamedColor::Cyan => NamedColor::DimCyan, + NamedColor::White => NamedColor::DimWhite, + NamedColor::Foreground => NamedColor::DimForeground, + NamedColor::BrightBlack => NamedColor::Black, + NamedColor::BrightRed => NamedColor::Red, + NamedColor::BrightGreen => NamedColor::Green, + NamedColor::BrightYellow => NamedColor::Yellow, + NamedColor::BrightBlue => NamedColor::Blue, + NamedColor::BrightMagenta => NamedColor::Magenta, + NamedColor::BrightCyan => NamedColor::Cyan, + NamedColor::BrightWhite => NamedColor::White, + NamedColor::BrightForeground => NamedColor::Foreground, + val => val, + } + } +} diff --git a/examples/markdown/examples/emojis.rs b/examples/markdown/examples/emojis.rs index 3151648..107806b 100644 --- a/examples/markdown/examples/emojis.rs +++ b/examples/markdown/examples/emojis.rs @@ -168,16 +168,20 @@ async fn emojis(mut ctx: ApplicationContext) -> Result<()> { // Application - let mut application = Application::new(SizeI::new(page_width as _, page_height)); - let mut current_matrix = application.matrix(); + let page_size = SizeI::new(page_width as _, page_height); + let mut application = Application::default(); + let mut current_matrix = application.matrix(page_size); let matrix = director.cast(current_matrix); let position = director.cast(matrix.clone().into()); // Hold the positioned shapes in this context, otherwise they will disappear. - let _positioned_shapes: Vec<_> = glyph_runs - .into_iter() - .map(|run| director.cast(PositionedShape::new(position.clone(), run))) - .collect(); + let _positioned_shape = director.cast(PositionedShape::new( + position.clone(), + glyph_runs + .into_iter() + .map(|run| run.into()) + .collect::>(), + )); director.action()?; @@ -193,7 +197,7 @@ async fn emojis(mut ctx: ApplicationContext) -> Result<()> { // DI: This check has to be done in the renderer and the renderer has to decide when it // needs to redraw. - let new_matrix = application.matrix(); + let new_matrix = application.matrix(page_size); if new_matrix != current_matrix { matrix.update(new_matrix); current_matrix = new_matrix; diff --git a/examples/markdown/examples/markdown.rs b/examples/markdown/examples/markdown.rs index 583eb01..24114af 100644 --- a/examples/markdown/examples/markdown.rs +++ b/examples/markdown/examples/markdown.rs @@ -86,16 +86,19 @@ async fn application(mut ctx: ApplicationContext) -> Result<()> { markdown, )?; - let mut application = Application::new(page_size); - let mut current_matrix = application.matrix(); + let mut application = Application::default(); + let mut current_matrix = application.matrix(page_size); let matrix = director.cast(current_matrix); let position = director.cast(matrix.clone().into()); // Hold the positioned shapes in this context, otherwise they will disappear. - let _positioned_shapes: Vec<_> = glyph_runs - .into_iter() - .map(|run| director.cast(PositionedShape::new(position.clone(), run))) - .collect(); + let _positioned_shape = director.cast(PositionedShape::new( + position.clone(), + glyph_runs + .into_iter() + .map(|run| run.into()) + .collect::>(), + )); director.action()?; @@ -111,7 +114,7 @@ async fn application(mut ctx: ApplicationContext) -> Result<()> { // DI: This check has to be done in the renderer and the renderer has to decide when it // needs to redraw. - let new_matrix = application.matrix(); + let new_matrix = application.matrix(page_size); if new_matrix != current_matrix { matrix.update(new_matrix); current_matrix = new_matrix; diff --git a/examples/shared/src/application.rs b/examples/shared/src/application.rs index 1a83ab3..a1c4ddd 100644 --- a/examples/shared/src/application.rs +++ b/examples/shared/src/application.rs @@ -14,9 +14,8 @@ enum ActiveGesture { Rotation(RotationGesture), } +#[derive(Default)] pub struct Application { - page_size: SizeI, - gesture: Option, /// Tracked positions of all devices. @@ -30,20 +29,6 @@ pub struct Application { rotation: PointI, } -impl Application { - pub fn new(page_size: impl Into) -> Self { - Self { - page_size: page_size.into(), - gesture: None, - positions: HashMap::new(), - modifiers: Modifiers::default(), - translation: PointI::default(), - translation_z: 0, - rotation: PointI::default(), - } - } -} - struct MovementGesture { origin: PointI, translation_origin: PointI, @@ -204,11 +189,13 @@ impl Application { UpdateResponse::Continue } - pub fn matrix(&self) -> Matrix4 { + pub fn matrix(&self, page_size: impl Into) -> Matrix4 { // let mut shapes = Vec::new(); - let page_x_center: f64 = -((self.page_size.width / 2) as f64); - let page_y_center: f64 = -((self.page_size.height / 2) as f64); + let page_size = page_size.into(); + + let page_x_center: f64 = -((page_size.width / 2) as f64); + let page_y_center: f64 = -((page_size.height / 2) as f64); let center_transformation = Matrix4::from_translation((page_x_center, page_y_center, 0.0).into()); let current_translation = Matrix4::from_translation( diff --git a/examples/shared/src/code_viewer.rs b/examples/shared/src/attributed_text.rs similarity index 90% rename from examples/shared/src/code_viewer.rs rename to examples/shared/src/attributed_text.rs index 1edc73e..cfc2c2d 100644 --- a/examples/shared/src/code_viewer.rs +++ b/examples/shared/src/attributed_text.rs @@ -10,7 +10,7 @@ use crate::positioning; /// A serializable representation of highlighted code. #[derive(Debug, Serialize, Deserialize)] -pub struct AttributedCode { +pub struct AttributedText { pub text: String, pub attributes: Vec, } @@ -28,6 +28,7 @@ pub fn shape_text( attributes: &[TextAttribute], font_size: f32, line_height: f32, + translation: impl Into>, ) -> (Vec, f64) { syntax::assert_covers_all_text( &attributes @@ -59,15 +60,17 @@ pub fn shape_text( let attributes: Vec<_> = attributes.iter().map(|ta| (ta.color, ta.weight)).collect(); + let translation = translation.into().unwrap_or(Vector3::new(0., 0., 0.)); + for run in buffer.layout_runs() { // Lines are positioned on line_height. - let translation = Vector3::new(0., run.line_top as f64, 0.); + let translation = translation + Vector3::new(0., run.line_top as f64, 0.); for run in positioning::to_attributed_glyph_runs(translation, &run, line_height, &attributes) { runs.push(run); } - height = height.max(translation.y + line_height as f64); + height = height.max(run.line_top as f64 + line_height as f64); } (runs, height) diff --git a/examples/shared/src/lib.rs b/examples/shared/src/lib.rs index 82f8ebc..3c7e1f3 100644 --- a/examples/shared/src/lib.rs +++ b/examples/shared/src/lib.rs @@ -1,5 +1,5 @@ pub mod application; -pub mod code_viewer; +pub mod attributed_text; pub mod fonts; pub mod positioning; @@ -13,7 +13,8 @@ where { #[cfg(not(target_arch = "wasm32"))] { - env_logger::init(); + // Don't force initialization of the env logger (calling main may already initialized it) + let _ = env_logger::try_init(); let rt = tokio::runtime::Runtime::new().expect("Failed to create tokio runtime"); // Use the runtime to block on the async function diff --git a/examples/syntax/examples/syntax.rs b/examples/syntax/examples/syntax.rs index b3f3c09..5059e0d 100644 --- a/examples/syntax/examples/syntax.rs +++ b/examples/syntax/examples/syntax.rs @@ -16,7 +16,7 @@ use massive_shapes::TextWeight; use massive_shell::{shell, ApplicationContext}; use shared::{ application::{Application, UpdateResponse}, - code_viewer::{self, TextAttribute}, + attributed_text::{self, TextAttribute}, }; const CANVAS_ID: &str = "massive-syntax"; @@ -83,12 +83,13 @@ async fn syntax(mut ctx: ApplicationContext) -> Result<()> { Camera::new((0.0, 0.0, camera_distance), (0.0, 0.0, 0.0)) }; - let (glyph_runs, height) = code_viewer::shape_text( + let (glyph_runs, height) = attributed_text::shape_text( &mut font_system, &final_text, &text_attributes, font_size, line_height, + None, ); let font_system = Arc::new(Mutex::new(font_system)); @@ -103,16 +104,20 @@ async fn syntax(mut ctx: ApplicationContext) -> Result<()> { // Application - let mut application = Application::new((1280, height as u64)); - let mut current_matrix = application.matrix(); + let page_size = (1280, height as u64); + let mut application = Application::default(); + let mut current_matrix = application.matrix(page_size); let matrix = director.cast(current_matrix); let position = director.cast(matrix.clone().into()); // Hold the positioned shapes in this context, otherwise they will disappear. - let _positioned_shapes: Vec<_> = glyph_runs - .into_iter() - .map(|run| director.cast(PositionedShape::new(position.clone(), run))) - .collect(); + let _positioned_shape = director.cast(PositionedShape::new( + position.clone(), + glyph_runs + .into_iter() + .map(|run| run.into()) + .collect::>(), + )); director.action()?; @@ -126,7 +131,7 @@ async fn syntax(mut ctx: ApplicationContext) -> Result<()> { // DI: This check has to be done in the renderer and the renderer has to decide when it // needs to redraw. - let new_matrix = application.matrix(); + let new_matrix = application.matrix(page_size); if new_matrix != current_matrix { matrix.update(new_matrix); current_matrix = new_matrix; diff --git a/renderer/src/quads/renderer.rs b/renderer/src/quads/renderer.rs index 7e7ceb9..be9d98b 100644 --- a/renderer/src/quads/renderer.rs +++ b/renderer/src/quads/renderer.rs @@ -65,10 +65,10 @@ impl QuadsRenderer { } } - pub fn prepare( + pub fn prepare<'a>( &mut self, context: &mut PreparationContext, - shapes: &[(Matrix4, &[&Shape])], + shapes: &[(Matrix4, impl Iterator + Clone)], ) -> Result<()> { self.layers.clear(); @@ -78,7 +78,7 @@ impl QuadsRenderer { if let Some(quads_layer) = self.prepare_quads( context, matrix, - shapes.iter().filter_map(|s| match s { + shapes.clone().filter_map(|s| match s { Shape::GlyphRun(_) => None, Shape::Quads(quads) => Some(quads), }), @@ -95,12 +95,23 @@ impl QuadsRenderer { } pub fn render<'rpass>(&'rpass self, context: &mut RenderContext<'_, 'rpass>) { + let max_quads = self + .layers + .iter() + .map(|QuadsLayer { quad_count, .. }| *quad_count) + .max() + .unwrap_or_default(); + + if max_quads == 0 { + return; + } + let pass = &mut context.pass; pass.set_pipeline(&self.pipeline); // DI: May do this inside this renderer and pass a Matrix to prepare?. pass.set_bind_group(0, context.view_projection_bind_group, &[]); // DI: May share index buffers between renderers? - pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); + self.index_buffer.set(pass, max_quads); for QuadsLayer { model_matrix, diff --git a/renderer/src/renderer.rs b/renderer/src/renderer.rs index 14c5232..4fd243a 100644 --- a/renderer/src/renderer.rs +++ b/renderer/src/renderer.rs @@ -122,16 +122,16 @@ impl<'window> Renderer<'window> { font_system, }; - // OO: Lot's of allocations here. + // OO: Avoid allocations. let grouped_shapes: Vec<_> = self.scene.grouped_shapes().collect(); let pixel_matrix = self.pixel_matrix(); - // OO: Lot's of allocations here. // Group by matrix and apply the pixel matrix. + // OO: Lot's of allocations here. Modify Matrix in-place? let grouped_by_matrix: Vec<_> = grouped_shapes - .iter() - .map(|(m, v)| (pixel_matrix * *m, v.as_slice())) + .into_iter() + .map(|(m, v)| (pixel_matrix * m, v)) .collect(); // OO: parallelize? diff --git a/renderer/src/scene/mod.rs b/renderer/src/scene/mod.rs index c3a90e2..6689334 100644 --- a/renderer/src/scene/mod.rs +++ b/renderer/src/scene/mod.rs @@ -42,14 +42,14 @@ impl Scene { } /// Returns a set of grouped shape by matrix. - /// - /// TODO: This should not be &mut self, because it updates computed values only. - pub fn grouped_shapes(&self) -> impl Iterator)> { - let mut map: HashMap> = HashMap::new(); + pub fn grouped_shapes( + &self, + ) -> impl Iterator + Clone)> { + let mut map: HashMap> = HashMap::new(); for positioned in self.shapes.iter_some() { let position_id = positioned.position; - map.entry(position_id).or_default().push(&positioned.shape); + map.entry(position_id).or_default().push(&positioned.shapes); } // Update all matrices that are in use. @@ -65,10 +65,9 @@ impl Scene { let caches = self.caches.borrow(); map.into_iter().map(move |(position_id, shapes)| { - // Ensure the matrix is up2date. - // We can't return a reference to matrix, because this would also borrow `caches``. + // We can't return a reference to matrix, because this would also borrow `caches`. let matrix = *caches.positions_matrix[position_id]; - (matrix, shapes) + (matrix, shapes.into_iter().flatten()) }) } @@ -78,7 +77,7 @@ impl Scene { /// version and can be used for rendering. /// /// We don't return a reference to the result here, because the borrow checker would make this - /// recursive function invocation uncessarily more complex. + /// recursive function invocation unnecessarily more complex. /// /// TODO: Unrecurse this. There might be degenerate cases of large dependency chains. fn resolve_positioned_matrix(&self, position_id: Id, caches: &mut SceneCaches) { @@ -99,7 +98,7 @@ impl Scene { let position = self.positions.unwrapped(position_id); let (parent_id, matrix) = (position.parent, position.matrix); - // Find out the max version of all the immeidate and (indirect / computed) dependencies. + // Find out the max version of all the immediate and (indirect / computed) dependencies. // Get the _three_ versions of the elements this one is computed on. // a) The self position's version. diff --git a/renderer/src/scene/versioning.rs b/renderer/src/scene/versioning.rs index 83ed3bd..8dd7ff7 100644 --- a/renderer/src/scene/versioning.rs +++ b/renderer/src/scene/versioning.rs @@ -37,15 +37,15 @@ pub struct Computed { /// This is last the time the `max_deps_version` and computed value was validated to be /// consistent with its dependencies. /// - /// If `validated_at` is less than the curreent tick, `max_deps_version` and `value` may be + /// If `validated_at` is less than the latest tick, `max_deps_version` and `value` may be /// outdated. pub validated_at: Version, - /// The maximum version of all its dependencies. May be outdated if `checked_at` does not equals - /// the current version. + /// The maximum version of all its dependencies. May be outdated if `validated_at` does not + /// equal the latest version. pub max_deps_version: Version, - /// The value at `max_deps_version`. Because of laziness, this value may be computed at a later - /// time tick, but it always represents the result of a computation matching the dependencies at - /// `max_deps_version`. + /// The value computed value at `max_deps_version`. This value is computed on demand and may not + /// be up to date. but it always represents the result of a computation matching the + /// dependencies at `max_deps_version`. pub value: V, } diff --git a/renderer/src/text_layer/color_atlas/renderer.rs b/renderer/src/text_layer/color_atlas/renderer.rs index 63bdba3..ad6ec6a 100644 --- a/renderer/src/text_layer/color_atlas/renderer.rs +++ b/renderer/src/text_layer/color_atlas/renderer.rs @@ -1,5 +1,3 @@ -use std::mem; - use wgpu::{ util::{BufferInitDescriptor, DeviceExt}, TextureFormat, @@ -82,8 +80,9 @@ impl ColorAtlasRenderer { for instance in instances { let r = instance.atlas_rect; - // ADR: u/v normalization is dont in the shader, for once, its probably free, and scondly - // we don't have to care about the atlas texture growing as long the rects stay the same. + // ADR: u/v normalization is dont in the shader, for once, its probably free, and + // secondly we don't have to care about the atlas texture growing as long the rects stay + // the same. let (ltx, lty) = (r.min.x as f32, r.min.y as f32); let (rbx, rby) = (r.max.x as f32, r.max.y as f32); @@ -140,7 +139,7 @@ impl ColorAtlasRenderer { pass.set_bind_group(0, context.view_projection_bind_group, &[]); // DI: May share index buffers between renderers? // - // OO: Don't pass the full index buffer here, only what's actully needed (it is growing + // OO: Don't pass the full index buffer here, only what's actually needed (it is growing // only) let max_quads = batches @@ -149,12 +148,7 @@ impl ColorAtlasRenderer { .max() .unwrap_or_default(); - pass.set_index_buffer( - self.index_buffer.slice( - ..(max_quads * QuadIndexBuffer::INDICES_PER_QUAD * mem::size_of::()) as u64, - ), - wgpu::IndexFormat::Uint16, - ); + self.index_buffer.set(pass, max_quads); for QuadBatch { model_matrix, diff --git a/renderer/src/text_layer/renderer.rs b/renderer/src/text_layer/renderer.rs index f1b02d1..57596ee 100644 --- a/renderer/src/text_layer/renderer.rs +++ b/renderer/src/text_layer/renderer.rs @@ -68,21 +68,22 @@ impl TextLayerRenderer { } } - pub fn prepare( + pub fn prepare<'a>( &mut self, context: &mut PreparationContext, - shapes: &[(Matrix4, &[&Shape])], + shapes: &[(Matrix4, impl Iterator + Clone)], ) -> Result<()> { self.sdf_batches.clear(); self.color_batches.clear(); - for (matrix, shapes) in shapes { + for (matrix, ref shapes) in shapes { // NB: could deref the pointer here using unsafe. let (sdf_batch, color_batch) = self.prepare_runs( context, matrix, // DI: Move this filter up (callers should just pass here what's needed). - shapes.iter().filter_map(|s| match s { + // OO: clone() will clone the backing Vec. + shapes.clone().filter_map(|s| match s { Shape::GlyphRun(run) => Some(run), Shape::Quads(_) => None, }), diff --git a/renderer/src/text_layer/sdf_atlas/renderer.rs b/renderer/src/text_layer/sdf_atlas/renderer.rs index 4db2329..e0ab222 100644 --- a/renderer/src/text_layer/sdf_atlas/renderer.rs +++ b/renderer/src/text_layer/sdf_atlas/renderer.rs @@ -1,5 +1,3 @@ -use std::mem; - use wgpu::{ util::{BufferInitDescriptor, DeviceExt}, TextureFormat, @@ -83,8 +81,9 @@ impl SdfAtlasRenderer { for instance in instances { let r = instance.atlas_rect; - // ADR: u/v normalization is dont in the shader, for once, its probably free, and scondly - // we don't have to care about the atlas texture growing as long the rects stay the same. + // ADR: u/v normalization is dont in the shader, for once, its probably free, and + // secondly we don't have to care about the atlas texture growing as long the rects stay + // the same. let (ltx, lty) = (r.min.x as f32, r.min.y as f32); let (rbx, rby) = (r.max.x as f32, r.max.y as f32); @@ -136,13 +135,14 @@ impl SdfAtlasRenderer { return; } + // OO: This resolves to a &mut &mut, is this really needed? let pass = &mut context.pass; pass.set_pipeline(&self.pipeline); // DI: May do this inside this renderer and pass a Matrix to prepare?. pass.set_bind_group(0, context.view_projection_bind_group, &[]); // DI: May share index buffers between renderers? // - // OO: Don't pass the full index buffer here, only what's actully needed (it is growing + // OO: Don't pass the full index buffer here, only what's actually needed (it is growing // only) let max_quads = batches @@ -151,12 +151,7 @@ impl SdfAtlasRenderer { .max() .unwrap_or_default(); - pass.set_index_buffer( - self.index_buffer.slice( - ..(max_quads * QuadIndexBuffer::INDICES_PER_QUAD * mem::size_of::()) as u64, - ), - wgpu::IndexFormat::Uint16, - ); + self.index_buffer.set(pass, max_quads); for QuadBatch { model_matrix, diff --git a/renderer/src/tools/quad_index_buffer.rs b/renderer/src/tools/quad_index_buffer.rs index d473175..3af2cc2 100644 --- a/renderer/src/tools/quad_index_buffer.rs +++ b/renderer/src/tools/quad_index_buffer.rs @@ -1,15 +1,20 @@ -use std::mem::size_of_val; +use std::mem::{self, size_of_val}; use log::debug; -use wgpu::util::DeviceExt; +use wgpu::{util::DeviceExt, BufferSlice, IndexFormat, RenderPass}; -#[derive(Debug, derive_more::Deref)] +#[derive(Debug)] pub struct QuadIndexBuffer(wgpu::Buffer); +type Index = u32; + impl QuadIndexBuffer { + // OO: Use only 16 bit if not more is needed. + pub const INDEX_FORMAT: IndexFormat = IndexFormat::Uint32; + pub fn new(device: &wgpu::Device) -> Self { // OO: Provide a good initial size. - const NO_INDICES: [u16; 0] = []; + const NO_INDICES: [Index; 0] = []; Self(Self::create_buffer(device, &NO_INDICES)) } @@ -17,6 +22,18 @@ impl QuadIndexBuffer { (self.0.size() as usize) / size_of_val(Self::QUAD_INDICES) } + pub fn set<'a, 'rpass>(&'a self, pass: &mut RenderPass<'rpass>, max_quads: usize) + where + 'a: 'rpass, + { + pass.set_index_buffer(self.slice(max_quads), Self::INDEX_FORMAT) + } + + fn slice(&self, max_quads: usize) -> BufferSlice { + self.0 + .slice(..(max_quads * Self::INDICES_PER_QUAD * Self::INDEX_SIZE) as u64) + } + pub fn ensure_can_index_num_quads( &mut self, device: &wgpu::Device, @@ -42,24 +59,23 @@ impl QuadIndexBuffer { self.0 = Self::create_buffer(device, &indices); } - fn generate_array(&self, quads: usize) -> Vec { + fn generate_array(&self, quads: usize) -> Vec { let mut v = Vec::with_capacity(Self::QUAD_INDICES.len() * quads); (0..quads).for_each(|quad_index| { - v.extend( - Self::QUAD_INDICES - .iter() - .map(|i| *i + (quad_index << 2) as u16), - ) + let offset = quad_index * Self::VERTICES_PER_QUAD; + v.extend(Self::QUAD_INDICES.iter().map(|i| *i + offset as Index)) }); v } - pub const QUAD_INDICES: &'static [u16] = &[0, 1, 2, 0, 2, 3]; + pub const QUAD_INDICES: &'static [Index] = &[0, 1, 2, 0, 2, 3]; pub const INDICES_PER_QUAD: usize = Self::QUAD_INDICES.len(); + pub const VERTICES_PER_QUAD: usize = 4; + const INDEX_SIZE: usize = mem::size_of::(); - fn create_buffer(device: &wgpu::Device, indices: &[u16]) -> wgpu::Buffer { + fn create_buffer(device: &wgpu::Device, indices: &[Index]) -> wgpu::Buffer { device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Quad Index Buffer"), contents: bytemuck::cast_slice(indices), diff --git a/scene/src/objects.rs b/scene/src/objects.rs index dd425c6..e8be6c2 100644 --- a/scene/src/objects.rs +++ b/scene/src/objects.rs @@ -13,13 +13,20 @@ pub enum Shape { #[derive(Debug)] pub struct PositionedShape { pub position: Handle, - pub shape: Shape, + /// DR: Clients should be able to use [`PositionedShape`] directly as a an abstract thing. Like + /// for example a line which contains multiple Shapes (runs, quads, etc.). Therefore + /// `Vec` and not just `Shape`. + /// + /// GI: Another idea is to add `Shape::Combined(Vec)`, but this makes extraction per + /// renderer a bit more complex. This would also point to sharing Shapes as handles ... which + /// could go in direction of layout? + pub shapes: Vec, } #[derive(Debug)] pub struct PositionedRenderShape { pub position: Id, - pub shape: Shape, + pub shapes: Vec, } impl Object for PositionedShape { @@ -29,10 +36,10 @@ impl Object for PositionedShape { type Change = PositionedRenderShape; fn split(self) -> (Self::Keep, Self::Change) { - let PositionedShape { position, shape } = self; + let PositionedShape { position, shapes } = self; let shape = PositionedRenderShape { position: position.id(), - shape, + shapes, }; (position, shape) } @@ -43,14 +50,20 @@ impl Object for PositionedShape { } impl PositionedShape { - pub fn new(position: Handle, shape: impl Into) -> Self { + pub fn new(position: Handle, shapes: impl Into>) -> Self { Self { position, - shape: shape.into(), + shapes: shapes.into(), } } } +impl From for Vec { + fn from(value: Shape) -> Self { + vec![value] + } +} + #[derive(Debug, Clone)] pub struct Position { pub parent: Option>, @@ -149,10 +162,10 @@ pub mod legacy { let positioned = match shape { Shape::GlyphRun(GlyphRunShape { run, .. }) => { - PositionedShape::new(position.clone(), run) + PositionedShape::new(position.clone(), super::Shape::from(run)) } Shape::Quads(QuadsShape { quads, .. }) => { - PositionedShape::new(position.clone(), quads) + PositionedShape::new(position.clone(), super::Shape::from(quads)) } };