Multiple Canvas on a layout do not render properly except the first one
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?
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]
}
}
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:
Firefox, not working, it looks like all circle are drawn in the same position:
Also, when resizing firefox: