Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Z-ordering of shapes #1516

Open
emilk opened this issue Apr 19, 2022 · 4 comments · May be fixed by #2633
Open

Z-ordering of shapes #1516

emilk opened this issue Apr 19, 2022 · 4 comments · May be fixed by #2633
Labels
design Some architectual design work needed feature New feature or request

Comments

@emilk
Copy link
Owner

emilk commented Apr 19, 2022

Problem

egui and epaint are currently based on "painters algorithm", i.e. painting things from back-to-front in order to get some shapes to cover others. This means when painting widgets you need to do so back-to-front. Sometimes this is difficult, especially since immediate mode ties interaction and painting together. Sometimes you want to paint things that go in the background last, or things that go in the foreground first.

For instance, when painting the egui::Frame we need to wait until the contents has been added before we know how big to paint the frame. So the code adds a dummy Shape::Noop and gets a ShapeIdx where the background it later put. This is a bit ugly.

Proposed solution

One way to make this less cumbersome is to introduce a Z value for each Shape. This could be an integer or float, and shapes with a lower Z will always be behind those with a higher Z. Shapes with the same Z are painted in the order they are added.

This could be implemented by having PaintList store Vec<(ZIndex, ClippedShape)>, where ZIndex is a a typedef for e.g. i32. Each PaintList would do a stable sort on the Z value before draining in GraphicLayers::drain.

egui::Painter could have a setting for which Z value it uses so that one can set it for a Painter and then pass the Painter to a function that does the painting, with that function being agnostic to what Z it is painting to (just as it is agnostic to what paint layer it is painting to).

Questions

What type for Z index: i32, i64, f32, f64?

Should we have standard indexes for foreground and background?

What are the performance implications of this?

Should we still have separate PaintLists in GraphicLayers, or just put the LayerId together with ZIndex and ClippedShape and stable-sort on that too while we're at it?

Applications

  • Separator lines between panels should be on top of the panel contents
  • Window shadows should be behind all windows in the same layer (e.g. behind all popups)
@setzer22
Copy link
Contributor

setzer22 commented Nov 14, 2022

Hi! I'd like to tackle this. I think this would help simplify things a lot. As you say, widget call order is coupling together interaction and draw order in a way that makes some things more cumbersome than they should.

The plan you outline above sounds good. But I have a few questions / comments.

First, how is this going to interact with the order in the Areas memory object? Right now, GraphicLayers::drain is special cased for areas and those are ordered using their LayerIds. Would the layer ordering mechanism be kept as-is, and z-index only used to sort things within a layer?

What type for Z index: i32, i64, f32, f64?

Float sounds like the most flexible, because you can always sneak in a shape between every pair of shapes (as long as you're not within precision limits, but it's still better than an integer). As for 32 or 64 bits, I'm ambivalent. Egui seems to be using f32 everywhere else (Pos2, Rect, ...) so it sounds like f32 might be a good option here too.

Should we still have separate PaintLists in GraphicLayers, or just put the LayerId together with ZIndex and ClippedShape and stable-sort on that too while we're at it?

I assume you mean sorting on a compound key (LayerId, ZIndex), i.e. sorting first by LayerId, then by ZIndex? I'm not fully sure about the performance implications of that. Sounds like the hierarchical approach (i.e. as it is right now) would imply sorting more times, but for smaller lists. Can't say much without benchmarking.

What are the performance implications of this?

Presumably, there will be a performance hit. I'm not sure if trying to keep things sorted incrementally (by not using a Vec, but some other data structure that allows fast insertion and keeps things sorted) would help soften the blow. Again, can't say much without benchmarking.

I'm back-linking to the issue from my repo so people can track discussion: setzer22/egui_node_graph#71.

@emilk
Copy link
Owner Author

emilk commented Nov 16, 2022

Thanks for thinking about this @setzer22!

As you notice, most of the work is design rather than implementation :)

This is all related to #2244 (overlapping widgets) too. Ideally we should be able to add widgets to the same Window to different ZIndex in any order, and have the top ZIndex get the interaction.

I don't have a helpful plan yet (and don't really have time to think about it this week), but this feature is something I really want to get solved.

Another use case for this feature is the resize-edges of Panels, which should be painted on top of all other panels, but below windows.

@setzer22
Copy link
Contributor

Thanks! 😄

As you notice, most of the work is design rather than implementation :)

With some guidance, I'd be more than happy to lead the implementation efforts for this 😄

Overlapping widgets

You could say overlapping interactive widgets is my exact use case, so I'm very interested in this too. The nodes in my library are conceptually just widgets you can drag around, even if they might look like a window. Implementing them as windows would be wrong, as that would have all sorts of weird interactions when drawing a graph editor inside a window.

I have no problem handling the ordering of widget draw calls on my end as a user (and I already do that), but right now there's no good mechanism to detach interactivity from draw order. Some sort of Z-index sounds like the perfect solution to that.

@emilk
Copy link
Owner Author

emilk commented Nov 29, 2022

I think we should split LayerId into two struct:s with different responsibilities.

One would be AreaLayer, the other ZLayer:

pub struct AreaLayerId {
    pub order: Order,
    pub id: Id,
}

pub struct ZLayer {
    pub area_layer: AreaLayer
    pub z: ZOrder,
}

pub struct ZOrder(pub i32);

AreaLayer would be used in Areas to order windows etc.

ZLayer would be used in Painter, Context::interact and in GraphicLayers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design Some architectual design work needed feature New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants