iced-rs / iced

A cross-platform GUI library for Rust, inspired by Elm
https://iced.rs
MIT License
24.69k stars 1.16k forks source link

Freezes when trying to make a table with a large number of rows #1720

Open Kuraga13 opened 1 year ago

Kuraga13 commented 1 year ago

Is there an existing issue for this?

Is this issue related to iced?

What happened?

I'm trying to make something like a Hex Viewer, apparently, this only works with a small number of lines, since the window just freezes. Is there any way to make a table? Originally used the iced_aw widget grid so I asked them first. It was explained to me that this is some kind of problem inside iced, I tried the following code right in the tour example:

fn scrollable() -> Column<'a, StepMessage> {

        let test_addr_line   = vec!["adress column 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ascii_ASCII_ascii".to_string(); 4500];
        let test_hex_line   = vec!["01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F".to_string(); 4500];
        let test_ascii_line   = vec!["ascii_ASCII_ascii".to_string(); 4500];

        let test_row = column![
            column(
                test_addr_line
                    .iter()
                    .cloned()
                    .map(|line| {
                      text(line).size(10)
                    })
                    .map(Element::from)
                    .collect()

            ),

        ]      
        .padding(10)
        .spacing(10);

        Self::container("Scrollable")
            .push(
                "Iced supports scrollable content. Try it out! Find the \
                 button further below.",
            )
            .push(
                "Scroll test",
            )
            .push(test_row)
            .push(
                text("Tip: You can use the scrollbar to scroll down faster!")
                    .size(16),
            )
            .push(
                text("You are halfway there!")
                    .width(Length::Fill)
                    .size(30)
                    .horizontal_alignment(alignment::Horizontal::Center),
            )
            .push(ferris(300))
            .push(
                text("You made it!")
                    .width(Length::Fill)
                    .size(50)
                    .horizontal_alignment(alignment::Horizontal::Center),
            )
    }

What is the expected behavior?

The text table is a must have for any gui. Tables contain the logic of a very large number of applications. I would like to know if this can be fixed and how

Version

master

Operative System

Windows

Do you have any log output?

No response

qwxxx commented 1 year ago

You can draw Widget inside Scrollable, drawing only rows inside a viewport. In my case freezes are gone(i've tested 10k rows).

qwxxx commented 1 year ago

Working example:

pub struct TableContents<Message> {
    item_height:f32,
    contents:Vec<Vec<String>>,
    on_double_clicked: Box<dyn Fn() -> Message>,
}

impl <Message>TableContents<Message> {
    pub fn new(item_height:f32,
        contents:Vec<Vec<String>>,
                on_double_clicked:impl Fn() -> Message + 'static
    ) -> Self {
        Self {item_height,contents,on_double_clicked:Box::new(on_double_clicked)}
    }
}

pub fn table_contents< Message>(item_height:f32,
    contents:Vec<Vec<String>>,
                              on_double_clicked:impl Fn() -> Message + 'static) -> TableContents<Message> {
    TableContents::new(item_height,contents,on_double_clicked)
}

impl<Message:std::clone::Clone> Widget<Message, iced::Renderer> for TableContents<Message>
{
    fn width(&self) -> Length {
        Length::Fill
    }

    fn height(&self) -> Length {
        Length::Fill
    }
    fn on_event(
        &mut self,
        _state: &mut Tree,
        event: iced::Event,
        layout: Layout<'_>,
        cursor_position: Point,
        _renderer: &iced::Renderer,
        _clipboard: &mut dyn Clipboard,
        _shell: &mut Shell<'_, Message>) -> Status {
        Status::Ignored
    }

    fn draw(
        & self,
        _state: &widget::Tree,
        renderer: &mut iced::Renderer,
        _theme: &iced::Theme,
        _style: &renderer::Style,
        layout: Layout<'_>,
        _cursor_position: Point,
        viewport: &Rectangle,
    ) {
        use iced_native::text::Renderer as text_renderer;
        let mut viewport_layout_y=(viewport.y-layout.bounds().y);
        let mut end_y=viewport.y+viewport.height;
        let mut number_of_element=(viewport_layout_y/self.item_height) as i32;
        let mut element_bounds=Rectangle{x:layout.bounds().x, y:(self.item_height*number_of_element as f32)+layout.bounds().y, width:layout.bounds().width, height:self.item_height};
        let mut contents=self.contents.clone();

        while element_bounds.y<end_y{
            let mut rectangle_bounds=element_bounds;
            if element_bounds.y+element_bounds.height>end_y {
                rectangle_bounds.height=end_y-element_bounds.y;
            }

            renderer.fill_quad(
                renderer::Quad {
                    bounds: rectangle_bounds,
                    border_radius: Default::default(),
                    border_width: 0.0,
                    border_color: Color::WHITE,
                }, Background::Color(if number_of_element % 2 == 0 { COLOR_LIGHTER } else { COLOR_DARKER })
            );

            let mut text_bounds=element_bounds;
            text_bounds.y=element_bounds.center_y();
            if let Some(itemvec)=contents.get(number_of_element as usize) {

                if itemvec.len()==1{
                    text_bounds.x=element_bounds.center_x();
                    renderer.fill_text(
                        iced_native::text::Text {
                            content: itemvec[0].as_str(),
                            bounds: text_bounds,
                            size: 20.0,
                            color: Color::WHITE,
                            font: Font::Default,
                            horizontal_alignment: Horizontal::Center,
                            vertical_alignment: Vertical::Center,
                        }
                    );
                }else{
                    text_bounds.width/=itemvec.len() as f32;
                    for item in itemvec.iter(){;
                        renderer.fill_text(
                            iced_native::text::Text {
                                content: format!("  {}",item.as_str()).as_str(),
                                bounds: text_bounds,
                                size: 20.0,
                                color: Color::WHITE,
                                font: Font::Default,
                                horizontal_alignment: Horizontal::Left,
                                vertical_alignment: Vertical::Center,
                            }
                        );
                        text_bounds.x+=text_bounds.width;
                    }
                }

            }
            element_bounds.y+=element_bounds.height;
            number_of_element+=1;
        }
    }

    fn layout(&self, renderer: &iced::Renderer, limits: &Limits) -> Node {
        layout::Node::new(Size{
            width: limits.max().width,
            height:  self.item_height*((self.contents.len()) as f32)
        })
    }

}

impl<'a, Message> From<TableContents<Message>> for Element< 'a,Message, iced::Renderer>
    where Message:'a,
          Message:std::clone::Clone
{
    fn from(table_contents: TableContents<Message>) -> Self {
        Self::new(table_contents)
    }
}

Sry if this code is bad, im new in rust. But it works without any freezes. One question to maintainers: how can i calculate item_height inside this widget, if i wanna draw only 7 rows in one view? Only way that i see requires viewport data inside layout method.

qwxxx commented 1 year ago

https://user-images.githubusercontent.com/60794504/220211354-d3590ac2-be07-4e38-8fda-536c89e9d165.mov

10k elements table

Kuraga13 commented 1 year ago

Hi, thanks a lot for your answer! If it works, it'll be awesome! I tried to build your code, what version of iced are you using? I am quite a beginner in iced. While the compiler does not seem to see any crate.

impl<Message:std::clone::Clone> Widget<Message, iced::Renderer> for TableContents<Message>  
    |                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `iced_native::Renderer` is not implemented for `iced_graphics::renderer::Renderer<iced_wgpu::backend::Backend, iced::Theme>`
qwxxx commented 1 year ago

My Cargo.toml:

iced={version="0.8.0",features=["default_system_font","image","debug"]}
iced_lazy= "0.5.0"
iced_native="0.9.1"
iced_graphics = "0.7.0"
iced_futures = "0.6.0"
Kuraga13 commented 1 year ago

Works! I had an old version of iced, 0.7. Thanks, I struggled with this for a long time, great, zbs.

2023-02-21_22-59-02

luizvidoto commented 1 year ago

Hey, I had the same issue in my app, so I created this small library to fix it! Although this component isn't finished, it's still usable. iced-flatlist

Kuraga13 commented 1 year ago

Hey, I had the same issue in my app, so I created this small library to fix it! Although this component isn't finished, it's still usable. iced-flatlist

This is great! Will have to try. I did something too HexBufferWidget

screen

luizvidoto commented 1 year ago

I'll try to bump the version of my component, it uses an older version of iced. When I click your link it goes nowhere, maybe it is set to be private?

Kuraga13 commented 1 year ago

Ok I will wait. Link corrected

luizvidoto commented 1 year ago

Alright, bumped iced version to 0.8.0