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