Skip to content

Instantly share code, notes, and snippets.

@videni
Created August 27, 2024 03:51
Show Gist options
  • Save videni/ad89282eb7b9cf7a18eb637a3e8e2ceb to your computer and use it in GitHub Desktop.
Save videni/ad89282eb7b9cf7a18eb637a3e8e2ceb to your computer and use it in GitHub Desktop.
iced_grid_with_taffy
//! 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 }
}
}
@videni
Copy link
Author

videni commented Aug 27, 2024

here is the usage:


// two modes, list and grid
let grid = {
        let grid = grid()
            .with_row_gap(length(10.0))
            .with_column_gap(length(10.0))
            .style(|style| style.justify_content = Some(JustifyContent::SpaceAround));

        match view_mode {
            ViewMode::Grid => grid.with_columns(vec![repeat("auto-fill", vec![length(150.0)])]),
            ViewMode::List => grid.with_columns(vec![fr(1.0)]),
        }
};

 // create the child widget
    let projects = self
        .projects
        .iter()
        .map(|p| self.create_item(p.clone()))
        .collect::<Vec<Element<ProjectListMessage>>>();

    center(
    // add the children widget to grid
        scrollable(projects.into_iter().fold(grid, |g, item| g.with_child(item)))
            .height(Fill)
            .width(Fill)
            .direction(scrollable::Direction::default()),
    )
    .padding(10)
    .into()

@videni
Copy link
Author

videni commented Aug 27, 2024

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