a-b-street/abstreet · error

to_geom called on a widget tree that has something…

Error message

to_geom called on a widget tree that has something interactive

What it means

`to_geom`/`consume_geometry` converts a widget tree into a plain GeomBatch for rendering; it only handles non-interactive widgets. Reaching any interactive widget (Button, Slider, etc.) in the tree is unsupported and panics.

Solutions

  1. Remove/replace interactive widgets before calling to_geom
  2. Only call to_geom on purely decorative trees (Labels, Lines, DeferDraw)
  3. Render interactive widgets normally via draw instead of extracting geometry
  4. Build a separate static tree for the geometry extraction

Example fix

// before
panel.consume_geometry(&mut batch); // panel contains buttons
// after
let static_tree = Container::vertical(vec![Widget::draw_batch(label_batch)]);
static_tree.consume_geometry(&mut batch);
Defensive patterns

Strategy: validation

Validate before calling

// ensure tree is non-interactive before extracting geometry
assert!(!panel.has_widget("save_btn"), "remove interactive widgets before to_geom");

Prevention

When it happens

Trigger: Calling `Panel::consume_geometry`/to_geom on a tree containing interactive widgets such as Buttons, Sliders, or TextBoxes.

Common situations: Trying to snapshot or export a Panel that still has controls on it; reusing an interactive Panel's contents as static drawing; forgetting to replace controls with a rendering of them first.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13). Data as JSON: /api/errors/b2b3fdd151394ee0. Report an issue: GitHub.

Appendix: source

Thrown at widgetry/src/widgets/mod.rs:774

        }
    }

    fn consume_geometry(mut self, batch: &mut GeomBatch) {
        if let Some(bg) = self.bg_batch.take() {
            batch.append(bg.translate(self.rect.x1, self.rect.y1));
        }

        if self.widget.is::<Container>() {
            // downcast() consumes, so we have to do the is() check first
            if let Ok(container) = self.widget.downcast::<Container>() {
                for w in container.members {
                    w.consume_geometry(batch);
                }
            }
        } else if let Ok(defer) = self.widget.downcast::<DeferDraw>() {
            batch.append(defer.batch.translate(defer.top_left.x, defer.top_left.y));
        } else {
            panic!("to_geom called on a widget tree that has something interactive");
        }
    }

    fn find(&self, name: &str) -> Option<&Widget> {
        if self.id == Some(name.to_string()) {
            return Some(self);
        }

        if let Some(container) = self.widget.downcast_ref::<Container>() {
            for widget in &container.members {
                if let Some(w) = widget.find(name) {
                    return Some(w);
                }
            }
        }

        None
    }

View on GitHub (pinned to 0964f29315)