[culling] full widget culling

This commit is contained in:
Emil Ernerfeldt 2020-10-10 07:23:02 +02:00
parent 7abb9a2814
commit 9112834f20
16 changed files with 179 additions and 109 deletions

View file

@ -180,6 +180,8 @@ impl CollapsingHeader {
let id = ui.make_persistent_id(id_source);
let mut state = State::from_memory_with_default_open(ui.ctx(), id, default_open);
let available = ui.available_finite();
let text_pos = available.min + vec2(ui.style().spacing.indent, 0.0);
let galley = label.layout_width(ui, available.right() - text_pos.x);
@ -192,15 +194,16 @@ impl CollapsingHeader {
galley.size.y + 2.0 * ui.style().spacing.button_padding.y,
);
desired_size = desired_size.at_least(ui.style().spacing.interact_size);
let rect = ui.allocate_space(desired_size);
let header_response = ui.interact(rect, id, Sense::click());
let header_response;
if let Some(rect) = ui.request_space(desired_size) {
header_response = ui.interact(rect, id, Sense::click());
let text_pos = pos2(
text_pos.x,
header_response.rect.center().y - galley.size.y / 2.0,
);
let mut state = State::from_memory_with_default_open(ui.ctx(), id, default_open);
if header_response.clicked {
state.toggle(ui);
}
@ -238,6 +241,9 @@ impl CollapsingHeader {
stroke: Default::default(),
},
);
} else {
header_response = Default::default();
}
Prepared {
id,

View file

@ -38,7 +38,7 @@ pub fn combo_box(
let advance = full_minimum_width - icon_width - ui.min_rect().width();
ui.advance_cursor(advance.at_least(0.0));
let icon_rect = ui.allocate_space(Vec2::splat(icon_width));
let icon_rect = unwrap_or_return_default!(ui.request_space(Vec2::splat(icon_width)));
let button_rect = ui.min_rect().expand2(ui.style().spacing.button_padding);
let mut response = ui.interact(button_rect, button_id, Sense::click());
response.active |= button_active;
@ -91,6 +91,7 @@ fn button_frame(
add_contents(&mut content_ui);
let outer_rect = Rect::from_min_max(outer_rect_bounds.min, content_ui.min_rect().max + margin);
ui.allocate_space(outer_rect.size());
let mut response = ui.interact(outer_rect, id, sense);
response.active |= button_active;
@ -106,8 +107,6 @@ fn button_frame(
},
);
ui.allocate_space(outer_rect.size());
response
}

View file

@ -436,7 +436,7 @@ impl Context {
if id.is_none() || sense == Sense::nothing() || !layer_id.allow_interaction() {
// Not interested or allowed input:
return Response {
ctx: self.clone(),
ctx: Some(self.clone()),
sense,
rect,
hovered,
@ -461,7 +461,7 @@ impl Context {
if self.input.mouse.pressed {
if hovered {
let mut response = Response {
ctx: self.clone(),
ctx: Some(self.clone()),
sense,
rect,
hovered: true,
@ -491,7 +491,7 @@ impl Context {
} else {
// miss
Response {
ctx: self.clone(),
ctx: Some(self.clone()),
sense,
rect,
hovered,
@ -504,7 +504,7 @@ impl Context {
} else if self.input.mouse.released {
let clicked = hovered && active && self.input.mouse.could_be_click;
Response {
ctx: self.clone(),
ctx: Some(self.clone()),
sense,
rect,
hovered,
@ -515,7 +515,7 @@ impl Context {
}
} else if self.input.mouse.down {
Response {
ctx: self.clone(),
ctx: Some(self.clone()),
sense,
rect,
hovered: hovered && active,
@ -526,7 +526,7 @@ impl Context {
}
} else {
Response {
ctx: self.clone(),
ctx: Some(self.clone()),
sense,
rect,
hovered,

View file

@ -105,7 +105,9 @@ impl FrameHistory {
// TODO: we should not use `slider_width` as default graph width.
let height = ui.style().spacing.slider_width;
let rect = ui.allocate_space(vec2(ui.available_finite().width(), height));
let rect = unwrap_or_return_default!(
ui.request_space(vec2(ui.available_finite().width(), height))
);
let style = ui.style().noninteractive();
let mut cmds = vec![PaintCmd::Rect {

View file

@ -267,7 +267,7 @@ impl ColorTest {
fn vertex_gradient(ui: &mut Ui, bg_fill: Srgba, gradient: &Gradient) -> Response {
use crate::paint::*;
let rect = ui.allocate_space(GRADIENT_SIZE);
let rect = unwrap_or_return_default!(ui.request_space(GRADIENT_SIZE));
if bg_fill != Default::default() {
let mut triangles = Triangles::default();
triangles.add_colored_rect(rect, bg_fill);

View file

@ -93,7 +93,7 @@ impl DemoWindow {
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.label("You can pretty easily paint your own small icons:");
let rect = ui.allocate_space(Vec2::splat(16.0));
if let Some(rect) = ui.request_space(Vec2::splat(16.0)) {
let painter = ui.painter();
let c = rect.center();
let r = rect.width() / 2.0 - 1.0;
@ -103,6 +103,7 @@ impl DemoWindow {
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);
painter.line_segment([c, c + r * Vec2::angled(TAU * 1.0 / 8.0)], stroke);
painter.line_segment([c, c + r * Vec2::angled(TAU * 3.0 / 8.0)], stroke);
}
});
});
}
@ -210,9 +211,9 @@ impl BoxPainting {
ui.add(Slider::f32(&mut self.stroke_width, 0.0..=10.0).text("stroke_width"));
ui.add(Slider::usize(&mut self.num_boxes, 0..=5).text("num_boxes"));
let pos = ui
.allocate_space(vec2(self.size.x * (self.num_boxes as f32), self.size.y))
.min;
let rect = ui.request_space(vec2(self.size.x * (self.num_boxes as f32), self.size.y));
let rect = unwrap_or_return_default!(rect);
let pos = rect.min;
let mut cmds = vec![];
for i in 0..self.num_boxes {
@ -265,7 +266,7 @@ impl Painting {
}
fn content(&mut self, ui: &mut Ui) {
let rect = ui.allocate_space(ui.available_finite().size());
let rect = unwrap_or_return_default!(ui.request_space(ui.available_finite().size()));
let response = ui.interact(rect, ui.id(), Sense::drag());
let rect = response.rect;
let clip_rect = ui.clip_rect().intersect(rect); // Make sure we don't paint out of bounds

View file

@ -13,7 +13,7 @@ use crate::*;
pub fn toggle(ui: &mut Ui, on: &mut bool) -> Response {
// Widget code can be broken up in four steps:
// 1. Decide a size for the widget
// 2. Allocate space for it
// 2. Request space for it
// 3. Handle interactions with the widget (if any)
// 4. Paint the widget
@ -22,9 +22,14 @@ pub fn toggle(ui: &mut Ui, on: &mut bool) -> Response {
// but in this example we have a fixed size widget of the default size for a button:
let desired_size = ui.style().spacing.interact_size;
// 2. Allocating space:
// 2. Requesting space:
// This is where we get a region (`Rect`) of the screen assigned.
let rect = ui.allocate_space(desired_size);
let rect = ui.request_space(desired_size);
// If we get `None` back from `request_space`, it means this widgets isn't visible.
// In this case we shouldn't do anything else and just return early.
// Egui has a helper macro for this:
let rect = unwrap_or_return_default!(rect);
// 3. Interact: Time to check for clicks!
// To do that we need an `Id` for the button.

View file

@ -19,7 +19,7 @@ impl Texture {
if size.x > ui.available().width() {
size *= ui.available().width() / size.x;
}
let rect = ui.allocate_space(size);
let rect = unwrap_or_return_default!(ui.request_space(size));
let mut triangles = Triangles::default();
triangles.add_rect_with_uv(rect, [pos2(0.0, 0.0), pos2(1.0, 1.0)].into(), WHITE);
ui.painter().add(PaintCmd::triangles(triangles));
@ -34,7 +34,7 @@ impl Texture {
.mouse
.pos
.unwrap_or_else(|| ui.min_rect().left_top());
let zoom_rect = ui.allocate_space(vec2(128.0, 128.0));
let zoom_rect = unwrap_or_return_default!(ui.request_space(vec2(128.0, 128.0)));
let u = remap_clamp(pos.x, rect.x_range(), 0.0..=tex_w);
let v = remap_clamp(pos.y, rect.y_range(), 0.0..=tex_h);

View file

@ -120,3 +120,15 @@ pub fn text_egui_e2e() {
assert!(!paint_jobs.is_empty());
}
}
#[macro_export]
macro_rules! unwrap_or_return_default {
($option:expr) => {
match $option {
Some(value) => value,
None => {
return Default::default();
}
}
};
}

View file

@ -1,11 +1,6 @@
#![allow(clippy::if_same_then_else)]
use crate::{
color::*,
math::*,
paint::{Stroke, TextStyle},
types::*,
};
use crate::{color::*, *};
/// Specifies the look and feel of a `Ui`.
#[derive(Clone, Debug, PartialEq)]
@ -474,7 +469,8 @@ impl Stroke {
ui.label(text);
// stroke preview:
let stroke_rect = ui.allocate_space(ui.style().spacing.interact_size);
let stroke_rect =
unwrap_or_return_default!(ui.request_space(ui.style().spacing.interact_size));
let left = stroke_rect.left_center();
let right = stroke_rect.right_center();
ui.painter().line_segment([left, right], (*width, *color));

View file

@ -56,8 +56,10 @@ impl Default for CursorIcon {
#[derive(Clone)]
pub struct Response {
// CONTEXT:
/// Used for optionally showing a tooltip
pub ctx: Arc<Context>,
/// Used for optionally showing a tooltip.
/// If `None`, we likely come from a cull widgets and shouldn't show any
/// tooltip.
pub ctx: Option<Arc<Context>>,
// IN:
/// The area of the screen we are talking about
@ -83,17 +85,18 @@ pub struct Response {
pub has_kb_focus: bool,
}
impl std::fmt::Debug for Response {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Response")
.field("rect", &self.rect)
.field("sense", &self.sense)
.field("hovered", &self.hovered)
.field("clicked", &self.clicked)
.field("double_clicked", &self.double_clicked)
.field("active", &self.active)
.field("has_kb_focus", &self.has_kb_focus)
.finish()
impl Default for Response {
fn default() -> Self {
Self {
ctx: None,
rect: Rect::nothing(),
sense: Sense::nothing(),
hovered: false,
clicked: false,
double_clicked: false,
active: false,
has_kb_focus: false,
}
}
}
@ -101,7 +104,11 @@ impl Response {
/// Show this UI if the item was hovered (i.e. a tooltip)
pub fn on_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
if self.hovered {
crate::containers::show_tooltip(&self.ctx, add_contents);
if let Some(ctx) = &self.ctx {
crate::containers::show_tooltip(ctx, add_contents);
} else {
panic!("We shouldn't be able to hover something without a Context");
}
}
self
}
@ -123,9 +130,12 @@ impl Response {
/// A logical "or" operation.
/// For instance `a.union(b).hovered` means "was either a or b hovered?".
pub fn union(&self, other: Self) -> Self {
assert!(Arc::ptr_eq(&self.ctx, &other.ctx));
if let (Some(lc), Some(rc)) = (&self.ctx, &other.ctx) {
debug_assert!(Arc::ptr_eq(lc, rc));
}
Self {
ctx: other.ctx,
ctx: other.ctx.or_else(|| self.ctx.clone()),
rect: self.rect.union(other.rect),
sense: self.sense.union(other.sense),
hovered: self.hovered || other.hovered,
@ -137,6 +147,23 @@ impl Response {
}
}
impl std::fmt::Debug for Response {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Response")
.field("ctx", &self.ctx.is_some())
.field("rect", &self.rect)
.field("sense", &self.sense)
.field("hovered", &self.hovered)
.field("clicked", &self.clicked)
.field("double_clicked", &self.double_clicked)
.field("active", &self.active)
.field("has_kb_focus", &self.has_kb_focus)
.finish()
}
}
// ----------------------------------------------------------------------------
/// To summarize the response from many widgets you can use this pattern:
///
/// ```

View file

@ -462,6 +462,26 @@ impl Ui {
rect
}
/// Potential future of `allocate_space`.
/// Returns `None` if the allocated rectangle is outside the clip rect and is thus invisible.
/// Works well together with the `unwrap_or_return_default!` macro:
/// ```
/// # use egui::*;
/// # let mut ui = Ui::test();
/// let desired_size = vec2(100.0, 200.0);
/// let rect = unwrap_or_return_default!(ui.request_space(desired_size));
/// assert_eq!(rect, Rect::from_min_size(pos2(0.0, 0.0), desired_size));
/// ```
///
pub fn request_space(&mut self, desired_size: Vec2) -> Option<Rect> {
let rect = self.allocate_space(desired_size);
if self.clip_rect().intersects(rect) {
Some(rect)
} else {
None
}
}
/// Reserve this much space and move the cursor.
/// Returns where to put the widget.
fn reserve_space_impl(&mut self, child_size: Vec2) -> Rect {

View file

@ -43,7 +43,7 @@ pub fn show_color(ui: &mut Ui, color: impl Into<Srgba>, desired_size: Vec2) -> R
}
fn show_srgba(ui: &mut Ui, srgba: Srgba, desired_size: Vec2) -> Response {
let rect = ui.allocate_space(desired_size);
let rect = unwrap_or_return_default!(ui.request_space(desired_size));
background_checkers(ui.painter(), rect);
ui.painter().add(PaintCmd::Rect {
rect,
@ -56,7 +56,7 @@ fn show_srgba(ui: &mut Ui, srgba: Srgba, desired_size: Vec2) -> Response {
fn color_button(ui: &mut Ui, color: Srgba) -> Response {
let desired_size = ui.style().spacing.interact_size;
let rect = ui.allocate_space(desired_size);
let rect = unwrap_or_return_default!(ui.request_space(desired_size));
let id = ui.make_position_id();
let response = ui.interact(rect, id, Sense::click());
let visuals = ui.style().interact(&response);
@ -77,7 +77,7 @@ fn color_slider_1d(ui: &mut Ui, value: &mut f32, color_at: impl Fn(f32) -> Srgba
ui.style().spacing.slider_width,
ui.style().spacing.interact_size.y * 2.0,
);
let rect = ui.allocate_space(desired_size);
let rect = unwrap_or_return_default!(ui.request_space(desired_size));
let id = ui.make_position_id();
let response = ui.interact(rect, id, Sense::click_and_drag());
@ -136,7 +136,7 @@ fn color_slider_2d(
color_at: impl Fn(f32, f32) -> Srgba,
) -> Response {
let desired_size = Vec2::splat(ui.style().spacing.slider_width);
let rect = ui.allocate_space(desired_size);
let rect = unwrap_or_return_default!(ui.request_space(desired_size));
let id = ui.make_position_id();
let response = ui.interact(rect, id, Sense::click_and_drag());

View file

@ -49,7 +49,7 @@ impl Widget for Image {
bg_fill,
tint,
} = self;
let rect = ui.allocate_space(desired_size);
let rect = unwrap_or_return_default!(ui.request_space(desired_size));
if bg_fill != Default::default() {
let mut triangles = Triangles::default();
triangles.add_colored_rect(rect, bg_fill);

View file

@ -135,7 +135,7 @@ macro_rules! label {
impl Widget for Label {
fn ui(self, ui: &mut Ui) -> Response {
let galley = self.layout(ui);
let rect = ui.allocate_space(galley.size);
let rect = unwrap_or_return_default!(ui.request_space(galley.size));
self.paint_galley(ui, rect.min, galley);
ui.interact_hover(rect)
}
@ -207,7 +207,7 @@ impl Widget for Hyperlink {
let text_style = text_style.unwrap_or_else(|| ui.style().body_text_style);
let font = &ui.fonts()[text_style];
let galley = font.layout_multiline(text, ui.available().width());
let rect = ui.allocate_space(galley.size);
let rect = unwrap_or_return_default!(ui.request_space(galley.size));
let id = ui.make_position_id();
let response = ui.interact(rect, id, Sense::click());
@ -318,7 +318,7 @@ impl Widget for Button {
let galley = font.layout_multiline(text, ui.available().width());
let mut desired_size = galley.size + 2.0 * button_padding;
desired_size = desired_size.at_least(ui.style().spacing.interact_size);
let rect = ui.allocate_space(desired_size);
let rect = unwrap_or_return_default!(ui.request_space(desired_size));
let id = ui.make_position_id();
let response = ui.interact(rect, id, sense);
@ -390,7 +390,7 @@ impl<'a> Widget for Checkbox<'a> {
button_padding + vec2(icon_width + icon_spacing, 0.0) + galley.size + button_padding;
desired_size = desired_size.at_least(spacing.interact_size);
desired_size.y = desired_size.y.max(icon_width);
let rect = ui.allocate_space(desired_size);
let rect = unwrap_or_return_default!(ui.request_space(desired_size));
let id = ui.make_position_id();
let response = ui.interact(rect, id, Sense::click());
@ -475,7 +475,7 @@ impl Widget for RadioButton {
button_padding + vec2(icon_width + icon_spacing, 0.0) + galley.size + button_padding;
desired_size = desired_size.at_least(ui.style().spacing.interact_size);
desired_size.y = desired_size.y.max(icon_width);
let rect = ui.allocate_space(desired_size);
let rect = unwrap_or_return_default!(ui.request_space(desired_size));
let id = ui.make_position_id();
let response = ui.interact(rect, id, Sense::click());
@ -544,7 +544,8 @@ impl Widget for Separator {
let (points, rect) = match ui.layout().dir() {
Direction::Horizontal => {
let rect = ui.allocate_space(vec2(spacing, available_space.y));
let rect =
unwrap_or_return_default!(ui.request_space(vec2(spacing, available_space.y)));
(
[
pos2(rect.center().x, rect.top()),
@ -554,7 +555,8 @@ impl Widget for Separator {
)
}
Direction::Vertical => {
let rect = ui.allocate_space(vec2(available_space.x, spacing));
let rect =
unwrap_or_return_default!(ui.request_space(vec2(available_space.x, spacing)));
(
[
pos2(rect.left(), rect.center().y),

View file

@ -122,7 +122,7 @@ impl<'t> Widget for TextEdit<'t> {
galley.size.x.max(desired_width.min(available_width)),
galley.size.y.max(line_spacing),
);
let rect = ui.allocate_space(desired_size);
let rect = unwrap_or_return_default!(ui.request_space(desired_size));
let sense = if enabled {
Sense::click_and_drag()
} else {