Add `Ui::close` and `Response::should_close` (#5729)
This adds a generic way of telling containers to close from their child `Ui`s. * Part of #5727 * [x] I have followed the instructions in the PR template --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
264749b8af
commit
f5b058b908
|
|
@ -556,7 +556,8 @@ impl Prepared {
|
||||||
.ui_stack_info(UiStackInfo::new(self.kind))
|
.ui_stack_info(UiStackInfo::new(self.kind))
|
||||||
.layer_id(self.layer_id)
|
.layer_id(self.layer_id)
|
||||||
.max_rect(max_rect)
|
.max_rect(max_rect)
|
||||||
.layout(self.layout);
|
.layout(self.layout)
|
||||||
|
.closable();
|
||||||
|
|
||||||
if !self.enabled {
|
if !self.enabled {
|
||||||
ui_builder = ui_builder.disabled();
|
ui_builder = ui_builder.disabled();
|
||||||
|
|
@ -611,6 +612,12 @@ impl Prepared {
|
||||||
response.rect = final_rect;
|
response.rect = final_rect;
|
||||||
response.interact_rect = final_rect;
|
response.interact_rect = final_rect;
|
||||||
|
|
||||||
|
// TODO(lucasmerlin): Can the area response be based on Ui::response? Then this won't be needed
|
||||||
|
// Bubble up the close event
|
||||||
|
if content_ui.should_close() {
|
||||||
|
response.set_close();
|
||||||
|
}
|
||||||
|
|
||||||
ctx.memory_mut(|m| m.areas_mut().set_state(layer_id, state));
|
ctx.memory_mut(|m| m.areas_mut().set_state(layer_id, state));
|
||||||
|
|
||||||
if sizing_pass {
|
if sizing_pass {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ClosableTag {
|
||||||
|
pub close: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClosableTag {
|
||||||
|
pub const NAME: &'static str = "egui_close_tag";
|
||||||
|
|
||||||
|
/// Set close to `true`
|
||||||
|
pub fn set_close(&self) {
|
||||||
|
self.close.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if [`ClosableTag::set_close`] has been called.
|
||||||
|
pub fn should_close(&self) -> bool {
|
||||||
|
self.close.load(std::sync::atomic::Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,8 @@ use std::hash::Hash;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
emath, epaint, pos2, remap, remap_clamp, vec2, Context, Id, InnerResponse, NumExt, Rect,
|
emath, epaint, pos2, remap, remap_clamp, vec2, Context, Id, InnerResponse, NumExt, Rect,
|
||||||
Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2, WidgetInfo, WidgetText, WidgetType,
|
Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, UiBuilder, UiKind, UiStackInfo, Vec2,
|
||||||
|
WidgetInfo, WidgetText, WidgetType,
|
||||||
};
|
};
|
||||||
use emath::GuiRounding as _;
|
use emath::GuiRounding as _;
|
||||||
use epaint::{Shape, StrokeKind};
|
use epaint::{Shape, StrokeKind};
|
||||||
|
|
@ -203,11 +204,16 @@ impl CollapsingState {
|
||||||
add_body: impl FnOnce(&mut Ui) -> R,
|
add_body: impl FnOnce(&mut Ui) -> R,
|
||||||
) -> Option<InnerResponse<R>> {
|
) -> Option<InnerResponse<R>> {
|
||||||
let openness = self.openness(ui.ctx());
|
let openness = self.openness(ui.ctx());
|
||||||
|
|
||||||
|
let builder = UiBuilder::new()
|
||||||
|
.ui_stack_info(UiStackInfo::new(UiKind::Collapsible))
|
||||||
|
.closable();
|
||||||
|
|
||||||
if openness <= 0.0 {
|
if openness <= 0.0 {
|
||||||
self.store(ui.ctx()); // we store any earlier toggling as promised in the docstring
|
self.store(ui.ctx()); // we store any earlier toggling as promised in the docstring
|
||||||
None
|
None
|
||||||
} else if openness < 1.0 {
|
} else if openness < 1.0 {
|
||||||
Some(ui.scope(|child_ui| {
|
Some(ui.scope_builder(builder, |child_ui| {
|
||||||
let max_height = if self.state.open && self.state.open_height.is_none() {
|
let max_height = if self.state.open && self.state.open_height.is_none() {
|
||||||
// First frame of expansion.
|
// First frame of expansion.
|
||||||
// We don't know full height yet, but we will next frame.
|
// We don't know full height yet, but we will next frame.
|
||||||
|
|
@ -226,6 +232,9 @@ impl CollapsingState {
|
||||||
|
|
||||||
let mut min_rect = child_ui.min_rect();
|
let mut min_rect = child_ui.min_rect();
|
||||||
self.state.open_height = Some(min_rect.height());
|
self.state.open_height = Some(min_rect.height());
|
||||||
|
if child_ui.should_close() {
|
||||||
|
self.state.open = false;
|
||||||
|
}
|
||||||
self.store(child_ui.ctx()); // remember the height
|
self.store(child_ui.ctx()); // remember the height
|
||||||
|
|
||||||
// Pretend children took up at most `max_height` space:
|
// Pretend children took up at most `max_height` space:
|
||||||
|
|
@ -234,7 +243,10 @@ impl CollapsingState {
|
||||||
ret
|
ret
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
let ret_response = ui.scope(add_body);
|
let ret_response = ui.scope_builder(builder, add_body);
|
||||||
|
if ret_response.response.should_close() {
|
||||||
|
self.state.open = false;
|
||||||
|
}
|
||||||
let full_size = ret_response.response.rect.size();
|
let full_size = ret_response.response.rect.size();
|
||||||
self.state.open_height = Some(full_size.y);
|
self.state.open_height = Some(full_size.y);
|
||||||
self.store(ui.ctx()); // remember the height
|
self.store(ui.ctx()); // remember the height
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
//! For instance, a [`Frame`] adds a frame and background to some contained UI.
|
//! For instance, a [`Frame`] adds a frame and background to some contained UI.
|
||||||
|
|
||||||
pub(crate) mod area;
|
pub(crate) mod area;
|
||||||
|
pub mod close_tag;
|
||||||
pub mod collapsing_header;
|
pub mod collapsing_header;
|
||||||
mod combo_box;
|
mod combo_box;
|
||||||
pub mod frame;
|
pub mod frame;
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,10 @@ impl<T> ModalResponse<T> {
|
||||||
let escape_clicked =
|
let escape_clicked =
|
||||||
|| ctx.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape));
|
|| ctx.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape));
|
||||||
|
|
||||||
|
let ui_close_called = self.response.should_close();
|
||||||
|
|
||||||
self.backdrop_response.clicked()
|
self.backdrop_response.clicked()
|
||||||
|
|| ui_close_called
|
||||||
|| (self.is_top_modal && !self.any_popup_open && escape_clicked())
|
|| (self.is_top_modal && !self.any_popup_open && escape_clicked())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -542,7 +542,9 @@ impl<'a> Popup<'a> {
|
||||||
PopupCloseBehavior::IgnoreClicks => false,
|
PopupCloseBehavior::IgnoreClicks => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
should_close || ctx.input(|i| i.key_pressed(Key::Escape))
|
should_close
|
||||||
|
|| ctx.input(|i| i.key_pressed(Key::Escape))
|
||||||
|
|| response.response.should_close()
|
||||||
};
|
};
|
||||||
|
|
||||||
match open_kind {
|
match open_kind {
|
||||||
|
|
|
||||||
|
|
@ -434,7 +434,7 @@ impl Window<'_> {
|
||||||
) -> Option<InnerResponse<Option<R>>> {
|
) -> Option<InnerResponse<Option<R>>> {
|
||||||
let Window {
|
let Window {
|
||||||
title,
|
title,
|
||||||
open,
|
mut open,
|
||||||
area,
|
area,
|
||||||
frame,
|
frame,
|
||||||
resize,
|
resize,
|
||||||
|
|
@ -634,7 +634,7 @@ impl Window<'_> {
|
||||||
title_bar.ui(
|
title_bar.ui(
|
||||||
&mut area_content_ui,
|
&mut area_content_ui,
|
||||||
&content_response,
|
&content_response,
|
||||||
open,
|
open.as_deref_mut(),
|
||||||
&mut collapsing,
|
&mut collapsing,
|
||||||
collapsible,
|
collapsible,
|
||||||
);
|
);
|
||||||
|
|
@ -650,6 +650,12 @@ impl Window<'_> {
|
||||||
|
|
||||||
let full_response = area.end(ctx, area_content_ui);
|
let full_response = area.end(ctx, area_content_ui);
|
||||||
|
|
||||||
|
if full_response.should_close() {
|
||||||
|
if let Some(open) = open {
|
||||||
|
*open = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let inner_response = InnerResponse {
|
let inner_response = InnerResponse {
|
||||||
inner: content_inner,
|
inner: content_inner,
|
||||||
response: full_response,
|
response: full_response,
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ pub fn zoom_menu_buttons(ui: &mut Ui) {
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
zoom_in(ui.ctx());
|
zoom_in(ui.ctx());
|
||||||
ui.close_menu();
|
ui.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui
|
if ui
|
||||||
|
|
@ -99,7 +99,7 @@ pub fn zoom_menu_buttons(ui: &mut Ui) {
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
zoom_out(ui.ctx());
|
zoom_out(ui.ctx());
|
||||||
ui.close_menu();
|
ui.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui
|
if ui
|
||||||
|
|
@ -110,6 +110,6 @@ pub fn zoom_menu_buttons(ui: &mut Ui) {
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
ui.ctx().set_zoom_factor(1.0);
|
ui.ctx().set_zoom_factor(1.0);
|
||||||
ui.close_menu();
|
ui.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -364,7 +364,10 @@ impl MenuRoot {
|
||||||
let menu_state = self.menu_state.read();
|
let menu_state = self.menu_state.read();
|
||||||
|
|
||||||
let escape_pressed = button.ctx.input(|i| i.key_pressed(Key::Escape));
|
let escape_pressed = button.ctx.input(|i| i.key_pressed(Key::Escape));
|
||||||
if menu_state.response.is_close() || escape_pressed {
|
if menu_state.response.is_close()
|
||||||
|
|| escape_pressed
|
||||||
|
|| inner_response.response.should_close()
|
||||||
|
{
|
||||||
return (MenuResponse::Close, Some(inner_response));
|
return (MenuResponse::Close, Some(inner_response));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -667,6 +670,9 @@ impl MenuState {
|
||||||
) -> Option<R> {
|
) -> Option<R> {
|
||||||
let (sub_response, response) = self.submenu(id).map(|sub| {
|
let (sub_response, response) = self.submenu(id).map(|sub| {
|
||||||
let inner_response = menu_popup(ctx, parent_layer, sub, id, add_contents);
|
let inner_response = menu_popup(ctx, parent_layer, sub, id, add_contents);
|
||||||
|
if inner_response.response.should_close() {
|
||||||
|
sub.write().close();
|
||||||
|
}
|
||||||
(sub.read().response, inner_response.inner)
|
(sub.read().response, inner_response.inner)
|
||||||
})?;
|
})?;
|
||||||
self.cascade_close_response(sub_response);
|
self.cascade_close_response(sub_response);
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,9 @@ bitflags::bitflags! {
|
||||||
/// Note that this can be `true` even if the user did not interact with the widget,
|
/// Note that this can be `true` even if the user did not interact with the widget,
|
||||||
/// for instance if an existing slider value was clamped to the given range.
|
/// for instance if an existing slider value was clamped to the given range.
|
||||||
const CHANGED = 1<<11;
|
const CHANGED = 1<<11;
|
||||||
|
|
||||||
|
/// Should this container be closed?
|
||||||
|
const CLOSE = 1<<12;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -528,6 +531,21 @@ impl Response {
|
||||||
self.flags.set(Flags::CHANGED, true);
|
self.flags.set(Flags::CHANGED, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Should the container be closed?
|
||||||
|
///
|
||||||
|
/// Will e.g. be set by calling [`Ui::close`] in a child [`Ui`] or by calling
|
||||||
|
/// [`Self::set_close`].
|
||||||
|
pub fn should_close(&self) -> bool {
|
||||||
|
self.flags.contains(Flags::CLOSE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the [`Flags::CLOSE`] flag.
|
||||||
|
///
|
||||||
|
/// Can be used to e.g. signal that a container should be closed.
|
||||||
|
pub fn set_close(&mut self) {
|
||||||
|
self.flags.set(Flags::CLOSE, true);
|
||||||
|
}
|
||||||
|
|
||||||
/// Show this UI if the widget was hovered (i.e. a tooltip).
|
/// Show this UI if the widget was hovered (i.e. a tooltip).
|
||||||
///
|
///
|
||||||
/// The text will not be visible if the widget is not enabled.
|
/// The text will not be visible if the widget is not enabled.
|
||||||
|
|
@ -909,13 +927,13 @@ impl Response {
|
||||||
/// let response = ui.add(Label::new("Right-click me!").sense(Sense::click()));
|
/// let response = ui.add(Label::new("Right-click me!").sense(Sense::click()));
|
||||||
/// response.context_menu(|ui| {
|
/// response.context_menu(|ui| {
|
||||||
/// if ui.button("Close the menu").clicked() {
|
/// if ui.button("Close the menu").clicked() {
|
||||||
/// ui.close_menu();
|
/// ui.close();
|
||||||
/// }
|
/// }
|
||||||
/// });
|
/// });
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// See also: [`Ui::menu_button`] and [`Ui::close_menu`].
|
/// See also: [`Ui::menu_button`] and [`Ui::close`].
|
||||||
pub fn context_menu(&self, add_contents: impl FnOnce(&mut Ui)) -> Option<InnerResponse<()>> {
|
pub fn context_menu(&self, add_contents: impl FnOnce(&mut Ui)) -> Option<InnerResponse<()>> {
|
||||||
menu::context_menu(self, add_contents)
|
menu::context_menu(self, add_contents)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
#![warn(missing_docs)] // Let's keep `Ui` well-documented.
|
#![warn(missing_docs)] // Let's keep `Ui` well-documented.
|
||||||
#![allow(clippy::use_self)]
|
#![allow(clippy::use_self)]
|
||||||
|
|
||||||
use std::{any::Any, hash::Hash, sync::Arc};
|
|
||||||
|
|
||||||
use emath::GuiRounding as _;
|
use emath::GuiRounding as _;
|
||||||
use epaint::mutex::RwLock;
|
use epaint::mutex::RwLock;
|
||||||
|
use std::{any::Any, hash::Hash, sync::Arc};
|
||||||
|
|
||||||
|
use crate::close_tag::ClosableTag;
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
use crate::Stroke;
|
||||||
use crate::{
|
use crate::{
|
||||||
containers::{CollapsingHeader, CollapsingResponse, Frame},
|
containers::{CollapsingHeader, CollapsingResponse, Frame},
|
||||||
ecolor::Hsva,
|
ecolor::Hsva,
|
||||||
|
|
@ -26,11 +28,9 @@ use crate::{
|
||||||
},
|
},
|
||||||
Align, Color32, Context, CursorIcon, DragAndDrop, Id, InnerResponse, InputState, LayerId,
|
Align, Color32, Context, CursorIcon, DragAndDrop, Id, InnerResponse, InputState, LayerId,
|
||||||
Memory, Order, Painter, PlatformOutput, Pos2, Rangef, Rect, Response, Rgba, RichText, Sense,
|
Memory, Order, Painter, PlatformOutput, Pos2, Rangef, Rect, Response, Rgba, RichText, Sense,
|
||||||
Style, TextStyle, TextWrapMode, UiBuilder, UiStack, UiStackInfo, Vec2, WidgetRect, WidgetText,
|
Style, TextStyle, TextWrapMode, UiBuilder, UiKind, UiStack, UiStackInfo, Vec2, WidgetRect,
|
||||||
|
WidgetText,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
use crate::Stroke;
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// This is what you use to place widgets.
|
/// This is what you use to place widgets.
|
||||||
|
|
@ -1095,7 +1095,8 @@ impl Ui {
|
||||||
// This is the inverse of Context::read_response. We prefer a response
|
// This is the inverse of Context::read_response. We prefer a response
|
||||||
// based on last frame's widget rect since the one from this frame is Rect::NOTHING until
|
// based on last frame's widget rect since the one from this frame is Rect::NOTHING until
|
||||||
// Ui::interact_bg is called or the Ui is dropped.
|
// Ui::interact_bg is called or the Ui is dropped.
|
||||||
self.ctx()
|
let mut response = self
|
||||||
|
.ctx()
|
||||||
.viewport(|viewport| {
|
.viewport(|viewport| {
|
||||||
viewport
|
viewport
|
||||||
.prev_pass
|
.prev_pass
|
||||||
|
|
@ -1107,7 +1108,11 @@ impl Ui {
|
||||||
.map(|widget_rect| self.ctx().get_response(widget_rect))
|
.map(|widget_rect| self.ctx().get_response(widget_rect))
|
||||||
.expect(
|
.expect(
|
||||||
"Since we always call Context::create_widget in Ui::new, this should never be None",
|
"Since we always call Context::create_widget in Ui::new, this should never be None",
|
||||||
)
|
);
|
||||||
|
if self.should_close() {
|
||||||
|
response.set_close();
|
||||||
|
}
|
||||||
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the [`WidgetRect`] created in [`Ui::new`] or [`Ui::new_child`] with the current
|
/// Update the [`WidgetRect`] created in [`Ui::new`] or [`Ui::new_child`] with the current
|
||||||
|
|
@ -1121,7 +1126,7 @@ impl Ui {
|
||||||
fs.used_ids.remove(&self.unique_id);
|
fs.used_ids.remove(&self.unique_id);
|
||||||
});
|
});
|
||||||
// This will update the WidgetRect that was first created in `Ui::new`.
|
// This will update the WidgetRect that was first created in `Ui::new`.
|
||||||
self.ctx().create_widget(
|
let mut response = self.ctx().create_widget(
|
||||||
WidgetRect {
|
WidgetRect {
|
||||||
id: self.unique_id,
|
id: self.unique_id,
|
||||||
layer_id: self.layer_id(),
|
layer_id: self.layer_id(),
|
||||||
|
|
@ -1131,7 +1136,11 @@ impl Ui {
|
||||||
enabled: self.enabled,
|
enabled: self.enabled,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
)
|
);
|
||||||
|
if self.should_close() {
|
||||||
|
response.set_close();
|
||||||
|
}
|
||||||
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interact with the background of this [`Ui`],
|
/// Interact with the background of this [`Ui`],
|
||||||
|
|
@ -1165,6 +1174,69 @@ impl Ui {
|
||||||
pub fn ui_contains_pointer(&self) -> bool {
|
pub fn ui_contains_pointer(&self) -> bool {
|
||||||
self.rect_contains_pointer(self.min_rect())
|
self.rect_contains_pointer(self.min_rect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find and close the first closable parent.
|
||||||
|
///
|
||||||
|
/// Use [`UiBuilder::closable`] to make a [`Ui`] closable.
|
||||||
|
/// You can then use [`Ui::should_close`] to check if it should be closed.
|
||||||
|
///
|
||||||
|
/// This is implemented for all egui containers, e.g. [`crate::Popup`], [`crate::Modal`],
|
||||||
|
/// [`crate::Area`], [`crate::Window`], [`crate::CollapsingHeader`], etc.
|
||||||
|
///
|
||||||
|
/// What exactly happens when you close a container depends on the container implementation.
|
||||||
|
/// [`crate::Area`] e.g. will return true from it's [`Response::should_close`] method.
|
||||||
|
///
|
||||||
|
/// If you want to close a specific kind of container, use [`Ui::close_kind`] instead.
|
||||||
|
pub fn close(&self) {
|
||||||
|
let tag = self.stack.iter().find_map(|stack| {
|
||||||
|
stack
|
||||||
|
.info
|
||||||
|
.tags
|
||||||
|
.get_downcast::<ClosableTag>(ClosableTag::NAME)
|
||||||
|
});
|
||||||
|
if let Some(tag) = tag {
|
||||||
|
tag.set_close();
|
||||||
|
} else {
|
||||||
|
#[cfg(feature = "log")]
|
||||||
|
log::warn!("Called ui.close() on a Ui that has no closable parent.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find and close the first closable parent of a specific [`UiKind`].
|
||||||
|
///
|
||||||
|
/// This is useful if you want to e.g. close a [`crate::Window`]. Since it contains a
|
||||||
|
/// `Collapsible`, [`Ui::close`] would close the `Collapsible` instead.
|
||||||
|
/// You can close the [`crate::Window`] by calling `ui.close_kind(UiKind::Window)`.
|
||||||
|
pub fn close_kind(&self, ui_kind: UiKind) {
|
||||||
|
let tag = self
|
||||||
|
.stack
|
||||||
|
.iter()
|
||||||
|
.filter(|stack| stack.info.kind == Some(ui_kind))
|
||||||
|
.find_map(|stack| {
|
||||||
|
stack
|
||||||
|
.info
|
||||||
|
.tags
|
||||||
|
.get_downcast::<ClosableTag>(ClosableTag::NAME)
|
||||||
|
});
|
||||||
|
if let Some(tag) = tag {
|
||||||
|
tag.set_close();
|
||||||
|
} else {
|
||||||
|
#[cfg(feature = "log")]
|
||||||
|
log::warn!("Called ui.close_kind({ui_kind:?}) on ui with no such closable parent.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Was [`Ui::close`] called on this [`Ui`] or any of its children?
|
||||||
|
/// Only works if the [`Ui`] was created with [`UiBuilder::closable`].
|
||||||
|
///
|
||||||
|
/// You can also check via this [`Ui`]'s [`Response::should_close`].
|
||||||
|
pub fn should_close(&self) -> bool {
|
||||||
|
self.stack
|
||||||
|
.info
|
||||||
|
.tags
|
||||||
|
.get_downcast(ClosableTag::NAME)
|
||||||
|
.is_some_and(|tag: &ClosableTag| tag.should_close())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Allocating space: where do I put my widgets?
|
/// # Allocating space: where do I put my widgets?
|
||||||
|
|
@ -2900,11 +2972,9 @@ impl Ui {
|
||||||
/// Close the menu we are in (including submenus), if any.
|
/// Close the menu we are in (including submenus), if any.
|
||||||
///
|
///
|
||||||
/// See also: [`Self::menu_button`] and [`Response::context_menu`].
|
/// See also: [`Self::menu_button`] and [`Response::context_menu`].
|
||||||
pub fn close_menu(&mut self) {
|
#[deprecated = "Use `ui.close()` or `ui.close_kind(UiKind::Menu)` instead"]
|
||||||
if let Some(menu_state) = &mut self.menu_state {
|
pub fn close_menu(&self) {
|
||||||
menu_state.write().close();
|
self.close_kind(UiKind::Menu);
|
||||||
}
|
|
||||||
self.menu_state = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_menu_state(&mut self, menu_state: Option<Arc<RwLock<MenuState>>>) {
|
pub(crate) fn set_menu_state(&mut self, menu_state: Option<Arc<RwLock<MenuState>>>) {
|
||||||
|
|
@ -2921,14 +2991,14 @@ impl Ui {
|
||||||
/// ui.menu_button("My menu", |ui| {
|
/// ui.menu_button("My menu", |ui| {
|
||||||
/// ui.menu_button("My sub-menu", |ui| {
|
/// ui.menu_button("My sub-menu", |ui| {
|
||||||
/// if ui.button("Close the menu").clicked() {
|
/// if ui.button("Close the menu").clicked() {
|
||||||
/// ui.close_menu();
|
/// ui.close();
|
||||||
/// }
|
/// }
|
||||||
/// });
|
/// });
|
||||||
/// });
|
/// });
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// See also: [`Self::close_menu`] and [`Response::context_menu`].
|
/// See also: [`Self::close`] and [`Response::context_menu`].
|
||||||
pub fn menu_button<R>(
|
pub fn menu_button<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
title: impl Into<WidgetText>,
|
title: impl Into<WidgetText>,
|
||||||
|
|
@ -2952,7 +3022,7 @@ impl Ui {
|
||||||
/// ui.menu_image_button(title, img, |ui| {
|
/// ui.menu_image_button(title, img, |ui| {
|
||||||
/// ui.menu_button("My sub-menu", |ui| {
|
/// ui.menu_button("My sub-menu", |ui| {
|
||||||
/// if ui.button("Close the menu").clicked() {
|
/// if ui.button("Close the menu").clicked() {
|
||||||
/// ui.close_menu();
|
/// ui.close();
|
||||||
/// }
|
/// }
|
||||||
/// });
|
/// });
|
||||||
/// });
|
/// });
|
||||||
|
|
@ -2960,7 +3030,7 @@ impl Ui {
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
/// See also: [`Self::close_menu`] and [`Response::context_menu`].
|
/// See also: [`Self::close`] and [`Response::context_menu`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn menu_image_button<'a, R>(
|
pub fn menu_image_button<'a, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -2986,14 +3056,14 @@ impl Ui {
|
||||||
/// ui.menu_image_text_button(img, title, |ui| {
|
/// ui.menu_image_text_button(img, title, |ui| {
|
||||||
/// ui.menu_button("My sub-menu", |ui| {
|
/// ui.menu_button("My sub-menu", |ui| {
|
||||||
/// if ui.button("Close the menu").clicked() {
|
/// if ui.button("Close the menu").clicked() {
|
||||||
/// ui.close_menu();
|
/// ui.close();
|
||||||
/// }
|
/// }
|
||||||
/// });
|
/// });
|
||||||
/// });
|
/// });
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// See also: [`Self::close_menu`] and [`Response::context_menu`].
|
/// See also: [`Self::close`] and [`Response::context_menu`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn menu_image_text_button<'a, R>(
|
pub fn menu_image_text_button<'a, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use std::{hash::Hash, sync::Arc};
|
use std::{hash::Hash, sync::Arc};
|
||||||
|
|
||||||
use crate::{Id, LayerId, Layout, Rect, Sense, Style, UiStackInfo};
|
use crate::close_tag::ClosableTag;
|
||||||
|
|
||||||
#[allow(unused_imports)] // Used for doclinks
|
#[allow(unused_imports)] // Used for doclinks
|
||||||
use crate::Ui;
|
use crate::Ui;
|
||||||
|
use crate::{Id, LayerId, Layout, Rect, Sense, Style, UiStackInfo};
|
||||||
|
|
||||||
/// Build a [`Ui`] as the child of another [`Ui`].
|
/// Build a [`Ui`] as the child of another [`Ui`].
|
||||||
///
|
///
|
||||||
|
|
@ -125,6 +125,7 @@ impl UiBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set if you want sense clicks and/or drags. Default is [`Sense::hover`].
|
/// Set if you want sense clicks and/or drags. Default is [`Sense::hover`].
|
||||||
|
///
|
||||||
/// The sense will be registered below the Senses of any widgets contained in this [`Ui`], so
|
/// The sense will be registered below the Senses of any widgets contained in this [`Ui`], so
|
||||||
/// if the user clicks a button contained within this [`Ui`], that button will receive the click
|
/// if the user clicks a button contained within this [`Ui`], that button will receive the click
|
||||||
/// instead.
|
/// instead.
|
||||||
|
|
@ -135,4 +136,19 @@ impl UiBuilder {
|
||||||
self.sense = Some(sense);
|
self.sense = Some(sense);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Make this [`Ui`] closable.
|
||||||
|
///
|
||||||
|
/// Calling [`Ui::close`] in a child [`Ui`] will mark this [`Ui`] for closing.
|
||||||
|
/// After [`Ui::close`] was called, [`Ui::should_close`] and [`crate::Response::should_close`] will
|
||||||
|
/// return `true` (for this frame).
|
||||||
|
///
|
||||||
|
/// This works by adding a [`ClosableTag`] to the [`UiStackInfo`].
|
||||||
|
#[inline]
|
||||||
|
pub fn closable(mut self) -> Self {
|
||||||
|
self.ui_stack_info
|
||||||
|
.tags
|
||||||
|
.insert(ClosableTag::NAME, Some(Arc::new(ClosableTag::default())));
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,9 @@ pub enum UiKind {
|
||||||
|
|
||||||
/// An [`crate::Area`] that is not of any other kind.
|
/// An [`crate::Area`] that is not of any other kind.
|
||||||
GenericArea,
|
GenericArea,
|
||||||
|
|
||||||
|
/// A collapsible container, e.g. a [`crate::CollapsingHeader`].
|
||||||
|
Collapsible,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiKind {
|
impl UiKind {
|
||||||
|
|
@ -81,6 +84,7 @@ impl UiKind {
|
||||||
| Self::Frame
|
| Self::Frame
|
||||||
| Self::ScrollArea
|
| Self::ScrollArea
|
||||||
| Self::Resize
|
| Self::Resize
|
||||||
|
| Self::Collapsible
|
||||||
| Self::TableCell => false,
|
| Self::TableCell => false,
|
||||||
|
|
||||||
Self::Window
|
Self::Window
|
||||||
|
|
|
||||||
|
|
@ -333,7 +333,7 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||||
.send_viewport_cmd(egui::ViewportCommand::InnerSize(size));
|
.send_viewport_cmd(egui::ViewportCommand::InnerSize(size));
|
||||||
ui.ctx()
|
ui.ctx()
|
||||||
.send_viewport_cmd(egui::ViewportCommand::Fullscreen(false));
|
.send_viewport_cmd(egui::ViewportCommand::Fullscreen(false));
|
||||||
ui.close_menu();
|
ui.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -384,12 +384,12 @@ impl WrapApp {
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
ui.ctx().memory_mut(|mem| *mem = Default::default());
|
ui.ctx().memory_mut(|mem| *mem = Default::default());
|
||||||
ui.close_menu();
|
ui.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui.button("Reset everything").clicked() {
|
if ui.button("Reset everything").clicked() {
|
||||||
*cmd = Command::ResetEverything;
|
*cmd = Command::ResetEverything;
|
||||||
ui.close_menu();
|
ui.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,25 +59,25 @@ impl ContextMenus {
|
||||||
ui.set_max_width(200.0); // To make sure we wrap long text
|
ui.set_max_width(200.0); // To make sure we wrap long text
|
||||||
|
|
||||||
if ui.button("Open…").clicked() {
|
if ui.button("Open…").clicked() {
|
||||||
ui.close_menu();
|
ui.close();
|
||||||
}
|
}
|
||||||
ui.menu_button("SubMenu", |ui| {
|
ui.menu_button("SubMenu", |ui| {
|
||||||
ui.menu_button("SubMenu", |ui| {
|
ui.menu_button("SubMenu", |ui| {
|
||||||
if ui.button("Open…").clicked() {
|
if ui.button("Open…").clicked() {
|
||||||
ui.close_menu();
|
ui.close();
|
||||||
}
|
}
|
||||||
let _ = ui.button("Item");
|
let _ = ui.button("Item");
|
||||||
ui.menu_button("Recursive", Self::nested_menus)
|
ui.menu_button("Recursive", Self::nested_menus)
|
||||||
});
|
});
|
||||||
ui.menu_button("SubMenu", |ui| {
|
ui.menu_button("SubMenu", |ui| {
|
||||||
if ui.button("Open…").clicked() {
|
if ui.button("Open…").clicked() {
|
||||||
ui.close_menu();
|
ui.close();
|
||||||
}
|
}
|
||||||
let _ = ui.button("Item");
|
let _ = ui.button("Item");
|
||||||
});
|
});
|
||||||
let _ = ui.button("Item");
|
let _ = ui.button("Item");
|
||||||
if ui.button("Open…").clicked() {
|
if ui.button("Open…").clicked() {
|
||||||
ui.close_menu();
|
ui.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.menu_button("SubMenu", |ui| {
|
ui.menu_button("SubMenu", |ui| {
|
||||||
|
|
@ -86,7 +86,7 @@ impl ContextMenus {
|
||||||
let _ = ui.button("Item3");
|
let _ = ui.button("Item3");
|
||||||
let _ = ui.button("Item4");
|
let _ = ui.button("Item4");
|
||||||
if ui.button("Open…").clicked() {
|
if ui.button("Open…").clicked() {
|
||||||
ui.close_menu();
|
ui.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let _ = ui.button("Very long text for this item that should be wrapped");
|
let _ = ui.button("Very long text for this item that should be wrapped");
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,7 @@ impl DemoWindows {
|
||||||
ui.set_style(ui.ctx().style()); // ignore the "menu" style set by `menu_button`.
|
ui.set_style(ui.ctx().style()); // ignore the "menu" style set by `menu_button`.
|
||||||
self.demo_list_ui(ui);
|
self.demo_list_ui(ui);
|
||||||
if ui.ui_contains_pointer() && ui.input(|i| i.pointer.any_click()) {
|
if ui.ui_contains_pointer() && ui.input(|i| i.pointer.any_click()) {
|
||||||
ui.close_menu();
|
ui.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -345,7 +345,7 @@ fn file_menu_button(ui: &mut Ui) {
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
ui.ctx().memory_mut(|mem| mem.reset_areas());
|
ui.ctx().memory_mut(|mem| mem.reset_areas());
|
||||||
ui.close_menu();
|
ui.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui
|
if ui
|
||||||
|
|
@ -357,7 +357,7 @@ fn file_menu_button(ui: &mut Ui) {
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
ui.ctx().memory_mut(|mem| *mem = Default::default());
|
ui.ctx().memory_mut(|mem| *mem = Default::default());
|
||||||
ui.close_menu();
|
ui.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,9 @@ impl crate::View for Modals {
|
||||||
*save_modal_open = true;
|
*save_modal_open = true;
|
||||||
}
|
}
|
||||||
if ui.button("Cancel").clicked() {
|
if ui.button("Cancel").clicked() {
|
||||||
*user_modal_open = false;
|
// You can call `ui.close()` to close the modal.
|
||||||
|
// (This causes the current modals `should_close` to return true)
|
||||||
|
ui.close();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -123,7 +125,7 @@ impl crate::View for Modals {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui.button("No Thanks").clicked() {
|
if ui.button("No Thanks").clicked() {
|
||||||
*save_modal_open = false;
|
ui.close();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,10 @@ impl crate::View for PopupsDemo {
|
||||||
.show(|ui| {
|
.show(|ui| {
|
||||||
_ = ui.button("Menu item 1");
|
_ = ui.button("Menu item 1");
|
||||||
_ = ui.button("Menu item 2");
|
_ = ui.button("Menu item 2");
|
||||||
|
|
||||||
|
if ui.button("I always close the menu").clicked() {
|
||||||
|
ui.close();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
self.apply_options(Popup::context_menu(&response).id(Id::new("context_menu")))
|
self.apply_options(Popup::context_menu(&response).id(Id::new("context_menu")))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use egui::Vec2b;
|
use egui::{UiKind, Vec2b};
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
|
@ -149,6 +149,16 @@ impl crate::View for WindowOptions {
|
||||||
self.disabled_time = ui.input(|i| i.time);
|
self.disabled_time = ui.input(|i| i.time);
|
||||||
}
|
}
|
||||||
egui::reset_button(ui, self, "Reset");
|
egui::reset_button(ui, self, "Reset");
|
||||||
|
if ui
|
||||||
|
.button("Close")
|
||||||
|
.on_hover_text("You can collapse / close Windows via Ui::close")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
// Calling close would close the collapsible within the window
|
||||||
|
// ui.close();
|
||||||
|
// Instead, we close the window itself
|
||||||
|
ui.close_kind(UiKind::Window);
|
||||||
|
}
|
||||||
ui.add(crate::egui_github_link_file!());
|
ui.add(crate::egui_github_link_file!());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3411a4a8939b7e731c9c1a6331921b0ac905f4e3e86a51af70bdb38d9446f5e1
|
oid sha256:e67b1e676ff994cb9557939db3dca5ddd15c69d167afd96c0957a2a3b75c0fd8
|
||||||
size 35193
|
size 36007
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue