Skip to content

Commit

Permalink
Api redesign with events for displaying.
Browse files Browse the repository at this point in the history
Fixes: #358

You can open / close windows programmatically to events. See demo about.

The good thing about the new API, is that it won't lock the
borrowchecker (unlike open). It's maybe a good idea to considering open
as deprecated.
  • Loading branch information
Norlock committed Nov 1, 2023
1 parent fd75adb commit b2d617a
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 42 deletions.
44 changes: 28 additions & 16 deletions crates/egui/src/containers/collapsing_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use epaint::Shape;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub(crate) struct InnerState {
open: bool,
open: bool, // Expand / collapse
hidden: bool, // Show / Hide

/// Height of the region when open. Used for animations
#[cfg_attr(feature = "serde", serde(default))]
Expand All @@ -25,13 +26,6 @@ pub struct CollapsingState {
}

impl CollapsingState {
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
ctx.data_mut(|d| {
d.get_persisted::<InnerState>(id)
.map(|state| Self { id, state })
})
}

pub fn store(&self, ctx: &Context) {
ctx.data_mut(|d| d.insert_persisted(self.id, self.state));
}
Expand All @@ -44,11 +38,16 @@ impl CollapsingState {
self.id
}

pub fn load_with_default_open(ctx: &Context, id: Id, default_open: bool) -> Self {
Self::load(ctx, id).unwrap_or(CollapsingState {
pub fn load(ctx: &Context, id: Id, default_open: bool) -> Self {
ctx.data_mut(|d| {
d.get_persisted::<InnerState>(id)
.map(|state| Self { id, state })
})
.unwrap_or(CollapsingState {
id,
state: InnerState {
open: default_open,
hidden: false,
open_height: None,
},
})
Expand All @@ -62,11 +61,23 @@ impl CollapsingState {
self.state.open = open;
}

pub fn toggle(&mut self, ui: &Ui) {
pub fn toggle_open(&mut self, ui: &Ui) {
self.state.open = !self.state.open;
ui.ctx().request_repaint();
}

pub fn is_hidden(&self) -> bool {
self.state.hidden
}

pub fn toggle_hidden(&mut self) {
self.state.hidden = !self.state.hidden;
}

pub fn set_hidden(&mut self, hidden: bool) {
self.state.hidden = hidden;
}

/// 0 for closed, 1 for open, with tweening
pub fn openness(&self, ctx: &Context) -> f32 {
if ctx.memory(|mem| mem.everything_is_visible()) {
Expand All @@ -85,7 +96,7 @@ impl CollapsingState {
let (_id, rect) = ui.allocate_space(button_size);
let response = ui.interact(rect, self.id, Sense::click());
if response.clicked() {
self.toggle(ui);
self.toggle_open(ui);
}
let openness = self.openness(ui.ctx());
paint_default_icon(ui, openness, &response);
Expand All @@ -107,7 +118,7 @@ impl CollapsingState {
let (_id, rect) = ui.allocate_space(size);
let response = ui.interact(rect, self.id, Sense::click());
if response.clicked() {
self.toggle(ui);
self.toggle_open(ui);
}

let (mut icon_rect, _) = ui.spacing().icon_rectangles(response.rect);
Expand Down Expand Up @@ -535,14 +546,15 @@ impl CollapsingHeader {
header_response.rect.center().y - text.size().y / 2.0,
);

let mut state = CollapsingState::load_with_default_open(ui.ctx(), id, default_open);
let mut state = CollapsingState::load(ui.ctx(), id, default_open);

if let Some(open) = open {
if open != state.is_open() {
state.toggle(ui);
state.toggle_open(ui);
header_response.mark_changed();
}
} else if header_response.clicked() {
state.toggle(ui);
state.toggle_open(ui);
header_response.mark_changed();
}

Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/containers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ pub use {
popup::*,
resize::Resize,
scroll_area::ScrollArea,
window::Window,
window::{DisplayEvent, Window},
};
105 changes: 82 additions & 23 deletions crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,33 @@ use super::*;
#[must_use = "You should call .show()"]
pub struct Window<'open> {
title: WidgetText,
open: Option<&'open mut bool>,
area: Area,
frame: Option<Frame>,
resize: Resize,
scroll: ScrollArea,
collapsible: bool,
default_open: bool,
with_title_bar: bool,
with_closing_btn: bool,
display_event: Option<DisplayEvent>,
open: Option<&'open mut bool>,
}

#[derive(PartialEq, Default, Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum DisplayEvent {
#[default]
Expand,
Collapse,
Hide,
ToggleCollapse,
ToggleHidden,
}

impl DisplayEvent {
pub fn equals(&self, other: DisplayEvent) -> bool {
self == &other
}
}

impl<'open> Window<'open> {
Expand All @@ -44,6 +63,7 @@ impl<'open> Window<'open> {
pub fn new(title: impl Into<WidgetText>) -> Self {
let title = title.into().fallback_text_style(TextStyle::Heading);
let area = Area::new(Id::new(title.text())).constrain(true);

Self {
title,
open: None,
Expand All @@ -55,8 +75,10 @@ impl<'open> Window<'open> {
.default_size([340.0, 420.0]), // Default inner size of a window
scroll: ScrollArea::neither(),
collapsible: true,
default_open: true,
with_title_bar: true,
with_closing_btn: false,
default_open: true,
display_event: None,
}
}

Expand All @@ -71,8 +93,15 @@ impl<'open> Window<'open> {
/// * If `*open == false`, the window will not be visible.
/// * If `*open == true`, the window will have a close button.
/// * If the close button is pressed, `*open` will be set to `false`.
//#[deprecated = "Use display_state and closing_button instead"]
pub fn open(mut self, open: &'open mut bool) -> Self {
self.open = Some(open);
self.with_closing_btn = true;
self
}

pub fn closing_button(mut self, closing_button: bool) -> Self {
self.with_closing_btn = closing_button;
self
}

Expand Down Expand Up @@ -205,6 +234,11 @@ impl<'open> Window<'open> {
self
}

pub fn display_mode(mut self, new_event: &mut Option<DisplayEvent>) -> Self {
self.display_event = new_event.take();
self
}

/// Set initial size of the window.
pub fn default_size(mut self, default_size: impl Into<Vec2>) -> Self {
self.resize = self.resize.default_size(default_size);
Expand Down Expand Up @@ -317,33 +351,58 @@ impl<'open> Window<'open> {
) -> Option<InnerResponse<Option<R>>> {
let Window {
title,
open,
mut open,
area,
frame,
resize,
scroll,
collapsible,
default_open,
with_title_bar,
with_closing_btn,
default_open,
display_event,
} = self;

let frame = frame.unwrap_or_else(|| Frame::window(&ctx.style()));

let is_explicitly_closed = matches!(open, Some(false));
let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible());
let area_id = area.id;
let mut collapsing = CollapsingState::load(ctx, area_id.with("collapsing"), default_open);

// Borrow open to not consume it.
// This is for backwards compatibility with events
fn backwards_compatibility_open(
open: &mut Option<&mut bool>,
collapsing: &CollapsingState,
) {
if let Some(open) = open {
**open = !collapsing.is_hidden();
}
}

match display_event {
Some(DisplayEvent::Hide) => collapsing.set_hidden(true),
Some(DisplayEvent::Expand) => collapsing.set_open(true),
Some(DisplayEvent::Collapse) => collapsing.set_open(false),
Some(DisplayEvent::ToggleHidden) => collapsing.toggle_hidden(),
Some(DisplayEvent::ToggleCollapse) => collapsing.set_open(!collapsing.is_open()),
None => collapsing.set_hidden(matches!(open, Some(false))),
}

let is_open = !collapsing.is_hidden() || ctx.memory(|mem| mem.everything_is_visible());
area.show_open_close_animation(ctx, &frame, is_open);

backwards_compatibility_open(&mut open, &collapsing);

if !is_open {
collapsing.store(ctx);
return None;
}

let area_id = area.id;
let area_layer_id = area.layer();
let resize_id = area_id.with("resize");
let mut collapsing =
CollapsingState::load_with_default_open(ctx, area_id.with("collapsing"), default_open);
let area_layer_id = area.layer();

let is_collapsed = with_title_bar && !collapsing.is_open();

let possible = PossibleInteractions::new(&area, &resize, is_collapsed);

let area = area.movable(false); // We move it manually, or the area will move the window when we want to resize it
Expand Down Expand Up @@ -388,6 +447,7 @@ impl<'open> Window<'open> {
} else {
None
};

let hover_interaction = resize_hover(ctx, possible, area_layer_id, last_frame_outer_rect);

let mut area_content_ui = area.content_ui(ctx);
Expand All @@ -397,12 +457,11 @@ impl<'open> Window<'open> {
let frame_stroke = frame.stroke;
let mut frame = frame.begin(&mut area_content_ui);

let show_close_button = open.is_some();
let title_bar = if with_title_bar {
let title_bar = show_title_bar(
&mut frame.content_ui,
title,
show_close_button,
with_closing_btn,
&mut collapsing,
collapsible,
);
Expand Down Expand Up @@ -438,7 +497,7 @@ impl<'open> Window<'open> {
&mut area_content_ui,
outer_rect,
&content_response,
open,
with_closing_btn,
&mut collapsing,
collapsible,
);
Expand Down Expand Up @@ -468,11 +527,13 @@ impl<'open> Window<'open> {

let full_response = area.end(ctx, area_content_ui);

let inner_response = InnerResponse {
// Hide butten could have been pressed
backwards_compatibility_open(&mut open, &collapsing);

Some(InnerResponse {
inner: content_inner,
response: full_response,
};
Some(inner_response)
})
}
}

Expand Down Expand Up @@ -907,7 +968,7 @@ impl TitleBar {
ui: &mut Ui,
outer_rect: Rect,
content_response: &Option<Response>,
open: Option<&mut bool>,
with_closing_btn: bool,
collapsing: &mut CollapsingState,
collapsible: bool,
) {
Expand All @@ -916,11 +977,9 @@ impl TitleBar {
self.rect.max.x = self.rect.max.x.max(content_response.rect.max.x);
}

if let Some(open) = open {
// Add close button now that we know our full width:
if self.close_button_ui(ui).clicked() {
*open = false;
}
// Add close button now that we know our full width:
if with_closing_btn && self.close_button_ui(ui).clicked() {
collapsing.toggle_hidden();
}

let full_top_rect = Rect::from_x_y_ranges(self.rect.x_range(), self.min_rect.y_range());
Expand Down Expand Up @@ -950,7 +1009,7 @@ impl TitleBar {
.double_clicked()
&& collapsible
{
collapsing.toggle(ui);
collapsing.toggle_open(ui);
}
}

Expand Down
17 changes: 16 additions & 1 deletion crates/egui_demo_lib/src/demo/about.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use egui::DisplayEvent;

#[derive(Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct About {}
pub struct About {
display_event: Option<DisplayEvent>,
}

impl super::Demo for About {
fn name(&self) -> &'static str {
Expand All @@ -13,13 +17,24 @@ impl super::Demo for About {
.default_width(320.0)
.default_height(480.0)
.open(open)
.display_mode(&mut self.display_event)
.show(ctx, |ui| {
use super::View as _;
self.ui(ui);
});
}
}

impl About {
pub fn toggle_collapse(&mut self) {
self.display_event = Some(DisplayEvent::ToggleCollapse);
}

pub fn toggle_hidden(&mut self) {
self.display_event = Some(DisplayEvent::ToggleHidden);
}
}

impl super::View for About {
fn ui(&mut self, ui: &mut egui::Ui) {
use egui::special_emojis::{OS_APPLE, OS_LINUX, OS_WINDOWS};
Expand Down
6 changes: 6 additions & 0 deletions crates/egui_demo_lib/src/demo/demo_app_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,12 @@ impl DemoWindows {
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
egui::menu::bar(ui, |ui| {
file_menu_button(ui);
if ui.button("Expand / Collapse event").clicked() {
self.about.toggle_collapse();
}
if ui.button("Show / hide event").clicked() {
self.about.toggle_hidden();
}
});
});

Expand Down
Loading

0 comments on commit b2d617a

Please sign in to comment.