Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion crates/egui/src/containers/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,8 @@ impl Prepared {
.ui_stack_info(UiStackInfo::new(self.kind))
.layer_id(self.layer_id)
.max_rect(max_rect)
.layout(self.layout);
.layout(self.layout)
.closable();

if !self.enabled {
ui_builder = ui_builder.disabled();
Expand Down Expand Up @@ -611,6 +612,12 @@ impl Prepared {
response.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));

if sizing_pass {
Expand Down
20 changes: 20 additions & 0 deletions crates/egui/src/containers/close_tag.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}
18 changes: 15 additions & 3 deletions crates/egui/src/containers/collapsing_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use std::hash::Hash;

use crate::{
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 epaint::{Shape, StrokeKind};
Expand Down Expand Up @@ -203,11 +204,16 @@ impl CollapsingState {
add_body: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<R>> {
let openness = self.openness(ui.ctx());

let builder = UiBuilder::new()
.ui_stack_info(UiStackInfo::new(UiKind::Collapsible))
.closable();

if openness <= 0.0 {
self.store(ui.ctx()); // we store any earlier toggling as promised in the docstring
None
} 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() {
// First frame of expansion.
// We don't know full height yet, but we will next frame.
Expand All @@ -226,6 +232,9 @@ impl CollapsingState {

let mut min_rect = child_ui.min_rect();
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

// Pretend children took up at most `max_height` space:
Expand All @@ -234,7 +243,10 @@ impl CollapsingState {
ret
}))
} 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();
self.state.open_height = Some(full_size.y);
self.store(ui.ctx()); // remember the height
Expand Down
1 change: 1 addition & 0 deletions crates/egui/src/containers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! For instance, a [`Frame`] adds a frame and background to some contained UI.

pub(crate) mod area;
pub mod close_tag;
pub mod collapsing_header;
mod combo_box;
pub mod frame;
Expand Down
3 changes: 3 additions & 0 deletions crates/egui/src/containers/modal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,10 @@ impl<T> ModalResponse<T> {
let escape_clicked =
|| 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()
|| ui_close_called
|| (self.is_top_modal && !self.any_popup_open && escape_clicked())
}
}
4 changes: 3 additions & 1 deletion crates/egui/src/containers/popup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,9 @@ impl<'a> Popup<'a> {
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 {
Expand Down
10 changes: 8 additions & 2 deletions crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ impl Window<'_> {
) -> Option<InnerResponse<Option<R>>> {
let Window {
title,
open,
mut open,
area,
frame,
resize,
Expand Down Expand Up @@ -634,7 +634,7 @@ impl Window<'_> {
title_bar.ui(
&mut area_content_ui,
&content_response,
open,
open.as_deref_mut(),
&mut collapsing,
collapsible,
);
Expand All @@ -650,6 +650,12 @@ impl Window<'_> {

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 {
inner: content_inner,
response: full_response,
Expand Down
6 changes: 3 additions & 3 deletions crates/egui/src/gui_zoom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ pub fn zoom_menu_buttons(ui: &mut Ui) {
.clicked()
{
zoom_in(ui.ctx());
ui.close_menu();
ui.close();
}

if ui
Expand All @@ -99,7 +99,7 @@ pub fn zoom_menu_buttons(ui: &mut Ui) {
.clicked()
{
zoom_out(ui.ctx());
ui.close_menu();
ui.close();
}

if ui
Expand All @@ -110,6 +110,6 @@ pub fn zoom_menu_buttons(ui: &mut Ui) {
.clicked()
{
ui.ctx().set_zoom_factor(1.0);
ui.close_menu();
ui.close();
}
}
8 changes: 7 additions & 1 deletion crates/egui/src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,10 @@ impl MenuRoot {
let menu_state = self.menu_state.read();

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));
}
}
Expand Down Expand Up @@ -667,6 +670,9 @@ impl MenuState {
) -> Option<R> {
let (sub_response, response) = self.submenu(id).map(|sub| {
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)
})?;
self.cascade_close_response(sub_response);
Expand Down
22 changes: 20 additions & 2 deletions crates/egui/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ bitflags::bitflags! {
/// 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.
const CHANGED = 1<<11;

/// Should this container be closed?
const CLOSE = 1<<12;
}
}

Expand Down Expand Up @@ -528,6 +531,21 @@ impl Response {
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).
///
/// The text will not be visible if the widget is not enabled.
Expand Down Expand Up @@ -909,13 +927,13 @@ impl Response {
/// let response = ui.add(Label::new("Right-click me!").sense(Sense::click()));
/// response.context_menu(|ui| {
/// 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<()>> {
menu::context_menu(self, add_contents)
}
Expand Down
Loading