Support interacting with the background of a `Ui` (#4074)
Add `Ui::interact_bg` which interacts with the ui _behind_ any of its children.
This commit is contained in:
parent
a33ae64785
commit
9096abdeec
|
|
@ -198,100 +198,6 @@ impl ContextImpl {
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Used to store each widget's [Id], [Rect] and [Sense] each frame.
|
|
||||||
/// Used to check for overlaps between widgets when handling events.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub struct WidgetRect {
|
|
||||||
/// The globally unique widget id.
|
|
||||||
///
|
|
||||||
/// For interactive widgets, this better be globally unique.
|
|
||||||
/// If not there will be weird bugs,
|
|
||||||
/// and also big red warning test on the screen in debug builds
|
|
||||||
/// (see [`Options::warn_on_id_clash`]).
|
|
||||||
///
|
|
||||||
/// You can ensure globally unique ids using [`Ui::push_id`].
|
|
||||||
pub id: Id,
|
|
||||||
|
|
||||||
/// What layer the widget is on.
|
|
||||||
pub layer_id: LayerId,
|
|
||||||
|
|
||||||
/// The full widget rectangle.
|
|
||||||
pub rect: Rect,
|
|
||||||
|
|
||||||
/// Where the widget is.
|
|
||||||
///
|
|
||||||
/// This is after clipping with the parent ui clip rect.
|
|
||||||
pub interact_rect: Rect,
|
|
||||||
|
|
||||||
/// How the widget responds to interaction.
|
|
||||||
pub sense: Sense,
|
|
||||||
|
|
||||||
/// Is the widget enabled?
|
|
||||||
pub enabled: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores the positions of all widgets generated during a single egui update/frame.
|
|
||||||
///
|
|
||||||
/// Actually, only those that are on screen.
|
|
||||||
#[derive(Default, Clone, PartialEq, Eq)]
|
|
||||||
pub struct WidgetRects {
|
|
||||||
/// All widgets, in painting order.
|
|
||||||
pub by_layer: HashMap<LayerId, Vec<WidgetRect>>,
|
|
||||||
|
|
||||||
/// All widgets
|
|
||||||
pub by_id: IdMap<WidgetRect>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WidgetRects {
|
|
||||||
/// Clear the contents while retaining allocated memory.
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
let Self { by_layer, by_id } = self;
|
|
||||||
|
|
||||||
for rects in by_layer.values_mut() {
|
|
||||||
rects.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
by_id.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert the given widget rect in the given layer.
|
|
||||||
pub fn insert(&mut self, layer_id: LayerId, widget_rect: WidgetRect) {
|
|
||||||
if !widget_rect.interact_rect.is_positive() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Self { by_layer, by_id } = self;
|
|
||||||
|
|
||||||
let layer_widgets = by_layer.entry(layer_id).or_default();
|
|
||||||
|
|
||||||
match by_id.entry(widget_rect.id) {
|
|
||||||
std::collections::hash_map::Entry::Vacant(entry) => {
|
|
||||||
// A new widget
|
|
||||||
entry.insert(widget_rect);
|
|
||||||
layer_widgets.push(widget_rect);
|
|
||||||
}
|
|
||||||
std::collections::hash_map::Entry::Occupied(mut entry) => {
|
|
||||||
// e.g. calling `response.interact(…)` to add more interaction.
|
|
||||||
let existing = entry.get_mut();
|
|
||||||
existing.rect = existing.rect.union(widget_rect.rect);
|
|
||||||
existing.interact_rect = existing.interact_rect.union(widget_rect.interact_rect);
|
|
||||||
existing.sense |= widget_rect.sense;
|
|
||||||
existing.enabled |= widget_rect.enabled;
|
|
||||||
|
|
||||||
// Find the existing widget in this layer and update it:
|
|
||||||
for previous in layer_widgets.iter_mut().rev() {
|
|
||||||
if previous.id == widget_rect.id {
|
|
||||||
*previous = *existing;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// State stored per viewport
|
/// State stored per viewport
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct ViewportState {
|
struct ViewportState {
|
||||||
|
|
@ -546,12 +452,7 @@ impl ContextImpl {
|
||||||
.map(|(i, id)| (*id, i))
|
.map(|(i, id)| (*id, i))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut layers: Vec<LayerId> = viewport
|
let mut layers: Vec<LayerId> = viewport.widgets_prev_frame.layer_ids().collect();
|
||||||
.widgets_prev_frame
|
|
||||||
.by_layer
|
|
||||||
.keys()
|
|
||||||
.copied()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
layers.sort_by(|a, b| {
|
layers.sort_by(|a, b| {
|
||||||
if a.order == b.order {
|
if a.order == b.order {
|
||||||
|
|
@ -1124,23 +1025,19 @@ impl Context {
|
||||||
w.sense.drag = false;
|
w.sense.drag = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if w.interact_rect.is_positive() {
|
// Remember this widget
|
||||||
// Remember this widget
|
self.write(|ctx| {
|
||||||
self.write(|ctx| {
|
let viewport = ctx.viewport();
|
||||||
let viewport = ctx.viewport();
|
|
||||||
|
|
||||||
// We add all widgets here, even non-interactive ones,
|
// We add all widgets here, even non-interactive ones,
|
||||||
// because we need this list not only for checking for blocking widgets,
|
// because we need this list not only for checking for blocking widgets,
|
||||||
// but also to know when we have reached the widget we are checking for cover.
|
// but also to know when we have reached the widget we are checking for cover.
|
||||||
viewport.widgets_this_frame.insert(w.layer_id, w);
|
viewport.widgets_this_frame.insert(w.layer_id, w);
|
||||||
|
|
||||||
if w.sense.focusable {
|
if w.sense.focusable {
|
||||||
ctx.memory.interested_in_focus(w.id);
|
ctx.memory.interested_in_focus(w.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
// Don't remember invisible widgets
|
|
||||||
}
|
|
||||||
|
|
||||||
if !w.enabled || !w.sense.focusable || !w.layer_id.allow_interaction() {
|
if !w.enabled || !w.sense.focusable || !w.layer_id.allow_interaction() {
|
||||||
// Not interested or allowed input:
|
// Not interested or allowed input:
|
||||||
|
|
@ -1175,9 +1072,8 @@ impl Context {
|
||||||
let viewport = ctx.viewport();
|
let viewport = ctx.viewport();
|
||||||
viewport
|
viewport
|
||||||
.widgets_this_frame
|
.widgets_this_frame
|
||||||
.by_id
|
.get(id)
|
||||||
.get(&id)
|
.or_else(|| viewport.widgets_prev_frame.get(id))
|
||||||
.or_else(|| viewport.widgets_prev_frame.by_id.get(&id))
|
|
||||||
.copied()
|
.copied()
|
||||||
})
|
})
|
||||||
.map(|widget_rect| self.get_response(widget_rect))
|
.map(|widget_rect| self.get_response(widget_rect))
|
||||||
|
|
@ -1916,13 +1812,16 @@ impl Context {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
fn debug_painting(&self) {
|
fn debug_painting(&self) {
|
||||||
let paint_widget = |widget: &WidgetRect, text: &str, color: Color32| {
|
let paint_widget = |widget: &WidgetRect, text: &str, color: Color32| {
|
||||||
let painter = Painter::new(self.clone(), widget.layer_id, Rect::EVERYTHING);
|
let rect = widget.interact_rect;
|
||||||
painter.debug_rect(widget.interact_rect, color, text);
|
if rect.is_positive() {
|
||||||
|
let painter = Painter::new(self.clone(), widget.layer_id, Rect::EVERYTHING);
|
||||||
|
painter.debug_rect(rect, color, text);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let paint_widget_id = |id: Id, text: &str, color: Color32| {
|
let paint_widget_id = |id: Id, text: &str, color: Color32| {
|
||||||
if let Some(widget) =
|
if let Some(widget) =
|
||||||
self.write(|ctx| ctx.viewport().widgets_this_frame.by_id.get(&id).cloned())
|
self.write(|ctx| ctx.viewport().widgets_this_frame.get(id).cloned())
|
||||||
{
|
{
|
||||||
paint_widget(&widget, text, color);
|
paint_widget(&widget, text, color);
|
||||||
}
|
}
|
||||||
|
|
@ -1931,8 +1830,8 @@ impl Context {
|
||||||
if self.style().debug.show_interactive_widgets {
|
if self.style().debug.show_interactive_widgets {
|
||||||
// Show all interactive widgets:
|
// Show all interactive widgets:
|
||||||
let rects = self.write(|ctx| ctx.viewport().widgets_this_frame.clone());
|
let rects = self.write(|ctx| ctx.viewport().widgets_this_frame.clone());
|
||||||
for (layer_id, rects) in rects.by_layer {
|
for (layer_id, rects) in rects.layers() {
|
||||||
let painter = Painter::new(self.clone(), layer_id, Rect::EVERYTHING);
|
let painter = Painter::new(self.clone(), *layer_id, Rect::EVERYTHING);
|
||||||
for rect in rects {
|
for rect in rects {
|
||||||
if rect.sense.interactive() {
|
if rect.sense.interactive() {
|
||||||
let (color, text) = if rect.sense.click && rect.sense.drag {
|
let (color, text) = if rect.sense.click && rect.sense.drag {
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,7 @@ pub fn hit_test(
|
||||||
let mut close: Vec<WidgetRect> = layer_order
|
let mut close: Vec<WidgetRect> = layer_order
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|layer| layer.order.allow_interaction())
|
.filter(|layer| layer.order.allow_interaction())
|
||||||
.filter_map(|layer_id| widgets.by_layer.get(layer_id))
|
.flat_map(|&layer_id| widgets.get_layer(layer_id))
|
||||||
.flatten()
|
|
||||||
.filter(|&w| {
|
.filter(|&w| {
|
||||||
let pos_in_layer = pos_in_layers.get(&w.layer_id).copied().unwrap_or(pos);
|
let pos_in_layer = pos_in_layers.get(&w.layer_id).copied().unwrap_or(pos);
|
||||||
let dist_sq = w.interact_rect.distance_sq_to_pos(pos_in_layer);
|
let dist_sq = w.interact_rect.distance_sq_to_pos(pos_in_layer);
|
||||||
|
|
|
||||||
|
|
@ -107,13 +107,13 @@ pub(crate) fn interact(
|
||||||
crate::profile_function!();
|
crate::profile_function!();
|
||||||
|
|
||||||
if let Some(id) = interaction.potential_click_id {
|
if let Some(id) = interaction.potential_click_id {
|
||||||
if !widgets.by_id.contains_key(&id) {
|
if !widgets.contains(id) {
|
||||||
// The widget we were interested in clicking is gone.
|
// The widget we were interested in clicking is gone.
|
||||||
interaction.potential_click_id = None;
|
interaction.potential_click_id = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(id) = interaction.potential_drag_id {
|
if let Some(id) = interaction.potential_drag_id {
|
||||||
if !widgets.by_id.contains_key(&id) {
|
if !widgets.contains(id) {
|
||||||
// The widget we were interested in dragging is gone.
|
// The widget we were interested in dragging is gone.
|
||||||
// This is fine! This could be drag-and-drop,
|
// This is fine! This could be drag-and-drop,
|
||||||
// and the widget being dragged is now "in the air" and thus
|
// and the widget being dragged is now "in the air" and thus
|
||||||
|
|
@ -145,7 +145,7 @@ pub(crate) fn interact(
|
||||||
if click.is_some() {
|
if click.is_some() {
|
||||||
if let Some(widget) = interaction
|
if let Some(widget) = interaction
|
||||||
.potential_click_id
|
.potential_click_id
|
||||||
.and_then(|id| widgets.by_id.get(&id))
|
.and_then(|id| widgets.get(id))
|
||||||
{
|
{
|
||||||
clicked = Some(widget.id);
|
clicked = Some(widget.id);
|
||||||
}
|
}
|
||||||
|
|
@ -160,10 +160,7 @@ pub(crate) fn interact(
|
||||||
|
|
||||||
if dragged.is_none() {
|
if dragged.is_none() {
|
||||||
// Check if we started dragging something new:
|
// Check if we started dragging something new:
|
||||||
if let Some(widget) = interaction
|
if let Some(widget) = interaction.potential_drag_id.and_then(|id| widgets.get(id)) {
|
||||||
.potential_drag_id
|
|
||||||
.and_then(|id| widgets.by_id.get(&id))
|
|
||||||
{
|
|
||||||
let is_dragged = if widget.sense.click && widget.sense.drag {
|
let is_dragged = if widget.sense.click && widget.sense.drag {
|
||||||
// This widget is sensitive to both clicks and drags.
|
// This widget is sensitive to both clicks and drags.
|
||||||
// When the mouse first is pressed, it could be either,
|
// When the mouse first is pressed, it could be either,
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@ pub mod text_selection;
|
||||||
mod ui;
|
mod ui;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod viewport;
|
pub mod viewport;
|
||||||
|
mod widget_rect;
|
||||||
pub mod widget_text;
|
pub mod widget_text;
|
||||||
pub mod widgets;
|
pub mod widgets;
|
||||||
|
|
||||||
|
|
@ -443,7 +444,7 @@ pub mod text {
|
||||||
|
|
||||||
pub use {
|
pub use {
|
||||||
containers::*,
|
containers::*,
|
||||||
context::{Context, RepaintCause, RequestRepaintInfo, WidgetRect, WidgetRects},
|
context::{Context, RepaintCause, RequestRepaintInfo},
|
||||||
data::{
|
data::{
|
||||||
input::*,
|
input::*,
|
||||||
output::{
|
output::{
|
||||||
|
|
@ -466,6 +467,7 @@ pub use {
|
||||||
text::{Galley, TextFormat},
|
text::{Galley, TextFormat},
|
||||||
ui::Ui,
|
ui::Ui,
|
||||||
viewport::*,
|
viewport::*,
|
||||||
|
widget_rect::{WidgetRect, WidgetRects},
|
||||||
widget_text::{RichText, WidgetText},
|
widget_text::{RichText, WidgetText},
|
||||||
widgets::*,
|
widgets::*,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -658,7 +658,7 @@ impl Response {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
rect: self.rect,
|
rect: self.rect,
|
||||||
interact_rect: self.interact_rect,
|
interact_rect: self.interact_rect,
|
||||||
sense,
|
sense: self.sense | sense,
|
||||||
enabled: self.enabled,
|
enabled: self.enabled,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ impl Ui {
|
||||||
/// [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`].
|
/// [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`].
|
||||||
pub fn new(ctx: Context, layer_id: LayerId, id: Id, max_rect: Rect, clip_rect: Rect) -> Self {
|
pub fn new(ctx: Context, layer_id: LayerId, id: Id, max_rect: Rect, clip_rect: Rect) -> Self {
|
||||||
let style = ctx.style();
|
let style = ctx.style();
|
||||||
Ui {
|
let ui = Ui {
|
||||||
id,
|
id,
|
||||||
next_auto_id_source: id.with("auto").value(),
|
next_auto_id_source: id.with("auto").value(),
|
||||||
painter: Painter::new(ctx, layer_id, clip_rect),
|
painter: Painter::new(ctx, layer_id, clip_rect),
|
||||||
|
|
@ -83,7 +83,20 @@ impl Ui {
|
||||||
placer: Placer::new(max_rect, Layout::default()),
|
placer: Placer::new(max_rect, Layout::default()),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
menu_state: None,
|
menu_state: None,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// Register in the widget stack early, to ensure we are behind all widgets we contain:
|
||||||
|
let start_rect = Rect::NOTHING; // This will be overwritten when/if `interact_bg` is called
|
||||||
|
ui.ctx().create_widget(WidgetRect {
|
||||||
|
id: ui.id,
|
||||||
|
layer_id: ui.layer_id(),
|
||||||
|
rect: start_rect,
|
||||||
|
interact_rect: start_rect,
|
||||||
|
sense: Sense::hover(),
|
||||||
|
enabled: ui.enabled,
|
||||||
|
});
|
||||||
|
|
||||||
|
ui
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new [`Ui`] at a specific region.
|
/// Create a new [`Ui`] at a specific region.
|
||||||
|
|
@ -101,7 +114,7 @@ impl Ui {
|
||||||
crate::egui_assert!(!max_rect.any_nan());
|
crate::egui_assert!(!max_rect.any_nan());
|
||||||
let next_auto_id_source = Id::new(self.next_auto_id_source).with("child").value();
|
let next_auto_id_source = Id::new(self.next_auto_id_source).with("child").value();
|
||||||
self.next_auto_id_source = self.next_auto_id_source.wrapping_add(1);
|
self.next_auto_id_source = self.next_auto_id_source.wrapping_add(1);
|
||||||
Ui {
|
let child_ui = Ui {
|
||||||
id: self.id.with(id_source),
|
id: self.id.with(id_source),
|
||||||
next_auto_id_source,
|
next_auto_id_source,
|
||||||
painter: self.painter.clone(),
|
painter: self.painter.clone(),
|
||||||
|
|
@ -109,7 +122,20 @@ impl Ui {
|
||||||
placer: Placer::new(max_rect, layout),
|
placer: Placer::new(max_rect, layout),
|
||||||
enabled: self.enabled,
|
enabled: self.enabled,
|
||||||
menu_state: self.menu_state.clone(),
|
menu_state: self.menu_state.clone(),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// Register in the widget stack early, to ensure we are behind all widgets we contain:
|
||||||
|
let start_rect = Rect::NOTHING; // This will be overwritten when/if `interact_bg` is called
|
||||||
|
child_ui.ctx().create_widget(WidgetRect {
|
||||||
|
id: child_ui.id,
|
||||||
|
layer_id: child_ui.layer_id(),
|
||||||
|
rect: start_rect,
|
||||||
|
interact_rect: start_rect,
|
||||||
|
sense: Sense::hover(),
|
||||||
|
enabled: child_ui.enabled,
|
||||||
|
});
|
||||||
|
|
||||||
|
child_ui
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
|
|
@ -668,6 +694,15 @@ impl Ui {
|
||||||
self.interact(rect, id, sense)
|
self.interact(rect, id, sense)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Interact with the background of this [`Ui`],
|
||||||
|
/// i.e. behind all the widgets.
|
||||||
|
///
|
||||||
|
/// The rectangle of the [`Response`] (and interactive area) will be [`Self::min_rect`].
|
||||||
|
pub fn interact_bg(&self, sense: Sense) -> Response {
|
||||||
|
// This will update the WidgetRect that was first created in `Ui::new`.
|
||||||
|
self.interact(self.min_rect(), self.id, sense)
|
||||||
|
}
|
||||||
|
|
||||||
/// Is the pointer (mouse/touch) above this rectangle in this [`Ui`]?
|
/// Is the pointer (mouse/touch) above this rectangle in this [`Ui`]?
|
||||||
///
|
///
|
||||||
/// The `clip_rect` and layer of this [`Ui`] will be respected, so, for instance,
|
/// The `clip_rect` and layer of this [`Ui`] will be respected, so, for instance,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
use ahash::HashMap;
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Used to store each widget's [Id], [Rect] and [Sense] each frame.
|
||||||
|
///
|
||||||
|
/// Used to check which widget gets input when a user clicks somewhere.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub struct WidgetRect {
|
||||||
|
/// The globally unique widget id.
|
||||||
|
///
|
||||||
|
/// For interactive widgets, this better be globally unique.
|
||||||
|
/// If not there will be weird bugs,
|
||||||
|
/// and also big red warning test on the screen in debug builds
|
||||||
|
/// (see [`Options::warn_on_id_clash`]).
|
||||||
|
///
|
||||||
|
/// You can ensure globally unique ids using [`Ui::push_id`].
|
||||||
|
pub id: Id,
|
||||||
|
|
||||||
|
/// What layer the widget is on.
|
||||||
|
pub layer_id: LayerId,
|
||||||
|
|
||||||
|
/// The full widget rectangle.
|
||||||
|
pub rect: Rect,
|
||||||
|
|
||||||
|
/// Where the widget is.
|
||||||
|
///
|
||||||
|
/// This is after clipping with the parent ui clip rect.
|
||||||
|
pub interact_rect: Rect,
|
||||||
|
|
||||||
|
/// How the widget responds to interaction.
|
||||||
|
pub sense: Sense,
|
||||||
|
|
||||||
|
/// Is the widget enabled?
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores the [`WidgetRect`]s of all widgets generated during a single egui update/frame.
|
||||||
|
///
|
||||||
|
/// All [`Ui`]s have a [`WidgetRects`], but whether or not their rects are correct
|
||||||
|
/// depends on if [`Ui::interact_bg`] was ever called.
|
||||||
|
#[derive(Default, Clone, PartialEq, Eq)]
|
||||||
|
pub struct WidgetRects {
|
||||||
|
/// All widgets, in painting order.
|
||||||
|
by_layer: HashMap<LayerId, Vec<WidgetRect>>,
|
||||||
|
|
||||||
|
/// All widgets, by id, and their order in their respective layer
|
||||||
|
by_id: IdMap<(usize, WidgetRect)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetRects {
|
||||||
|
/// All known layers with widgets.
|
||||||
|
pub fn layer_ids(&self) -> impl ExactSizeIterator<Item = LayerId> + '_ {
|
||||||
|
self.by_layer.keys().copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn layers(&self) -> impl Iterator<Item = (&LayerId, &[WidgetRect])> + '_ {
|
||||||
|
self.by_layer
|
||||||
|
.iter()
|
||||||
|
.map(|(layer_id, rects)| (layer_id, &rects[..]))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get(&self, id: Id) -> Option<&WidgetRect> {
|
||||||
|
self.by_id.get(&id).map(|(_, w)| w)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn contains(&self, id: Id) -> bool {
|
||||||
|
self.by_id.contains_key(&id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All widgets in this layer, sorted back-to-front.
|
||||||
|
#[inline]
|
||||||
|
pub fn get_layer(&self, layer_id: LayerId) -> impl Iterator<Item = &WidgetRect> + '_ {
|
||||||
|
self.by_layer.get(&layer_id).into_iter().flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear the contents while retaining allocated memory.
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
let Self { by_layer, by_id } = self;
|
||||||
|
|
||||||
|
for rects in by_layer.values_mut() {
|
||||||
|
rects.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
by_id.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert the given widget rect in the given layer.
|
||||||
|
pub fn insert(&mut self, layer_id: LayerId, widget_rect: WidgetRect) {
|
||||||
|
let Self { by_layer, by_id } = self;
|
||||||
|
|
||||||
|
let layer_widgets = by_layer.entry(layer_id).or_default();
|
||||||
|
|
||||||
|
match by_id.entry(widget_rect.id) {
|
||||||
|
std::collections::hash_map::Entry::Vacant(entry) => {
|
||||||
|
// A new widget
|
||||||
|
let idx_in_layer = layer_widgets.len();
|
||||||
|
entry.insert((idx_in_layer, widget_rect));
|
||||||
|
layer_widgets.push(widget_rect);
|
||||||
|
}
|
||||||
|
std::collections::hash_map::Entry::Occupied(mut entry) => {
|
||||||
|
// This is a known widget, but we might need to update it!
|
||||||
|
// e.g. calling `response.interact(…)` to add more interaction.
|
||||||
|
let (idx_in_layer, existing) = entry.get_mut();
|
||||||
|
|
||||||
|
// Update it:
|
||||||
|
existing.rect = widget_rect.rect; // last wins
|
||||||
|
existing.interact_rect = widget_rect.interact_rect; // last wins
|
||||||
|
existing.sense |= widget_rect.sense;
|
||||||
|
existing.enabled |= widget_rect.enabled;
|
||||||
|
|
||||||
|
egui_assert!(
|
||||||
|
existing.layer_id == widget_rect.layer_id,
|
||||||
|
"Widget changed layer_id during the frame"
|
||||||
|
);
|
||||||
|
|
||||||
|
if existing.layer_id == widget_rect.layer_id {
|
||||||
|
layer_widgets[*idx_in_layer] = *existing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue