iced icon indicating copy to clipboard operation
iced copied to clipboard

Multiple Canvas on a layout do not render properly except the first one

Open deavid opened this issue 4 months ago • 1 comments

Is your issue REALLY a bug?

  • [x] My issue is indeed a bug!
  • [x] I am not crazy! I will not fill out this form just to ask a question or request a feature. Pinky promise.

Is there an existing issue for this?

  • [x] I have searched the existing issues.

Is this issue related to iced?

  • [x] My hardware is compatible and my graphics drivers are up-to-date.

What happened?

After upgrading my project zzping-gui from iced 0.4 to 0.13 I discover that multiple canvas in a layout (column, row) do not render properly - except of the first one.

I created a sample file to demo this:

use iced::mouse;
use iced::widget::Column;
use iced::widget::canvas;
use iced::widget::canvas::Geometry;
use iced::widget::canvas::Path;
use iced::{Color, Element, Fill, Rectangle, Renderer, Theme};

pub fn main() -> iced::Result {
    iced::application("Two Circles", Circles::update, Circles::view)
        .theme(Circles::theme)
        .run()
}

struct Circles {
    circle1: Circle,
    circle2: Circle,
}

impl Default for Circles {
    fn default() -> Self {
        Self {
            circle1: Circle {
                color: Color::from_rgb8(255, 0, 0), // Red
                cache: canvas::Cache::default(),
            },
            circle2: Circle {
                color: Color::from_rgb8(0, 0, 255), // Blue
                cache: canvas::Cache::default(),
            },
        }
    }
}

#[derive(Debug, Clone, Copy)]
enum Message {}

impl Circles {
    fn update(&mut self, _message: Message) {}

    fn view(&self) -> Element<'_, Message> {
        let canvas1 = canvas::Canvas::new(&self.circle1).width(Fill).height(Fill);

        let canvas2 = canvas::Canvas::new(&self.circle2).width(Fill).height(Fill);

        Column::new()
            .push(canvas1)
            .push(canvas2)
            .spacing(20)
            .padding(20)
            .into()
    }

    fn theme(&self) -> Theme {
        Theme::default()
    }
}

impl Default for Circle {
    fn default() -> Self {
        Self {
            color: Color::from_rgb8(255, 0, 0), // Red
            cache: canvas::Cache::default(),
        }
    }
}

struct Circle {
    color: Color,
    cache: canvas::Cache,
}

impl<Message> canvas::Program<Message> for Circle {
    type State = ();

    fn draw(
        &self,
        _state: &Self::State,
        renderer: &Renderer,
        _theme: &Theme,
        bounds: Rectangle,
        _cursor: mouse::Cursor,
    ) -> Vec<Geometry> {
        let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
            let center = frame.center();
            let radius = frame.width().min(frame.height()) / 4.0;

            let circle = Path::circle(center, radius);
            frame.fill(&circle, self.color);
        });

        vec![geometry]
    }
}

What is the expected behavior?

I would expect to see two circles, one blue on top, one red on bottom.

What it actually happens is that only the top one renders properly. The bottom one randomly appears, flickers, and disappears on window resize.

The actual problem 1 is a bit more convoluted that what this shows. It seems like some canvas primitives such as text are rendered properly. Rectangles and lines seem to be hit and miss. It happens only to the 2nd, 3rd canvas etc. Never to the first one.

Also it doesn't seem (in my original project) that the canvas reserves the correct size, since it seems to have overlap between the different canvas widgets.

Version

crates.io release

Operating System

Linux

Do you have any log output?


deavid avatar Aug 23 '25 22:08 deavid

Try the following code.

Cargo.toml

[dependencies]
iced = { version = "0.13.1", features = ["canvas"] }

main.rs

use iced::mouse;
use iced::widget::canvas::{self, Geometry, Path};
use iced::widget::{Canvas, Column};
use iced::{Color, Element, Fill, Rectangle, Renderer, Theme};

pub fn main() -> iced::Result {
    iced::application("Two Circles", Circles::update, Circles::view)
        .theme(Circles::theme)
        .run()
}

struct Circles {
    circle1: Circle,
    circle2: Circle,
}

impl Default for Circles {
    fn default() -> Self {
        Self {
            circle1: Circle {
                color: Color::from_rgb8(255, 0, 0), // Red
                cache: canvas::Cache::default(),
            },
            circle2: Circle {
                color: Color::from_rgb8(0, 0, 255), // Blue
                cache: canvas::Cache::default(),
            },
        }
    }
}

#[derive(Debug, Clone, Copy)]
enum Message {}

impl Circles {
    fn update(&mut self, _message: Message) {}

    fn view(&self) -> Element<'_, Message> {
        let canvas1 = Canvas::new(&self.circle1).width(Fill).height(Fill);

        let canvas2 = Canvas::new(&self.circle2).width(Fill).height(Fill);

        Column::new()
            .push(canvas1)
            .push(canvas2)
            .spacing(20)
            .padding(20)
            .into()
    }

    fn theme(&self) -> Theme {
        Theme::default()
    }
}

impl Default for Circle {
    fn default() -> Self {
        Self {
            color: Color::from_rgb8(255, 0, 0), // Red
            cache: canvas::Cache::default(),
        }
    }
}

struct Circle {
    color: Color,
    cache: canvas::Cache,
}

impl<Message> canvas::Program<Message> for Circle {
    type State = ();

    fn draw(
        &self,
        _state: &Self::State,
        renderer: &Renderer,
        _theme: &Theme,
        bounds: Rectangle,
        _cursor: mouse::Cursor,
    ) -> Vec<Geometry> {
        let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
            let center = frame.center();
            let radius = frame.width().min(frame.height()) / 4.0;

            let circle = Path::circle(center, radius);
            frame.fill(&circle, self.color);
        });

        vec![geometry]
    }
}

jimmykuu avatar Sep 15 '25 13:09 jimmykuu

I tested this for fun with 0.14.0. I also added a 3rd circle, tried on Ubuntu 25.10 as well as firefox:

#[cfg(not(target_arch = "wasm32"))]
fn init() {
    tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "doris_client=debug,iced=warn".into()))
        .init();
}

#[cfg(target_arch = "wasm32")]
fn init() {
    use tracing::info;
    use tracing_subscriber::layer::SubscriberExt;
    use tracing_subscriber::util::SubscriberInitExt;
    let wasm_layer = tracing_wasm::WASMLayer::new(tracing_wasm::WASMLayerConfig::default());

    let filter = tracing_subscriber::EnvFilter::new("doris_client=debug,iced=warn");

    tracing_subscriber::registry().with(wasm_layer).with(filter).init();

    info!(target = "init", "Tracing initialized (wasm)");

    // Remove javascript loading spinner
    {
        use web_sys::window;

        if let Some(window) = window() {
            if let Some(document) = window.document() {
                if let Some(element) = document.get_element_by_id("loading_text") {
                    element.remove();
                }
            }
        }
    }
}

use iced::mouse;
use iced::theme::{Base, Mode};
use iced::widget::canvas::{Geometry, Path};
use iced::widget::{button, column, text, text_input};
use iced::widget::{canvas, Canvas};
use iced::{Color, Element, Length, Rectangle, Renderer, Task, Theme};

pub fn main() -> iced::Result {
    init();
    // 1. Pass the constructor (Circles::new) instead of the title string
    // 2. Move title to .title()
    iced::application(Circles::new, Circles::update, Circles::view)
        .title("Two Circles")
        .theme(Circles::theme)
        .run()
}

struct Circles {
    circle1: Circle,
    circle2: Circle,
    circle3: Circle,
}
#[derive(Debug, Clone, Copy)]
enum Message {}

impl Circles {
    // New initialization function required by iced::application
    fn new() -> (Self, Task<Message>) {
        (Self::default(), Task::none())
    }

    fn update(&mut self, _message: Message) -> Task<Message> {
        Task::none()
    }

    pub fn view(&self) -> Element<'_, Message> {
        column![
            Canvas::new(&self.circle1).width(Length::Fill).height(Length::Fill),
            Canvas::new(&self.circle2).width(Length::Fill).height(Length::Fill),
            Canvas::new(&self.circle3).width(Length::Fill).height(Length::Fill),
        ]
        .padding(20)
        .into()
    }

    fn theme(&self) -> Theme {
        Theme::default(Mode::Light)
    }

    fn default() -> Circles {
        Self {
            circle1: Circle {
                color: Color::from_rgb8(255, 0, 0), // Red
                cache: canvas::Cache::default(),
            },
            circle2: Circle {
                color: Color::from_rgb8(0, 0, 255), // Blue
                cache: canvas::Cache::default(),
            },
            circle3: Circle {
                color: Color::from_rgb8(0, 255, 0), // Blue
                cache: canvas::Cache::default(),
            },
        }
    }
}

impl Default for Circle {
    fn default() -> Self {
        Self {
            color: Color::from_rgb8(255, 0, 0), // Red
            cache: canvas::Cache::default(),
        }
    }
}

struct Circle {
    color: Color,
    cache: canvas::Cache,
}

impl<Message> canvas::Program<Message> for Circle {
    type State = ();

    fn draw(&self, _state: &Self::State, renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor) -> Vec<Geometry> {
        let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
            let center = frame.center();
            let radius = frame.width().min(frame.height()) / 4.0;

            let circle = Path::circle(center, radius);
            frame.fill(&circle, self.color);
        });

        vec![geometry]
    }
}

Native Linux, also, resizing works fine:

Image

Firefox, not working, it looks like all circle are drawn in the same position:

Image

Also, when resizing firefox:

Image

c64zottel avatar Dec 14 '25 07:12 c64zottel