blitzarx1 / egui_graphs

Interactive graph visualization widget for rust powered by egui and petgraph
https://docs.rs/crate/egui_graphs
MIT License
388 stars 29 forks source link

Drawing label inside of node #196

Closed lovesegfault closed 1 month ago

lovesegfault commented 1 month ago

I wanted to try implementing a custom NodeShape to render the node labels inside a node rect. I came up with this as a starting point:

#[derive(Clone, Debug)]
struct DepNodeShape {
    pub pos: Pos2,

    pub selected: bool,
    pub dragged: bool,

    pub name: String,
}

impl<N: Clone> From<NodeProps<N>> for DepNodeShape {
    fn from(node_props: NodeProps<N>) -> Self {
        let pos = node_props.location;
        let selected = node_props.selected;
        let dragged = node_props.dragged;
        let name = node_props.label.to_string();

        Self {
            pos,
            selected,
            dragged,
            name,
        }
    }
}

impl<N: Clone, E: Clone, Ty: EdgeType, Ix: IndexType> DisplayNode<N, E, Ty, Ix> for DepNodeShape {
    fn is_inside(&self, pos: Pos2) -> bool {
        todo!()
    }

    fn closest_boundary_point(&self, dir: Vec2) -> Pos2 {
        todo!()
    }

    fn shapes(&mut self, ctx: &DrawContext) -> Vec<Shape> {
        let mut shapes = Vec::with_capacity(2);

        let style = if self.selected {
            ctx.ctx.style().visuals.widgets.active
        } else {
            ctx.ctx.style().visuals.widgets.inactive
        };

        let color = style.fg_stroke.color;

        // First, the text with the dep name
        let galley = ctx.ctx.fonts(|f| {
            f.layout_no_wrap(
                self.name.clone(),
                egui::FontId {
                    size: 5.0,
                    family: FontFamily::Monospace,
                },
                color,
            )
        });
        let name_pos = Pos2::new(
            ctx.meta.canvas_to_screen_pos(self.pos).x - (galley.size().x / 2.),
            ctx.meta.canvas_to_screen_pos(self.pos).y - (galley.size().y / 2.),
        );
        let name_shape = TextShape::new(name_pos, galley, color);
        let box_rect = name_shape.visual_bounding_rect().expand(3.0);
        shapes.push(name_shape.into());

        // Now, the surrounding shape
        let box_shape = RectShape::stroke(
            box_rect,
            Rounding {
                nw: 0.2,
                ne: 0.2,
                sw: 0.2,
                se: 0.2,
            },
            Stroke::new(1.0, color),
        );
        shapes.push(box_shape.into());

        shapes
    }

    fn update(&mut self, state: &NodeProps<N>) {
        self.pos = state.location;
        self.pos = state.location;
        self.selected = state.selected;
        self.dragged = state.dragged;
        self.name = state.label.to_string();
    }
}

Once I got to implement is_inside, closest_boundary_point, and update, I couldn't figure out how to continue.

The issue is that the size of the surrounding Rect is dynamic on the length of the label, and I can only compute the TextShape when ctx: &DrawContext is available, which isn't the case in those other methods.

Is there a way to do this?

blitzarx1 commented 1 month ago

You could store the text size in the DepNodeShape inside shapes method. As it has &mut self reference.

I will add an example with flex nodes to demonstrate it.

lovesegfault commented 1 month ago

You could store the text size in the DepNodeShape inside shapes method. As it has &mut self reference.

But wouldn't that mean that if closest_boundary_point or is_inside are called before the first call to shapes their results would be incorrect?

blitzarx1 commented 1 month ago

But wouldn't that mean that if closest_boundary_point or is_inside are called before the first call to shapes their results would be incorrect?

Yep, but only for 1 frame, which is negligible.

Please check the new example.

blitzarx1 commented 1 month ago

Closing this one. If you have any more questions, welcome in other tickets 😃

lovesegfault commented 1 month ago

I was able to build on your example and get the result I wanted, thank you so much for taking the time!

This is a really cool lib :)