Created
August 27, 2024 03:51
-
-
Save videni/ad89282eb7b9cf7a18eb637a3e8e2ceb to your computer and use it in GitHub Desktop.
iced_grid_with_taffy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! A CSS Grid widget based on Taffy | |
use iced::advanced::layout; | |
use iced::advanced::layout::Node; | |
use iced::advanced::overlay; | |
use iced::advanced::renderer::{self, Renderer}; | |
use iced::advanced::widget::{ | |
tree::{self, Tree}, | |
Operation, | |
}; | |
use iced::advanced::{Clipboard, Layout, Shell, Widget}; | |
use iced::event::{self, Event}; | |
use iced::{mouse, Vector}; | |
use iced::{Element, Length, Point, Rectangle, Size}; | |
use taffy::prelude::*; | |
pub struct Grid<'a, Msg, Theme, R: Renderer> { | |
width: Length, | |
height: Length, | |
style: taffy::Style, | |
children: Vec<GridChild<'a, Msg, Theme, R>>, | |
} | |
impl<'a, Msg, Theme, R: Renderer> Grid<'a, Msg, Theme, R> { | |
pub fn new() -> Self { | |
Self { | |
width: Length::Fill, | |
height: Length::Fill, | |
style: taffy::Style { | |
display: Display::Grid, | |
..taffy::Style::DEFAULT | |
}, | |
children: vec![], | |
} | |
} | |
pub fn with_columns(mut self, columns: Vec<taffy::TrackSizingFunction>) -> Self { | |
self.style.grid_template_columns = columns; | |
self | |
} | |
pub fn with_rows(mut self, rows: Vec<taffy::TrackSizingFunction>) -> Self { | |
self.style.grid_template_rows = rows; | |
self | |
} | |
pub fn with_column_gap(mut self, gap: taffy::LengthPercentage) -> Self { | |
self.style.gap.width = gap; | |
self | |
} | |
pub fn with_row_gap(mut self, gap: taffy::LengthPercentage) -> Self { | |
self.style.gap.height = gap; | |
self | |
} | |
pub fn min_height(mut self, size: taffy::Dimension) -> Self { | |
self.style.min_size.height = size; | |
self | |
} | |
pub fn min_width(mut self, size: taffy::Dimension) -> Self { | |
self.style.min_size.width = size; | |
self | |
} | |
pub fn max_height(mut self, size: taffy::Dimension) -> Self { | |
self.style.max_size.height = size; | |
self | |
} | |
pub fn max_width(mut self, size: taffy::Dimension) -> Self { | |
self.style.max_size.width = size; | |
self | |
} | |
pub fn style(mut self, mut callback: impl FnMut(&mut taffy::Style)) -> Self { | |
callback(&mut self.style); | |
self | |
} | |
pub fn with_styled_child( | |
mut self, element: impl Into<Element<'a, Msg, Theme, R>>, mut callback: impl FnMut(&mut taffy::Style), | |
) -> Self { | |
let mut style = taffy::Style::DEFAULT; | |
callback(&mut style); | |
self.children.push(GridChild::new(element.into(), style)); | |
self | |
} | |
pub fn with_child(mut self, element: impl Into<Element<'a, Msg, Theme, R>>) -> Self { | |
self.children | |
.push(GridChild::new(element.into(), taffy::Style::DEFAULT)); | |
self | |
} | |
pub fn add_child(&mut self, element: impl Into<Element<'a, Msg, Theme, R>>) { | |
self.children | |
.push(GridChild::new(element.into(), taffy::Style::DEFAULT)); | |
} | |
/// Sets the width of the [`Grid`]. | |
pub fn width(mut self, width: Length) -> Self { | |
self.width = width; | |
self | |
} | |
/// Sets the height of the [`Grid`]. | |
pub fn height(mut self, height: Length) -> Self { | |
self.height = height; | |
self | |
} | |
pub fn compute_layout(&mut self) { | |
// compute_layout(self, NodeId::from(usize::MAX), available_space); | |
} | |
} | |
pub fn grid<'a, Msg, Theme, R: Renderer>() -> Grid<'a, Msg, Theme, R> { | |
Grid::new() | |
} | |
struct State {} | |
impl<'a, Msg, Theme, R: Renderer> Widget<Msg, Theme, R> for Grid<'a, Msg, Theme, R> { | |
fn size(&self) -> Size<Length> { | |
Size::new(self.width, self.height) | |
} | |
fn size_hint(&self) -> Size<Length> { | |
Size { | |
width: Length::Shrink, | |
height: Length::Shrink, | |
} | |
} | |
fn state(&self) -> tree::State { | |
tree::State::new(State {}) | |
} | |
fn children(&self) -> Vec<Tree> { | |
self.children.iter().map(|child| Tree::new(&child.element)).collect() | |
} | |
fn diff(&self, tree: &mut Tree) { | |
tree.diff_children( | |
&self | |
.children | |
.iter() | |
.map(|child| child.element.as_widget()) | |
.collect::<Vec<_>>(), | |
); | |
} | |
fn layout(&self, tree: &mut Tree, renderer: &R, limits: &layout::Limits) -> layout::Node { | |
// let state = tree.state.downcast_mut::<State>(); | |
let available_space: taffy::Size<AvailableSpace> = { | |
fn f32_to_opt(input: f32) -> Option<f32> { | |
if input.is_nan() || input.is_infinite() { | |
None | |
} else { | |
Some(input) | |
} | |
} | |
let size = taffy::Size { | |
width: f32_to_opt(limits.max().width), | |
height: f32_to_opt(limits.max().height), | |
}; | |
size.map(|s| s.into()) | |
}; | |
let mut taffy: TaffyTree<()> = TaffyTree::new(); | |
let children = self | |
.children | |
.iter() | |
.map(|child| { | |
let mut tree = Tree::new(&child.element); | |
let layout = child.element.as_widget().layout(&mut tree, renderer, &limits); | |
let size = taffy::Size { | |
width: length(layout.size().width), | |
height: length(layout.size().height), | |
}; | |
( | |
taffy | |
.new_leaf(taffy::Style { | |
size, | |
..child.style.clone() | |
}) | |
.unwrap(), | |
layout, | |
) | |
}) | |
.collect::<Vec<(taffy::NodeId, Node)>>(); | |
let grid_size = { | |
let mut width = length(limits.max().width); | |
let mut height = length(limits.max().height); | |
// horizontal | |
if limits.max().width.is_finite() { | |
height = length(limits.max().height); | |
width = auto(); | |
} | |
// vertical | |
if limits.max().height == f32::MAX { | |
height = auto(); | |
width = length(limits.max().width); | |
} | |
taffy::Size { width, height } | |
}; | |
let mut taffy_children = Vec::with_capacity(children.len()); | |
let mut iced_children = Vec::with_capacity(children.len()); | |
for (node, layout) in children { | |
taffy_children.push(node); | |
iced_children.push(layout); | |
} | |
let root = taffy | |
.new_with_children( | |
taffy::Style { | |
size: grid_size, | |
..self.style.clone() | |
}, | |
&taffy_children, | |
) | |
.unwrap(); | |
taffy.compute_layout(root, available_space).unwrap(); | |
let child_nodes = taffy_children | |
.iter() | |
.zip(iced_children) | |
.map(|(child_id, child_layout)| { | |
let taffy_layout = taffy.layout(*child_id).unwrap(); | |
let iced_layout: layout::Node = layout::Node::with_children( | |
Size { | |
width: taffy_layout.size.width, | |
height: taffy_layout.size.height, | |
}, | |
child_layout.children().to_vec(), | |
); | |
iced_layout.move_to(Point { | |
x: taffy_layout.location.x, | |
y: taffy_layout.location.y, | |
}) | |
}) | |
.collect::<Vec<layout::Node>>(); | |
let root_layout = taffy.layout(root).unwrap(); | |
return layout::Node::with_children( | |
Size { | |
width: root_layout.size.width, | |
height: root_layout.size.height, | |
}, | |
child_nodes, | |
); | |
} | |
fn operate(&self, tree: &mut Tree, layout: Layout<'_>, renderer: &R, operation: &mut dyn Operation<()>) { | |
operation.container(None, layout.bounds(), &mut |operation| { | |
self.children | |
.iter() | |
.zip(&mut tree.children) | |
.zip(layout.children()) | |
.for_each(|((child, state), layout)| { | |
child.element.as_widget().operate(state, layout, renderer, operation); | |
}) | |
}); | |
} | |
fn on_event( | |
&mut self, tree: &mut Tree, event: Event, layout: Layout<'_>, cursor_position: mouse::Cursor, renderer: &R, | |
clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Msg>, viewport: &Rectangle, | |
) -> event::Status { | |
self.children | |
.iter_mut() | |
.zip(&mut tree.children) | |
.zip(layout.children()) | |
.map(|((child, state), layout)| { | |
child.element.as_widget_mut().on_event( | |
state, | |
event.clone(), | |
layout, | |
cursor_position, | |
renderer, | |
clipboard, | |
shell, | |
viewport, | |
) | |
}) | |
.fold(event::Status::Ignored, event::Status::merge) | |
} | |
fn mouse_interaction( | |
&self, tree: &Tree, layout: Layout<'_>, cursor_position: iced::mouse::Cursor, viewport: &Rectangle, | |
renderer: &R, | |
) -> mouse::Interaction { | |
self.children | |
.iter() | |
.zip(&tree.children) | |
.zip(layout.children()) | |
.map(|((child, state), layout)| { | |
child | |
.element | |
.as_widget() | |
.mouse_interaction(state, layout, cursor_position, viewport, renderer) | |
}) | |
.max() | |
.unwrap_or_default() | |
} | |
fn draw( | |
&self, tree: &Tree, renderer: &mut R, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, | |
cursor_position: mouse::Cursor, viewport: &Rectangle, | |
) { | |
for ((child, state), layout) in self.children.iter().zip(&tree.children).zip(layout.children()) { | |
child | |
.element | |
.as_widget() | |
.draw(state, renderer, theme, style, layout, cursor_position, viewport); | |
} | |
} | |
fn overlay<'b>( | |
&'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &R, translation: Vector, | |
) -> Option<overlay::Element<'b, Msg, Theme, R>> { | |
// This calls the first overlay. We probably want all overlays? | |
self.children | |
.iter_mut() | |
.zip(&mut tree.children) | |
.zip(layout.children()) | |
.find_map(|((child, state), layout)| { | |
child | |
.element | |
.as_widget_mut() | |
.overlay(state, layout, renderer, translation) | |
}) | |
} | |
} | |
impl<'a, Msg: 'a, Theme: 'a, R: Renderer + 'a> From<Grid<'a, Msg, Theme, R>> for Element<'a, Msg, Theme, R> { | |
fn from(grid: Grid<'a, Msg, Theme, R>) -> Self { | |
Self::new(grid) | |
} | |
} | |
struct GridChild<'a, Msg, Theme, R: Renderer> { | |
element: Element<'a, Msg, Theme, R>, | |
style: taffy::Style, | |
} | |
impl<'a, Msg, Theme, R: Renderer> GridChild<'a, Msg, Theme, R> { | |
fn new(element: Element<'a, Msg, Theme, R>, style: taffy::Style) -> Self { | |
Self { element, style } | |
} | |
} |
There might be an issue, the text widget in grid child , doesn't show up somehow.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
here is the usage: