mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Add popup backdrops
This commit is contained in:
@@ -1,9 +1,22 @@
|
||||
use emath::{Align2, Vec2};
|
||||
use emath::{Align2, Rect, Vec2};
|
||||
|
||||
use crate::{
|
||||
Area, Color32, Context, Frame, Id, InnerResponse, Order, Response, Sense, Ui, UiBuilder, UiKind,
|
||||
Area, Color32, Context, Frame, Id, InnerResponse, Order, Response, Sense, Ui, UiKind,
|
||||
};
|
||||
|
||||
/// Paint a full-screen backdrop on the given [`Ui`] and return whether
|
||||
/// a click landed outside `content_rect` (i.e. on the backdrop).
|
||||
///
|
||||
/// This is used by both [`Modal`] and [`crate::Popup`].
|
||||
pub(crate) fn paint_backdrop(ui: &mut Ui, color: Color32) -> bool {
|
||||
let bg_rect = ui.ctx().viewport_rect();
|
||||
|
||||
let response = ui.interact(bg_rect, ui.unique_id().with("backdrop"), Sense::click_and_drag());
|
||||
ui.painter().rect_filled(response.rect, 0.0, color);
|
||||
|
||||
response.clicked() && !ui.response().contains_pointer()
|
||||
}
|
||||
|
||||
/// A modal dialog.
|
||||
///
|
||||
/// Similar to a [`crate::Window`] but centered and with a backdrop that
|
||||
@@ -26,7 +39,7 @@ impl Modal {
|
||||
pub fn new(id: Id) -> Self {
|
||||
Self {
|
||||
area: Self::default_area(id),
|
||||
backdrop_color: Color32::from_black_alpha(100),
|
||||
backdrop_color: Color32::PLACEHOLDER,
|
||||
frame: None,
|
||||
}
|
||||
}
|
||||
@@ -57,7 +70,7 @@ impl Modal {
|
||||
|
||||
/// Set the backdrop color of the modal.
|
||||
///
|
||||
/// Default is `Color32::from_black_alpha(100)`.
|
||||
/// Default comes from [`crate::Visuals::modal_backdrop_color`].
|
||||
#[inline]
|
||||
pub fn backdrop_color(mut self, color: Color32) -> Self {
|
||||
self.backdrop_color = color;
|
||||
@@ -77,42 +90,34 @@ impl Modal {
|
||||
pub fn show<T>(self, ctx: &Context, content: impl FnOnce(&mut Ui) -> T) -> ModalResponse<T> {
|
||||
let Self {
|
||||
area,
|
||||
backdrop_color,
|
||||
mut backdrop_color,
|
||||
frame,
|
||||
} = self;
|
||||
|
||||
if backdrop_color == Color32::PLACEHOLDER {
|
||||
backdrop_color = ctx.global_style().visuals.modal_backdrop_color;
|
||||
}
|
||||
|
||||
let is_top_modal = ctx.memory_mut(|mem| {
|
||||
mem.set_modal_layer(area.layer());
|
||||
mem.top_modal_layer() == Some(area.layer())
|
||||
});
|
||||
let any_popup_open = crate::Popup::is_any_open(ctx);
|
||||
let InnerResponse {
|
||||
inner: (inner, backdrop_response),
|
||||
inner: (inner, backdrop_clicked),
|
||||
response,
|
||||
} = area.show(ctx, |ui| {
|
||||
let bg_rect = ui.ctx().content_rect();
|
||||
let bg_sense = Sense::CLICK | Sense::DRAG;
|
||||
let mut backdrop = ui.new_child(UiBuilder::new().sense(bg_sense).max_rect(bg_rect));
|
||||
backdrop.set_min_size(bg_rect.size());
|
||||
ui.painter().rect_filled(bg_rect, 0.0, backdrop_color);
|
||||
let backdrop_response = backdrop.response();
|
||||
let backdrop_clicked = paint_backdrop(ui, backdrop_color);
|
||||
|
||||
let frame = frame.unwrap_or_else(|| Frame::popup(ui.style()));
|
||||
let inner = frame.show(ui, content).inner;
|
||||
|
||||
// We need the extra scope with the sense since frame can't have a sense and since we
|
||||
// need to prevent the clicks from passing through to the backdrop.
|
||||
let inner = ui
|
||||
.scope_builder(UiBuilder::new().sense(Sense::CLICK | Sense::DRAG), |ui| {
|
||||
frame.show(ui, content).inner
|
||||
})
|
||||
.inner;
|
||||
|
||||
(inner, backdrop_response)
|
||||
(inner, backdrop_clicked)
|
||||
});
|
||||
|
||||
ModalResponse {
|
||||
response,
|
||||
backdrop_response,
|
||||
backdrop_clicked,
|
||||
inner,
|
||||
is_top_modal,
|
||||
any_popup_open,
|
||||
@@ -125,11 +130,8 @@ pub struct ModalResponse<T> {
|
||||
/// The response of the modal contents
|
||||
pub response: Response,
|
||||
|
||||
/// The response of the modal backdrop.
|
||||
///
|
||||
/// A click on this means the user clicked outside the modal,
|
||||
/// in which case you might want to close the modal.
|
||||
pub backdrop_response: Response,
|
||||
/// Whether the backdrop was clicked (i.e. a click landed outside the modal).
|
||||
pub backdrop_clicked: bool,
|
||||
|
||||
/// The inner response from the content closure
|
||||
pub inner: T,
|
||||
@@ -157,7 +159,7 @@ impl<T> ModalResponse<T> {
|
||||
|
||||
let ui_close_called = self.response.should_close();
|
||||
|
||||
self.backdrop_response.clicked()
|
||||
self.backdrop_clicked
|
||||
|| ui_close_called
|
||||
|| (self.is_top_modal && !self.any_popup_open && escape_clicked())
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ use std::iter::once;
|
||||
use emath::{Align, Pos2, Rect, RectAlign, Vec2, vec2};
|
||||
|
||||
use crate::{
|
||||
Area, AreaState, Context, Frame, Id, InnerResponse, Key, LayerId, Layout, Order, Response,
|
||||
Sense, Ui, UiKind, UiStackInfo,
|
||||
Area, AreaState, Color32, Context, Frame, Id, InnerResponse, Key, LayerId, Layout, Order,
|
||||
Response, Sense, Ui, UiKind, UiStackInfo,
|
||||
containers::menu::{MenuConfig, MenuState, menu_style},
|
||||
style::StyleModifier,
|
||||
};
|
||||
@@ -185,6 +185,8 @@ pub struct Popup<'a> {
|
||||
layout: Layout,
|
||||
frame: Option<Frame>,
|
||||
style: StyleModifier,
|
||||
/// `None` = use style default, `Some(None)` = no backdrop, `Some(Some(color))` = this color
|
||||
backdrop_color: Option<Option<Color32>>,
|
||||
}
|
||||
|
||||
impl<'a> Popup<'a> {
|
||||
@@ -207,6 +209,7 @@ impl<'a> Popup<'a> {
|
||||
layout: Layout::default(),
|
||||
frame: None,
|
||||
style: StyleModifier::default(),
|
||||
backdrop_color: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,6 +413,22 @@ impl<'a> Popup<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Show a backdrop behind the popup.
|
||||
///
|
||||
/// The backdrop covers the entire screen, blocking interaction with the rest of the UI.
|
||||
///
|
||||
/// - `None` — no backdrop is shown.
|
||||
/// - `Some(color)` — show a backdrop with this color.
|
||||
/// - `Some(Color32::PLACEHOLDER)` — use the default from [`crate::Visuals::popup_backdrop_color`].
|
||||
///
|
||||
/// By default, this is controlled by [`crate::Visuals::popup_backdrop_color`].
|
||||
/// Calling this method overrides the global style for this popup.
|
||||
#[inline]
|
||||
pub fn backdrop(mut self, color: Option<Color32>) -> Self {
|
||||
self.backdrop_color = Some(color);
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the [`Context`]
|
||||
pub fn ctx(&self) -> &Context {
|
||||
&self.ctx
|
||||
@@ -553,6 +572,7 @@ impl<'a> Popup<'a> {
|
||||
layout,
|
||||
frame,
|
||||
style,
|
||||
backdrop_color,
|
||||
} = self;
|
||||
|
||||
if kind != PopupKind::Tooltip {
|
||||
@@ -588,7 +608,26 @@ impl<'a> Popup<'a> {
|
||||
area = area.default_width(width);
|
||||
}
|
||||
|
||||
// Resolve backdrop color: per-instance override, or fall back to global style
|
||||
let resolved_backdrop = match backdrop_color {
|
||||
Some(explicit) => explicit,
|
||||
None => ctx.global_style().visuals.popup_backdrop_color,
|
||||
};
|
||||
let resolved_backdrop = match resolved_backdrop {
|
||||
Some(color) if color == Color32::PLACEHOLDER => {
|
||||
ctx.global_style().visuals.popup_backdrop_color
|
||||
}
|
||||
other => other,
|
||||
};
|
||||
|
||||
let mut backdrop_clicked = false;
|
||||
let mut response = area.show(&ctx, |ui| {
|
||||
backdrop_clicked = if let Some(color) = resolved_backdrop {
|
||||
super::modal::paint_backdrop(ui, color) && was_open_last_frame
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
style.apply(ui.style_mut());
|
||||
let frame = frame.unwrap_or_else(|| Frame::popup(ui.style()));
|
||||
frame.show(ui, content).inner
|
||||
@@ -600,7 +639,7 @@ impl<'a> Popup<'a> {
|
||||
let closed_by_click = match close_behavior {
|
||||
PopupCloseBehavior::CloseOnClick => close_click,
|
||||
PopupCloseBehavior::CloseOnClickOutside => {
|
||||
close_click && response.response.clicked_elsewhere()
|
||||
backdrop_clicked || (close_click && response.response.clicked_elsewhere())
|
||||
}
|
||||
PopupCloseBehavior::IgnoreClicks => false,
|
||||
};
|
||||
|
||||
@@ -1024,6 +1024,20 @@ pub struct Visuals {
|
||||
|
||||
pub popup_shadow: Shadow,
|
||||
|
||||
/// The backdrop color for modals.
|
||||
///
|
||||
/// Default is `Color32::from_black_alpha(100)`.
|
||||
pub modal_backdrop_color: Color32,
|
||||
|
||||
/// The default backdrop color for popups.
|
||||
///
|
||||
/// If `Some`, popups show a backdrop by default.
|
||||
/// If `None` (default), popups don't show a backdrop unless explicitly enabled
|
||||
/// via [`crate::Popup::backdrop`].
|
||||
///
|
||||
/// Individual popups can still override this with [`crate::Popup::backdrop`].
|
||||
pub popup_backdrop_color: Option<Color32>,
|
||||
|
||||
pub resize_corner_size: f32,
|
||||
|
||||
/// How the text cursor acts.
|
||||
@@ -1460,6 +1474,9 @@ impl Visuals {
|
||||
color: Color32::from_black_alpha(96),
|
||||
},
|
||||
|
||||
modal_backdrop_color: Color32::from_black_alpha(100),
|
||||
popup_backdrop_color: None,
|
||||
|
||||
resize_corner_size: 12.0,
|
||||
|
||||
text_cursor: Default::default(),
|
||||
@@ -2152,6 +2169,8 @@ impl Visuals {
|
||||
panel_fill,
|
||||
|
||||
popup_shadow,
|
||||
modal_backdrop_color,
|
||||
popup_backdrop_color,
|
||||
|
||||
resize_corner_size,
|
||||
|
||||
@@ -2333,6 +2352,17 @@ impl Visuals {
|
||||
ui.label("Shadow");
|
||||
ui.add(popup_shadow);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Modal backdrop");
|
||||
ui.color_edit_button_srgba(modal_backdrop_color);
|
||||
ui.end_row();
|
||||
|
||||
ui_optional_color(
|
||||
ui,
|
||||
popup_backdrop_color,
|
||||
Color32::from_black_alpha(100),
|
||||
"Popup backdrop",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user