Remove smart pointer and factory without breaking code

use iced::{
    widget::{Button, Text},
    Element, Theme,
};
use iced_widget::{
    container, scrollable, Column, Row,
};

use std::sync::Arc;
use std::cell::RefCell;

use style::wrapper::{Target, Wrapper};
pub use style::Catalog;
pub mod style;

#[derive(Debug, Clone)]
pub enum CellMessage {
    Edit,
    Remove,
    Clicked,
}

#[derive(Debug, Clone)]
pub enum GridMessage {
    AddCell(usize),
    Cell(usize, usize, CellMessage),
}

// Internal enum to abstract Cell types
enum InternalCell<'a> {
    Text(String),
    Button {
        label: String,
        on_press: CellMessage,
    },
    Container(Arc<RefCell<dyn Fn() -> Element<'a, CellMessage> + 'a>>),
}

impl<'a> Clone for InternalCell<'a> {
    fn clone(&self) -> Self {
        match self {
            InternalCell::Text(content) => InternalCell::Text(content.clone()),
            InternalCell::Button { label, on_press } => InternalCell::Button {
                label: label.clone(),
                on_press: on_press.clone(),
            },
            InternalCell::Container(container) => InternalCell::Container(Arc::clone(container)),
        }
    }
}

impl<'a> InternalCell<'a> {
    fn view(&self) -> Element<CellMessage> {
        match self {
            InternalCell::Text(content) => Text::new(content.clone()).into(),
            InternalCell::Button { label, on_press } => {
                Button::new(Text::new(label.clone()))
                    .on_press(on_press.clone())
                    .into()
            }
            InternalCell::Container(factory) => (factory.borrow())(),
        }
    }
}

#[derive(Default, Clone)]
pub struct RowData {
    pub cells: Vec<InternalCell<'static>>,
}

impl RowData {
    pub fn push_text(&mut self, content: String) {
        self.cells.push(InternalCell::Text(content));
    }

    pub fn push_button(&mut self, label: String, on_press: CellMessage) {
        self.cells.push(InternalCell::Button { label, on_press });
    }

    pub fn push_container<F>(&mut self, factory: F)
    where
        F: Fn() -> Element<'static, CellMessage> + 'static,
    {
        self.cells.push(InternalCell::Container(Arc::new(RefCell::new(factory))));
    }

    pub fn len(&self) -> usize {
        self.cells.len()
    }

    pub fn get_mut(&mut self, index: usize) -> Option<&mut InternalCell<'static>> {
        self.cells.get_mut(index)
    }
}

#[derive(Clone)]
pub struct Grid<Message, Theme>
where
    Theme: style::Catalog,
{
    rows: Vec<RowData>,
    style: <Theme as style::Catalog>::Style,
    on_sync: fn(scrollable::AbsoluteOffset) -> Message,
}

impl<'a, Message, Theme, Renderer> From<Grid<Message, Theme>>
for Element<'a, Message, Theme, Renderer>
where
    Renderer: iced_core::Renderer + 'a,
    Theme: style::Catalog + container::Catalog + scrollable::Catalog + 'a,
    Message: 'a + Clone,
{
    fn from(grid: Grid<Message, Theme>) -> Self {
        let style = grid.style.clone();

        Element::new(Wrapper {
            content: grid.into(),
            target: Target::Style,
            style,
        })
    }
}

impl<Message, Theme> From<iced::Element<'_, Message, Theme>> for Grid<Message, Theme>
where
    Theme: style::Catalog,
{
    fn from(_element: iced::Element<'_, Message, Theme>) -> Self {
        Grid {
            rows: Vec::new(),
            style: Default::default(),
            on_sync: |_| panic!("Conversion from Element not implemented"),
        }
    }
}

impl<'a, Message, Theme: style::Catalog> Grid<Message, Theme> {
    pub fn new(
        rows: Vec<RowData>,
        style: <Theme as style::Catalog>::Style,
        on_sync: fn(scrollable::AbsoluteOffset) -> Message,
    ) -> Self {
        Self {
            rows,
            style,
            on_sync,
        }
    }

    pub fn style(&mut self, style: impl Into<<Theme as style::Catalog>::Style>) {
        self.style = style.into();
    }

    pub fn get_cell(&mut self, row_index: usize, cell_index: usize) -> Option<&mut InternalCell<'static>> {
        self.rows.get_mut(row_index).and_then(|row| row.get_mut(cell_index))
    }

    pub fn get_row(&mut self, row: usize) -> &mut RowData {
        if self.rows.len() <= row {
            self.rows.resize_with(row + 1, RowData::default);
        }
        &mut self.rows[row]
    }

    pub fn row_count(&self) -> usize {
        self.rows.len()
    }

    pub fn add_row(&mut self, row: RowData) {
        self.rows.push(row);
    }

    pub fn get_row_mut(&mut self, row: usize) -> Option<&mut RowData> {
        if row < self.rows.len() {
            Some(&mut self.rows[row])
        } else {
            None
        }
    }

    pub fn add_rows(&mut self, count: usize) {
        for _ in 0..count {
            self.rows.push(RowData::default());
        }
    }

    pub fn add_cells_to_row(&mut self, row_index: usize, count: usize) {
        let row = self.get_row(row_index);
        for _ in 0..count {
            row.cells.push(InternalCell::Text("Default".to_string()));
        }
    }

    pub fn add_cells_to_all_rows(&mut self, count: usize) {
        for row in &mut self.rows {
            for _ in 0..count {
                row.cells.push(InternalCell::Text("Default".to_string()));
            }
        }
    }

    pub fn create_grid(&'a self) -> Column<'a, GridMessage> {
        let mut column = Column::new().spacing(10);

        for row_index in 0..self.rows.len() {
            let mut row_view = Row::new().spacing(10);
            for cell_index in 0..self.rows[row_index].cells.len() {
                let cell = &self.rows[row_index].cells[cell_index];
                let cell_view: Element<GridMessage> = match cell {
                    InternalCell::Text(ref text) => {
                        Text::new(text.clone()).into()
                    }
                    InternalCell::Button { ref label, on_press } => {
                        Button::new(Text::new(label.clone()))
                            .on_press(GridMessage::Cell(row_index, cell_index, on_press.clone()))
                            .into()
                    }
                    InternalCell::Container(factory) => {
                        (factory.borrow())().map(move |cell_msg| {
                            GridMessage::Cell(row_index, cell_index, cell_msg)
                        })
                    }
                };
                row_view = row_view.push(cell_view);
            }
            column = column.push(row_view);
        }

        column
    }

    pub fn view(&'a self) -> iced::Element<'a, GridMessage>
    where
        iced_widget::container::Style: From<<Theme as style::Catalog>::Style>,
    {
        container(
            self.create_grid()
                .padding(10)
                .spacing(5),
        )
        .style({
            move |theme: &crate::Theme| self.style.clone().into()
        })
        .into()
    }
}

So, I am making a grid widget for the GUI library iced, I managed to get Text and Button working, but container has been a pain, my working code currently requires a smart pointer and a factory, however I do not want to use a smart pointer or factory when working with my library, so there are two solutions:

  • Remove smart pointers and factories

  • Abstract away all the factory and smart pointer stuff so the user just passes in the container and its wrapped as a smart pointer/factory and managed that way

I do not know how to do either, it has been a long time I been on this issue (a week), so specific solutions, (preferably with some code) would be helpful. You do not need to know iced to help me.

Repo:
GitHub - SpiderUnderUrBed/iced_grid: A widget for grids in iced

Demo repo:
GitHub - SpiderUnderUrBed/iced-calendar-rs

Hi there, and welcome here. Sorry your post was stuck in the spam filter a few hours. Also when cross-posting please take a minute to indicate that properly. Which would also help avoid having your post stuck in the spam filter (for being created suspiciously quickly).

1 Like