From c109c605fa5a6cd3cda6f3f28782eb870b751495 Mon Sep 17 00:00:00 2001 From: Lucas Timmins Date: Thu, 25 May 2023 02:19:08 +0800 Subject: [PATCH] WIP --- Cargo.lock | 68 ++++++++++++++++++++---------- Cargo.toml | 4 ++ src/main.rs | 1 + src/positioner.rs | 44 ++++++++----------- src/renderer.rs | 85 ++++++++++++++++--------------------- src/table.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++- src/text.rs | 89 +++++++++++++++++++++++++++++++++++++++ temp.md | 4 ++ 8 files changed, 300 insertions(+), 100 deletions(-) create mode 100644 temp.md diff --git a/Cargo.lock b/Cargo.lock index c4d3724a..676a0b96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,9 +289,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "3f1e31e207a6b8fb791a38ea3105e6cb541f55e4d029902d3039a4ad07cc4105" [[package]] name = "bincode" @@ -371,9 +371,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.2" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytemuck" @@ -559,9 +559,9 @@ dependencies = [ [[package]] name = "console" -version = "0.15.6" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0525278dce688103060006713371cedbad27186c7d913f33d866b498da0f595" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" dependencies = [ "encode_unicode", "lazy_static", @@ -1122,7 +1122,7 @@ checksum = "af8d8cbea8f21307d7e84bca254772981296f058a1d36b461bf4d83a7499fc9e" dependencies = [ "fontconfig-parser", "log", - "memmap2 0.6.1", + "memmap2 0.6.2", "slotmap", "tinyvec", "ttf-parser 0.19.0", @@ -1380,6 +1380,12 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "grid" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec1c01eb1de97451ee0d60de7d81cf1e72aabefb021616027f3d1c3ec1c723c" + [[package]] name = "h2" version = "0.3.19" @@ -1643,7 +1649,7 @@ dependencies = [ "log", "lyon", "lz4_flex", - "memmap2 0.6.1", + "memmap2 0.6.2", "notify", "open", "pollster", @@ -1652,6 +1658,7 @@ dependencies = [ "resvg", "serde", "serde_yaml", + "taffy", "tiny-skia 0.9.1", "toml", "twox-hash", @@ -1707,9 +1714,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", @@ -2038,9 +2045,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0aa1b505aeecb0adb017db2b6a79a17a38e64f882a201f05e9de8a982cd6096" +checksum = "6d28bba84adfe6646737845bc5ebbfa2c08424eb1c37e94a1fd2a82adb56a872" dependencies = [ "libc", ] @@ -2947,13 +2954,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "d1a59b5d8e97dee33696bf13c5ba8ab85341c002922fba050069326b9c498974" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.1", + "regex-syntax 0.7.2", ] [[package]] @@ -2964,9 +2971,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "renderdoc-sys" @@ -3537,6 +3544,17 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "taffy" +version = "0.3.11" +source = "git+https://github.com/DioxusLabs/taffy#5443996198f02cf5a6c6493499df3cf08394a70f" +dependencies = [ + "arrayvec", + "grid", + "num-traits", + "slotmap", +] + [[package]] name = "tempfile" version = "3.5.0" @@ -3757,9 +3775,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.9" +version = "0.19.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d964908cec0d030b812013af25a0e57fddfadb1e066ecc6681d86253129d4f" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" dependencies = [ "indexmap", "serde", @@ -3883,9 +3901,9 @@ checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-linebreak" @@ -4663,9 +4681,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.11" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1690519550bfa95525229b9ca2350c63043a4857b3b0013811b2ccf4a2420b01" +checksum = "2d8f380ae16a37b30e6a2cf67040608071384b1450c189e61bea3ff57cde922d" [[package]] name = "xmlparser" @@ -4823,3 +4841,7 @@ dependencies = [ "quote", "syn 1.0.109", ] + +[[patch.unused]] +name = "cosmic-text" +version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 80425852..b4707244 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ indexmap = { version = "1.9.3", features = ["serde"] } html-escape = "0.2.13" fxhash = "0.2.1" twox-hash = "1.6.3" +taffy = { git="https://github.com/DioxusLabs/taffy" } [dependencies.glyphon] version = "0.2" @@ -67,3 +68,6 @@ lto = true [dev-dependencies] insta = "1.29.0" pretty_assertions = "1.3.0" + +[patch.crates-io] +cosmic-text = { path = "../cosmic-text" } diff --git a/src/main.rs b/src/main.rs index 8117f41b..d540a805 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +#![feature(default_free_fn)] pub mod color; pub mod fonts; pub mod image; diff --git a/src/positioner.rs b/src/positioner.rs index 5ed0f6f2..f863effa 100644 --- a/src/positioner.rs +++ b/src/positioner.rs @@ -3,7 +3,6 @@ use std::{cell::RefCell, collections::HashMap}; use anyhow::Context; use crate::{ - table::{TABLE_COL_GAP, TABLE_ROW_GAP}, text::{TextBox, TextSystem}, utils::{Align, Point, Rect, Size}, Element, @@ -107,33 +106,24 @@ impl Positioner { } Element::Table(table) => { let pos = (DEFAULT_MARGIN + centering, self.reserved_height); - let width = table - .column_widths( - text_system, - ( - self.screen_size.0 - pos.0 - DEFAULT_MARGIN - centering, - f32::INFINITY, - ), - zoom, - ) - .iter() - .fold(0., |acc, x| acc + x); - let height = table - .row_heights( - text_system, - ( - self.screen_size.0 - pos.0 - DEFAULT_MARGIN - centering, - f32::INFINITY, - ), - zoom, - ) - .iter() - .fold(0., |acc, x| acc + x); - Rect::new( - pos, + let layout = table.layout( + text_system, + ( + self.screen_size.0 - pos.0 - DEFAULT_MARGIN - centering, + f32::INFINITY, + ), + zoom, + )?; + let min = layout.first().unwrap().first().unwrap(); + let max = layout.last().unwrap().last().unwrap(); + Rect::from_min_max( + ( + DEFAULT_MARGIN + centering + min.location.x, + self.reserved_height + min.location.y, + ), ( - width * (TABLE_COL_GAP * table.headers.len() as f32), - height + (TABLE_ROW_GAP * (table.rows.len() + 1) as f32), + DEFAULT_MARGIN + centering + max.location.x + max.size.width, + self.reserved_height + max.location.y + max.size.height, ), ) } diff --git a/src/renderer.rs b/src/renderer.rs index 6f693041..fcbbbcd6 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -3,7 +3,7 @@ use crate::fonts::get_fonts; use crate::image::ImageRenderer; use crate::opts::FontOptions; use crate::positioner::{Positioned, Positioner, DEFAULT_MARGIN}; -use crate::table::{TABLE_COL_GAP, TABLE_ROW_GAP}; +use crate::table::TABLE_ROW_GAP; use crate::text::{CachedTextArea, TextCache, TextSystem}; use crate::utils::{Point, Rect, Selection, Size}; use crate::Element; @@ -358,45 +358,26 @@ impl Renderer { } } Element::Table(table) => { - let row_heights = table.row_heights( - &mut self.text_system, - ( - screen_size.0 - pos.0 - DEFAULT_MARGIN - centering, - f32::INFINITY, - ), - self.zoom, - ); - let column_widths = table.column_widths( - &mut self.text_system, - ( - screen_size.0 - pos.0 - DEFAULT_MARGIN - centering, - f32::INFINITY, - ), - self.zoom, + let bounds = ( + (screen_size.0 - pos.0 - DEFAULT_MARGIN - centering).max(0.), + f32::INFINITY, ); - let mut x = 0.; - let mut y = 0.; + let layout = table.layout(&mut self.text_system, bounds, self.zoom)?; - let header_height = row_heights.first().unwrap(); - for (col, width) in column_widths.iter().enumerate() { + for (col, node) in layout[0].iter().enumerate() { if let Some(text_box) = table.headers.get(col) { - let bounds = ( - (screen_size.0 - pos.0 - x - DEFAULT_MARGIN - centering) - .min(screen_size.0 - DEFAULT_MARGIN - centering), - f32::INFINITY, - ); text_areas.push(text_box.text_areas( &mut self.text_system, - (pos.0 + x, pos.1 + y), - bounds, + (pos.0 + node.location.x, pos.1 + node.location.y), + (node.size.width, f32::MAX), self.zoom, self.scroll_y, )); if let Some(selection) = self.selection { let (selection_rects, selection_text) = text_box.render_selection( &mut self.text_system, - (pos.0 + x, pos.1 + y), - bounds, + (pos.0 + node.location.x, pos.1 + node.location.y), + (node.size.width, node.size.height), self.zoom, selection, ); @@ -412,10 +393,17 @@ impl Renderer { )?; } } - x += width + TABLE_COL_GAP; } } - y += header_height + (TABLE_ROW_GAP / 2.); + let last_header_node = layout[0].last().unwrap(); + let y = last_header_node.location.y + + last_header_node.size.height + + TABLE_ROW_GAP / 2.; + let x = layout + .first() + .and_then(|f| f.last()) + .map(|f| f.location.x + f.size.width) + .unwrap_or(0.); { let min = ( scrolled_pos.0.max(DEFAULT_MARGIN + centering), @@ -423,7 +411,7 @@ impl Renderer { ); let max = ( (scrolled_pos.0 + x), - scrolled_pos.1 + y + 1. * self.hidpi_scale * self.zoom, + scrolled_pos.1 + y + 2. * self.hidpi_scale * self.zoom, ); self.draw_rectangle( Rect::from_min_max(min, max), @@ -431,20 +419,14 @@ impl Renderer { )?; } - y += TABLE_ROW_GAP / 2.; - for (row, height) in row_heights.iter().skip(1).enumerate() { - let mut x = 0.; - for (col, width) in column_widths.iter().enumerate() { + for (row, node_row) in layout.iter().skip(1).enumerate() { + for (col, node) in node_row.iter().enumerate() { if let Some(row) = table.rows.get(row) { if let Some(text_box) = row.get(col) { - let bounds = ( - screen_size.0 - pos.0 - x - DEFAULT_MARGIN - centering, - f32::INFINITY, - ); text_areas.push(text_box.text_areas( &mut self.text_system, - (pos.0 + x, pos.1 + y), - bounds, + (pos.0 + node.location.x, pos.1 + node.location.y), + (node.size.width, f32::MAX), self.zoom, self.scroll_y, )); @@ -453,8 +435,8 @@ impl Renderer { let (selection_rects, selection_text) = text_box .render_selection( &mut self.text_system, - (pos.0 + x, pos.1 + y), - bounds, + (pos.0 + node.location.x, pos.1 + node.location.y), + (node.size.width, node.size.height), self.zoom, selection, ); @@ -475,24 +457,29 @@ impl Renderer { } } } - x += width + TABLE_COL_GAP; } - y += height + (TABLE_COL_GAP / 2.); + let last_row_node = node_row.last().unwrap(); + let y = last_row_node.location.y + + last_row_node.size.height + + TABLE_ROW_GAP / 2.; + let x = node_row + .last() + .map(|f| f.location.x + f.size.width) + .unwrap_or(0.); { let min = ( scrolled_pos.0.max(DEFAULT_MARGIN + centering), scrolled_pos.1 + y, ); let max = ( - (scrolled_pos.0 + x), + scrolled_pos.0 + x, scrolled_pos.1 + y + 1. * self.hidpi_scale * self.zoom, ); self.draw_rectangle( Rect::from_min_max(min, max), - native_color(self.theme.code_block_color, &self.surface_format), + native_color(self.theme.text_color, &self.surface_format), )?; } - y += TABLE_ROW_GAP / 2.; } } Element::Image(_) => {} diff --git a/src/table.rs b/src/table.rs index 54a08ae4..58554591 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1,8 +1,20 @@ use crate::{ - text::{Text, TextBox, TextSystem}, + text::{Text, TextBox, TextBoxMeasure, TextCache, TextSystem}, utils::{Point, Rect, Size}, }; +use std::{ + default::default, + sync::{Arc, Mutex}, +}; + +use glyphon::FontSystem; +use taffy::{ + prelude::{auto, length, Display, Layout, Size as TaffySize, Style, Taffy}, + style::AvailableSpace, + tree::MeasureFunc, +}; + pub const TABLE_ROW_GAP: f32 = 20.; pub const TABLE_COL_GAP: f32 = 20.; @@ -125,6 +137,97 @@ impl Table { heights } + pub fn layout( + &self, + _text_system: &mut TextSystem, + bounds: Size, + zoom: f32, + ) -> anyhow::Result>> { + let mut taffy = Taffy::new(); + let max_columns = self.rows.iter().fold(self.headers.len(), |max, row| { + if row.len() > max { + row.len() + } else { + max + } + }); + + // Setup the grid + let root_style = Style { + display: Display::Grid, + size: TaffySize { + width: auto(), + height: auto(), + }, + gap: TaffySize { + width: length(TABLE_COL_GAP), + height: length(TABLE_ROW_GAP), + }, + grid_template_columns: vec![auto(); max_columns], + ..default() + }; + + let font_system = Arc::new(Mutex::new(FontSystem::new())); + let text_cache = Arc::new(Mutex::new(TextCache::new())); + let mut nodes = Vec::new(); + let mut node_row = Vec::new(); + // Define the child nodes + for (_, header) in self.headers.iter().enumerate() { + node_row.push(taffy.new_leaf_with_measure( + Style { ..default() }, + MeasureFunc::Boxed(Box::new(TextBoxMeasure { + font_system: font_system.clone(), + text_cache: text_cache.clone(), + textbox: Arc::new(header.clone()), + zoom, + })), + )?); + } + nodes.push(node_row.clone()); + node_row.clear(); + + for (_, row) in self.rows.iter().enumerate() { + for (_, item) in row.iter().enumerate() { + let item = item.clone(); + node_row.push(taffy.new_leaf_with_measure( + Style { ..default() }, + MeasureFunc::Boxed(Box::new(TextBoxMeasure { + font_system: font_system.clone(), + text_cache: text_cache.clone(), + textbox: Arc::new(item.clone()), + zoom, + })), + )?); + } + nodes.push(node_row.clone()); + node_row.clear(); + } + + let mut flattened_nodes = Vec::new(); + for row in &nodes { + flattened_nodes.append(&mut row.clone()); + } + + let root = taffy.new_with_children(root_style, &flattened_nodes)?; + + taffy + .compute_layout( + root, + TaffySize:: { + width: AvailableSpace::Definite(bounds.0), + height: AvailableSpace::Definite(bounds.1), + }, + ) + .unwrap(); + let mut layouts = Vec::new(); + + for row in nodes { + layouts.push(row.iter().map(|n| *taffy.layout(*n).unwrap()).collect()) + } + + Ok(layouts) + } + pub fn push_header(&mut self, header: TextBox) { self.headers.push(header); } diff --git a/src/text.rs b/src/text.rs index 8cb88ac8..51d47640 100644 --- a/src/text.rs +++ b/src/text.rs @@ -2,6 +2,7 @@ use std::{ borrow::BorrowMut, collections::hash_map, hash::{BuildHasher, Hash, Hasher}, + sync::{Arc, Mutex}, }; use fxhash::{FxHashMap, FxHashSet}; @@ -9,12 +10,100 @@ use glyphon::{ cosmic_text::Align as TextAlign, Affinity, Attrs, AttrsList, BufferLine, Color, Cursor, FamilyOwned, FontSystem, Style, SwashCache, TextArea, TextBounds, Weight, }; +use taffy::prelude::Size as TaffySize; +use taffy::{style::AvailableSpace, tree::Measurable}; use crate::utils::{Align, Line, Point, Rect, Selection, Size}; type KeyHash = u64; type HashBuilder = twox_hash::RandomXxHashBuilder64; +pub struct TextBoxMeasure { + pub textbox: Arc, + pub text_cache: Arc>, + pub font_system: Arc>, + pub zoom: f32, +} + +impl Measurable for TextBoxMeasure { + fn measure( + &self, + known_dimensions: TaffySize>, + available_space: TaffySize, + ) -> TaffySize { + let size = move |bounds: (f32, f32)| { + if self.textbox.texts.is_empty() { + return ( + 0., + self.textbox.padding_height * self.textbox.hidpi_scale * self.zoom, + ); + } + + let mut cache = self.text_cache.lock().unwrap(); + + let line_height = self.textbox.line_height(self.zoom); + + let (_, paragraph) = cache.borrow_mut().allocate( + self.font_system.lock().unwrap().borrow_mut(), + self.textbox.key(bounds, self.zoom), + ); + + let (total_lines, max_width) = paragraph + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); + + ( + max_width, + total_lines as f32 * line_height + + self.textbox.padding_height * self.textbox.hidpi_scale * self.zoom, + ) + }; + let mut bounds = (0., f32::MAX); + match available_space.width { + AvailableSpace::Definite(space) => { + bounds.0 = space; + } + AvailableSpace::MinContent => { + bounds.0 = 0.; + } + AvailableSpace::MaxContent => { + bounds.0 = f32::MAX; + } + } + match known_dimensions { + TaffySize { + width: None, + height: Some(height), + } => { + let size = size((bounds.0, f32::MAX)); + return TaffySize:: { + width: size.0.min(bounds.0), + height, + }; + } + TaffySize { + width: Some(width), + height: None, + } => { + let size = size((bounds.0, f32::MAX)); + return TaffySize:: { + width, + height: size.1, + }; + } + _ => {} + } + let size = size(bounds); + TaffySize:: { + width: size.0.min(bounds.0), + height: size.1, + } + } +} + #[derive(Clone, Debug, Default)] pub struct TextBox { pub indent: f32, diff --git a/temp.md b/temp.md new file mode 100644 index 00000000..6802060a --- /dev/null +++ b/temp.md @@ -0,0 +1,4 @@ +| Syntax | Description | +| ----------- | ----------- | +| Header | Title | +| Paragraph | Text | \ No newline at end of file