From f50d6b1551dc08e18f6edd561433602607b79ca2 Mon Sep 17 00:00:00 2001 From: minchenlee <110967458+minchenlee@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:59:57 +0800 Subject: [PATCH 1/3] fix: make markdown tables responsive --- src/app.rs | 16 +- src/render.rs | 481 ++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 437 insertions(+), 60 deletions(-) diff --git a/src/app.rs b/src/app.rs index 4185d53..9516445 100644 --- a/src/app.rs +++ b/src/app.rs @@ -724,6 +724,7 @@ pub enum Message { OverlayScrolled(iced::widget::scrollable::Viewport), VaultScrolled(iced::widget::scrollable::Viewport), BodyScrolled(iced::widget::scrollable::Viewport), + TableScrolled, ScrollerTick, CopyCode(String), SidebarDragStart, @@ -4016,6 +4017,7 @@ impl App { self.diagram_theme_id, true, (0, 0), + recently_scrolled, )); if truncated { col = col.push( @@ -4234,8 +4236,12 @@ impl App { self.diagram_theme_id, false, preview_widget_generation, + recently_scrolled, ) - .map(|_| Message::Noop); + .map(|message| match message { + Message::TableScrolled => Message::TableScrolled, + _ => Message::Noop, + }); let mut preview = Column::new().push(rendered); if *truncated { preview = preview.push( @@ -7063,6 +7069,10 @@ impl App { } Task::none() } + Message::TableScrolled => { + self.last_scroll_at = Some(std::time::Instant::now()); + Task::none() + } Message::CopyCode(s) => { let toast = self.show_toast("Copied".into()); Task::batch([iced::clipboard::write::(s), toast]) @@ -8232,6 +8242,7 @@ impl App { self.diagram_theme_id, true, (0, 0), + recently_scrolled, ) } } else { @@ -8249,6 +8260,7 @@ impl App { self.diagram_theme_id, true, (0, 0), + recently_scrolled, ) }; if self.view_mode == ViewMode::Raw || self.view_mode == ViewMode::Mindmap { @@ -10975,7 +10987,7 @@ fn sidebar_titlebar_reserve_for_fullscreen(fullscreen: bool) -> f32 { } } -fn sleek_scrollable_style( +pub(crate) fn sleek_scrollable_style( status: scrollable::Status, pal: Palette, recently_scrolled: bool, diff --git a/src/render.rs b/src/render.rs index 3d5b5ec..0f1db55 100644 --- a/src/render.rs +++ b/src/render.rs @@ -3,11 +3,13 @@ use crate::ast::{Block, BlockId, Inline, ListItem}; use crate::diagram::{DiagramCache, DiagramState}; use crate::keyed_body::{KeyedBody, RowKey}; use crate::theme::{Palette, Typography}; +use iced::advanced::widget::{tree, Operation, Tree, Widget}; +use iced::advanced::{layout, mouse, overlay, renderer, Clipboard, Layout, Shell}; use iced::widget::{ - container, image as image_widget, mouse_area, rich_text, row, span, stack, svg as svg_widget, - text, tooltip, Column, Space, + container, image as image_widget, mouse_area, rich_text, row, scrollable, span, stack, + svg as svg_widget, text, tooltip, Column, Space, }; -use iced::{Element, Length, Padding}; +use iced::{Background, Element, Event, Length, Padding, Rectangle, Size, Vector}; use std::path::Path; pub fn block_anchor_id(id: BlockId) -> iced::widget::Id { @@ -35,12 +37,14 @@ pub fn render<'a>( diagram_theme_id: u32, keyed_widget_reuse: bool, keyed_widget_generation: (u64, u64), + recently_scrolled: bool, ) -> Element<'a, Message> { let img_ctx = ImgCtx { cache: image_cache, current_file, diagram_cache, diagram_theme_id, + recently_scrolled, }; // Which blocks to materialize: the precomputed fold-aware window (body // view; built in `App::update`, never here), or the full fold-aware list @@ -117,6 +121,7 @@ fn render_heading_with_chevron<'a>( current_file: None, diagram_cache: dcache, diagram_theme_id: 0, + recently_scrolled: false, }; let head = render_block(b, pal, typ, query, current_in_block, &img); let glyph = if folded { @@ -526,6 +531,7 @@ struct ImgCtx<'a> { current_file: Option<&'a Path>, diagram_cache: &'a DiagramCache, diagram_theme_id: u32, + recently_scrolled: bool, } fn render_block<'a>( @@ -542,6 +548,7 @@ fn render_block<'a>( counter: 0, current_in_block, pal: *pal, + recently_scrolled: img.recently_scrolled, }; match b { Block::Heading { level, inlines, .. } => { @@ -742,6 +749,7 @@ struct HlCtx<'a> { counter: usize, current_in_block: Option, pal: Palette, + recently_scrolled: bool, } fn inline_spans<'a>( @@ -749,10 +757,20 @@ fn inline_spans<'a>( pal: &Palette, size: f32, ctx: &mut HlCtx<'_>, +) -> Vec> { + inline_spans_with_style(inlines, pal, size, Style::default(), ctx) +} + +fn inline_spans_with_style<'a>( + inlines: &'a [Inline], + pal: &Palette, + size: f32, + style: Style, + ctx: &mut HlCtx<'_>, ) -> Vec> { let mut out = Vec::new(); for i in inlines { - push_span(i, &mut out, pal, size, Style::default(), ctx); + push_span(i, &mut out, pal, size, style.clone(), ctx); } out } @@ -1317,84 +1335,431 @@ fn render_table<'a>( .max(1); let pal_t = *pal; - let make_cell = |content: Element<'a, Message>, is_header: bool| -> Element<'a, Message> { + let make_cell = |content: Element<'a, Message>| -> Element<'a, Message> { container(content) - .padding(Padding::from([8, 12])) - .width(Length::FillPortion(1)) - .style(move |_| container::Style { - background: if is_header { - Some(pal_t.surface_alt.into()) - } else { - None - }, - ..Default::default() - }) + .padding(Padding::from([9, 12])) + .width(Length::Fill) + .clip(true) .into() }; - let mut header_row = iced::widget::Row::new().spacing(0); - for i in 0..cols { - let content: Element<'a, Message> = if let Some(cell) = headers.get(i) { - let spans = inline_spans(cell, pal, typ.body_size, ctx); + let mut cells = Vec::with_capacity((rows.len() + 1) * cols); + for column_index in 0..cols { + let content: Element<'a, Message> = if let Some(cell) = headers.get(column_index) { + let spans = inline_spans_with_style( + cell, + pal, + typ.body_size, + Style { + bold: true, + ..Style::default() + }, + ctx, + ); rich_text_links(spans) } else { text("").into() }; - header_row = header_row.push(make_cell(content, true)); + cells.push(make_cell(content)); } - let mut grid = Column::new().spacing(0); - grid = grid.push( - container(header_row) - .style(move |_| container::Style { - border: iced::Border { - color: pal_t.code_border, - width: 1.0, - radius: 0.0.into(), - }, - ..Default::default() - }) - .width(Length::Fill), - ); - for row_cells in rows { - let mut r = iced::widget::Row::new().spacing(0); - for i in 0..cols { - let content: Element<'a, Message> = if let Some(cell) = row_cells.get(i) { + for column_index in 0..cols { + let content: Element<'a, Message> = if let Some(cell) = row_cells.get(column_index) { let spans = inline_spans(cell, pal, typ.body_size, ctx); rich_text_links(spans) } else { text("").into() }; - r = r.push(make_cell(content, false)); + cells.push(make_cell(content)); } - grid = grid.push( - container(r) - .style(move |_| container::Style { - border: iced::Border { - color: pal_t.code_border, - width: 1.0, - radius: 0.0.into(), - }, - ..Default::default() - }) - .width(Length::Fill), - ); } - container(grid) + let table = AdaptiveTable::new(cols, cells, pal_t).width(Length::Shrink); + let recently_scrolled = ctx.recently_scrolled; + + scrollable(table) .width(Length::Fill) - .style(move |_| container::Style { - border: iced::Border { - color: pal_t.code_border, - width: 1.0, - radius: 6.0.into(), - }, - ..Default::default() + .height(Length::Shrink) + .on_scroll(|_| Message::TableScrolled) + .style(move |_, status| { + crate::app::sleek_scrollable_style(status, pal_t, recently_scrolled) }) + .direction(scrollable::Direction::Horizontal( + scrollable::Scrollbar::new() + .width(6.0) + .scroller_width(6.0) + .margin(2.0), + )) .into() } +/// A content-driven table layout with a responsive minimum width. +/// +/// Iced's stock table makes its first shrink column fluid. That is useful for +/// ordinary tables, but it prevents a table inside a horizontal scrollable +/// from exposing its intrinsic width. This layout measures every column, +/// fills the available document width when it can, and grows beyond it only +/// when the content actually needs more room. +struct AdaptiveTable<'a, Message, Theme, Renderer> { + columns: usize, + cells: Vec>, + width: Length, + height: Length, + separator_x: f32, + separator_y: f32, + palette: Palette, +} + +struct AdaptiveTableMetrics { + columns: Vec, + rows: Vec, +} + +impl<'a, Message, Theme, Renderer> AdaptiveTable<'a, Message, Theme, Renderer> { + const MIN_COLUMN_WIDTH: f32 = 128.0; + const MAX_COLUMN_WIDTH: f32 = 320.0; + + fn new( + columns: usize, + cells: Vec>, + palette: Palette, + ) -> Self { + Self { + columns: columns.max(1), + cells, + width: Length::Shrink, + height: Length::Shrink, + separator_x: 1.0, + separator_y: 1.0, + palette, + } + } + + fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } +} + +impl Widget + for AdaptiveTable<'_, Message, Theme, Renderer> +where + Renderer: renderer::Renderer, +{ + fn size(&self) -> Size { + Size { + width: self.width, + height: self.height, + } + } + + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(AdaptiveTableMetrics { + columns: Vec::new(), + rows: Vec::new(), + }) + } + + fn children(&self) -> Vec { + self.cells + .iter() + .map(|cell| Tree::new(cell.as_widget())) + .collect() + } + + fn diff(&self, tree: &mut Tree) { + tree.diff_children(&self.cells); + } + + fn layout( + &mut self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let metrics = tree.state.downcast_mut::(); + let columns = self.columns; + let row_count = (self.cells.len() + columns - 1) / columns; + let table_limits = limits.width(self.width).height(self.height); + let measure_max_width = table_limits.max().width; + + // First pass: measure the widest cell in every column without a + // wrapping width. The cap keeps a single URL or token from turning + // the entire document into an effectively unusable canvas. + let measure_limits = + layout::Limits::new(Size::ZERO, Size::new(measure_max_width, f32::INFINITY)) + .width(Length::Shrink); + let mut column_widths = vec![0.0_f32; columns]; + + for (index, (cell, state)) in self.cells.iter_mut().zip(&mut tree.children).enumerate() { + let column = index % columns; + let node = cell + .as_widget_mut() + .layout(state, renderer, &measure_limits); + column_widths[column] = + column_widths[column].max(node.size().width.min(Self::MAX_COLUMN_WIDTH)); + } + + for width in &mut column_widths { + *width = width + .max(Self::MIN_COLUMN_WIDTH) + .min(Self::MAX_COLUMN_WIDTH); + } + + let separators_width = self.separator_x * columns.saturating_sub(1) as f32; + let natural_width = column_widths.iter().sum::() + separators_width; + let viewport_width = table_limits.min().width; + + // Horizontal scrollables give their child an infinite maximum width + // but preserve the viewport as the minimum. This makes short tables + // fill the document column while long tables keep their intrinsic + // width and become horizontally scrollable. + if natural_width < viewport_width { + let extra = (viewport_width - natural_width) / columns as f32; + for width in &mut column_widths { + *width += extra; + } + } + + // Second pass: lay out every cell against its shared column width. + // This is what keeps all rows aligned and lets rich text wrap inside + // a cell instead of painting past its boundary. + let mut cells = Vec::with_capacity(self.cells.len()); + cells.resize(self.cells.len(), layout::Node::default()); + let mut row_heights = vec![0.0_f32; row_count]; + + for (index, (cell, state)) in self.cells.iter_mut().zip(&mut tree.children).enumerate() { + let row = index / columns; + let column = index % columns; + let width = column_widths[column]; + let cell_limits = layout::Limits::new(Size::ZERO, Size::new(width, f32::INFINITY)) + .width(Length::Fixed(width)) + .height(Length::Shrink); + let node = cell.as_widget_mut().layout(state, renderer, &cell_limits); + row_heights[row] = row_heights[row].max(node.size().height); + cells[index] = node; + } + + metrics.columns = column_widths.clone(); + metrics.rows = row_heights.clone(); + + let table_width = column_widths.iter().sum::() + separators_width; + let table_height = + row_heights.iter().sum::() + self.separator_y * row_count.saturating_sub(1) as f32; + + let mut y = 0.0; + for row in 0..row_count { + let mut x = 0.0; + for column in 0..columns { + let index = row * columns + column; + if let Some(cell) = cells.get_mut(index) { + cell.move_to_mut((x, y)); + } + x += column_widths[column] + self.separator_x; + } + y += row_heights[row] + self.separator_y; + } + + let size = table_limits.resolve( + self.width, + self.height, + Size::new(table_width, table_height), + ); + + layout::Node::with_children(size, cells) + } + + fn update( + &mut self, + tree: &mut Tree, + event: &Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) { + for ((cell, state), layout) in self + .cells + .iter_mut() + .zip(&mut tree.children) + .zip(layout.children()) + { + cell.as_widget_mut().update( + state, event, layout, cursor, renderer, clipboard, shell, viewport, + ); + } + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + let metrics = tree.state.downcast_ref::(); + let mut y = bounds.y; + + // Draw row surfaces independently from cell content. That keeps the + // header and body fills continuous even when a row's tallest cell is + // taller than its neighbors. + for (row, height) in metrics.rows.iter().copied().enumerate() { + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x, + y, + width: bounds.width, + height, + }, + snap: true, + ..Default::default() + }, + if row == 0 { + Background::Color(self.palette.surface_alt) + } else { + Background::Color(self.palette.surface) + }, + ); + y += height + self.separator_y; + } + + for ((cell, state), layout) in self.cells.iter().zip(&tree.children).zip(layout.children()) + { + cell.as_widget() + .draw(state, renderer, theme, style, layout, cursor, viewport); + } + + let line = self.palette.rule; + let mut x = bounds.x; + for width in metrics + .columns + .iter() + .copied() + .take(metrics.columns.len().saturating_sub(1)) + { + x += width; + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x, + y: bounds.y, + width: self.separator_x, + height: bounds.height, + }, + snap: true, + ..Default::default() + }, + line, + ); + x += self.separator_x; + } + + let mut y = bounds.y; + for height in metrics + .rows + .iter() + .copied() + .take(metrics.rows.len().saturating_sub(1)) + { + y += height; + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x, + y, + width: bounds.width, + height: self.separator_y, + }, + snap: true, + ..Default::default() + }, + line, + ); + y += self.separator_y; + } + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.cells + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|((cell, state), layout)| { + cell.as_widget() + .mouse_interaction(state, layout, cursor, viewport, renderer) + }) + .max() + .unwrap_or_default() + } + + fn operate( + &mut self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation, + ) { + for ((cell, state), layout) in self + .cells + .iter_mut() + .zip(&mut tree.children) + .zip(layout.children()) + { + cell.as_widget_mut() + .operate(state, layout, renderer, operation); + } + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'b>, + renderer: &Renderer, + viewport: &Rectangle, + translation: Vector, + ) -> Option> { + overlay::from_children( + &mut self.cells, + tree, + layout, + renderer, + viewport, + translation, + ) + } +} + +impl<'a, Message, Theme, Renderer> From> + for Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: 'a, + Renderer: 'a + renderer::Renderer, +{ + fn from(table: AdaptiveTable<'a, Message, Theme, Renderer>) -> Self { + Element::new(table) + } +} + pub fn style_color(s: crate::ast::HlStyle, pal: &Palette) -> iced::Color { use crate::ast::HlStyle::*; let sx = &pal.syntax; From 2c85b76f313fbca4ca2e0cb55ec94a1b7503a8fb Mon Sep 17 00:00:00 2001 From: minchenlee <110967458+minchenlee@users.noreply.github.com> Date: Wed, 22 Jul 2026 17:08:27 +0800 Subject: [PATCH 2/3] fix: measure table cells intrinsically --- src/render.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/render.rs b/src/render.rs index 0f1db55..58c1eb3 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1338,7 +1338,11 @@ fn render_table<'a>( let make_cell = |content: Element<'a, Message>| -> Element<'a, Message> { container(content) .padding(Padding::from([9, 12])) - .width(Length::Fill) + // The adaptive table supplies the shared fixed width during its + // second layout pass. Keeping this intrinsic pass shrink-wrapped + // prevents an unbounded horizontal scroll limit from inflating + // short columns to MAX_COLUMN_WIDTH. + .width(Length::Shrink) .clip(true) .into() }; From ef87b4672e80bd43d57652c7f343e1f246ca2de7 Mon Sep 17 00:00:00 2001 From: minchenlee <110967458+minchenlee@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:41:13 +0800 Subject: [PATCH 3/3] fix: keep table cell measurement intrinsic --- src/render.rs | 220 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 210 insertions(+), 10 deletions(-) diff --git a/src/render.rs b/src/render.rs index 58c1eb3..b79fff2 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1336,15 +1336,7 @@ fn render_table<'a>( let pal_t = *pal; let make_cell = |content: Element<'a, Message>| -> Element<'a, Message> { - container(content) - .padding(Padding::from([9, 12])) - // The adaptive table supplies the shared fixed width during its - // second layout pass. Keeping this intrinsic pass shrink-wrapped - // prevents an unbounded horizontal scroll limit from inflating - // short columns to MAX_COLUMN_WIDTH. - .width(Length::Shrink) - .clip(true) - .into() + AdaptiveTableCell::new(content).into() }; let mut cells = Vec::with_capacity((rows.len() + 1) * cols); @@ -1398,6 +1390,202 @@ fn render_table<'a>( .into() } +/// A table cell that switches between intrinsic measurement and the shared +/// fixed-column layout supplied by [`AdaptiveTable`]. +/// +/// The table's first pass marks its limits as shrink-wrapped. In that pass the +/// cell uses `Length::Shrink`, so an unbounded horizontal scroll limit cannot +/// make every non-empty column report the same maximum width. The final pass +/// uses fixed limits, and the cell switches to `Length::Fill` so the content +/// wraps against the column width and all rows stay aligned. +struct AdaptiveTableCell<'a, Message, Theme, Renderer> { + content: Element<'a, Message, Theme, Renderer>, + padding: Padding, + clip: bool, +} + +impl<'a, Message, Theme, Renderer> AdaptiveTableCell<'a, Message, Theme, Renderer> { + fn new(content: Element<'a, Message, Theme, Renderer>) -> Self { + Self { + content, + padding: Padding::from([9, 12]), + clip: true, + } + } +} + +fn adaptive_table_cell_width(limits: &layout::Limits) -> Length { + if limits.compression().width { + Length::Shrink + } else { + Length::Fill + } +} + +impl Widget + for AdaptiveTableCell<'_, Message, Theme, Renderer> +where + Renderer: renderer::Renderer, +{ + fn tag(&self) -> tree::Tag { + self.content.as_widget().tag() + } + + fn state(&self) -> tree::State { + self.content.as_widget().state() + } + + fn children(&self) -> Vec { + self.content.as_widget().children() + } + + fn diff(&self, tree: &mut Tree) { + self.content.as_widget().diff(tree); + } + + fn size(&self) -> Size { + Size { + width: Length::Fill, + height: Length::Shrink, + } + } + + fn layout( + &mut self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + // `AdaptiveTable` passes compressed limits during its intrinsic + // measurement pass and fixed limits during final layout. + let width = adaptive_table_cell_width(limits); + + container::layout( + limits, + width, + Length::Shrink, + f32::INFINITY, + f32::INFINITY, + self.padding, + iced::alignment::Horizontal::Left, + iced::alignment::Vertical::Top, + |limits| self.content.as_widget_mut().layout(tree, renderer, limits), + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + if let Some(clipped_viewport) = bounds.intersection(viewport) { + self.content.as_widget().draw( + tree, + renderer, + theme, + style, + layout.children().next().unwrap(), + cursor, + if self.clip { + &clipped_viewport + } else { + viewport + }, + ); + } + } + + fn update( + &mut self, + tree: &mut Tree, + event: &Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) { + self.content.as_widget_mut().update( + tree, + event, + layout.children().next().unwrap(), + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.content.as_widget().mouse_interaction( + tree, + layout.children().next().unwrap(), + cursor, + viewport, + renderer, + ) + } + + fn operate( + &mut self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation, + ) { + self.content.as_widget_mut().operate( + tree, + layout.children().next().unwrap(), + renderer, + operation, + ); + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'b>, + renderer: &Renderer, + viewport: &Rectangle, + translation: Vector, + ) -> Option> { + self.content.as_widget_mut().overlay( + tree, + layout.children().next().unwrap(), + renderer, + viewport, + translation, + ) + } +} + +impl<'a, Message, Theme, Renderer> From> + for Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: 'a, + Renderer: 'a + renderer::Renderer, +{ + fn from(cell: AdaptiveTableCell<'a, Message, Theme, Renderer>) -> Self { + Element::new(cell) + } +} + /// A content-driven table layout with a responsive minimum width. /// /// Iced's stock table makes its first shrink column fluid. That is useful for @@ -1784,7 +1972,19 @@ pub fn style_color(s: crate::ast::HlStyle, pal: &Palette) -> iced::Color { #[cfg(test)] mod tests { - use super::{for_text_runs, is_cjk_fallback_char}; + use super::{adaptive_table_cell_width, for_text_runs, is_cjk_fallback_char}; + use iced::advanced::layout; + use iced::{Length, Size}; + + #[test] + fn table_cell_intrinsic_measurement_does_not_fill_unbounded_limits() { + let measure_limits = layout::Limits::new(Size::ZERO, Size::INFINITE).width(Length::Shrink); + assert_eq!(adaptive_table_cell_width(&measure_limits), Length::Shrink); + + let fixed_limits = layout::Limits::new(Size::ZERO, Size::new(256.0, f32::INFINITY)) + .width(Length::Fixed(256.0)); + assert_eq!(adaptive_table_cell_width(&fixed_limits), Length::Fill); + } #[test] fn hangul_jamo_boundaries_use_cjk_fallback() {