From d1b00b6fdce4dc24189150bab6bdea84f26b2155 Mon Sep 17 00:00:00 2001 From: Konkitoman Date: Thu, 3 Aug 2023 20:13:04 +0300 Subject: [PATCH] Add Window::show_async! A refactoring of crates/egui/src/containers/window.rs is needed --- crates/egui/src/containers/window.rs | 468 +++++++++++++++++++++++++++ examples/viewports/src/main.rs | 111 ++++--- 2 files changed, 526 insertions(+), 53 deletions(-) diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 9a0ecd93b..fa98cdf9b 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -439,6 +439,11 @@ impl<'open> Window<'open> { self.show_dyn(ctx, Box::new(add_contents)) } + #[inline] + pub fn show_async(self, ctx: &Context, add_contents: impl Fn(&mut Ui) + Send + Sync + 'static) { + self.show_dyn_async(ctx, Box::new(add_contents)) + } + fn show_dyn<'a, R>( self, ctx: &Context, @@ -911,6 +916,469 @@ impl<'open> Window<'open> { }; Some(inner_response) } + + fn show_dyn_async<'a>(self, ctx: &Context, add_contents: Box) { + let Window { + title, + mut open, + mut embedded, + area, + frame, + resize, + scroll, + collapsible, + default_open, + with_title_bar, + mut window_builder, + } = self; + + let embedded = if let Some(embedded) = &mut embedded { + if let Some(tmp_embedded) = ctx.data_mut(|data| { + let tmp = data.get_persisted::(area.id.with("_embedded")); + data.remove::(area.id.with("_embedded")); + tmp + }) { + **embedded = tmp_embedded; + } + **embedded + } else { + true + }; + + let is_open = if let Some(open) = &mut open { + if let Some(tmp_open) = ctx.data_mut(|data| { + let tmp = data.get_persisted::(area.id.with("_open")); + data.remove::(area.id.with("_open")); + tmp + }) { + **open = tmp_open; + } + **open + } else { + true + }; + + let is_open = is_open || ctx.memory(|mem| mem.everything_is_visible()); + + ctx.data_mut(|data| { + data.insert_persisted(area.id.with("_is_embedded"), embedded); + data.insert_persisted(area.id.with("_is_open"), is_open); + }); + + if !is_open { + return; + } + + let show_close_button = open.is_some(); + + 'create_viewport: { + if !embedded && ctx.is_desktop() { + if let Some(size) = ctx.data(|data| data.get_temp::(area.id.with("size"))) { + let size = size.round() + + ctx.style().spacing.window_margin.sum() * ctx.pixels_per_point(); + window_builder = + window_builder.with_inner_size((size.x as u32 + 1, size.y as u32 + 1)); + } else { + ctx.request_repaint(); + break 'create_viewport; + } + window_builder.close_button = open.is_some(); + window_builder.resizable = resize.is_resizable(); + window_builder.decorations = !with_title_bar; + let pix = ctx.pixels_per_point(); + let min_size = resize.min_size * pix; + let max_size = resize.max_size * pix; + let max_size = if !max_size.is_finite() { + None + } else { + Some(max_size) + }; + window_builder.min_inner_size = Some((min_size.x as u32, min_size.y as u32)); + if let Some(max_size) = max_size { + window_builder.max_inner_size = Some((max_size.x as u32, max_size.y as u32)); + } + + ctx.create_viewport( + window_builder, + move |ctx, viewport_id, parent_viewport_id| { + let mut op = is_open; + let open = if show_close_button { + Some(&mut op) + } else { + None + }; + let scroll = scroll.clone(); + let title = title.clone(); + let frame = frame + .unwrap_or_else(|| Frame::window(&ctx.style())) + .outer_margin(0.0) + .shadow(Shadow::NONE) + .stroke(Stroke::new( + 1.0, + if ctx.input(|i| i.focused) { + Color32::BLUE + } else { + Color32::BROWN + }, + )); + + area.show_open_close_animation(ctx, &frame, is_open); + + 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 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 + let resize = resize.resizable(false); // We move it manually + let mut resize = resize.id(resize_id); + + let mut area = area.begin(ctx); + let win_size = ctx.input(|i| i.screen_rect.size()); + area.state_mut().set_left_top_pos(Pos2::ZERO); + area.state_mut().size = win_size; + let title_content_spacing = 2.0 * ctx.style().spacing.item_spacing.y; + + let title_bar_height = if with_title_bar { + let style = ctx.style(); + ctx.fonts(|f| title.font_height(f, &style)) + title_content_spacing + } else { + 0.0 + }; + let margins = frame.outer_margin.sum() + + frame.inner_margin.sum() + + vec2(0.0, title_bar_height) + - vec2(0.0, 3.0); //magic number + + if let Some(mut state) = resize::State::load(ctx, resize_id) { + state.requested_size = Some(win_size - margins); + state.store(ctx, resize_id); + } + + // First interact (move etc) to avoid frame delay: + let last_frame_outer_rect = area.state().rect(); + + let interaction = if possible.movable || possible.resizable() { + window_interaction( + ctx, + possible, + area_layer_id, + area_id.with("frame_resize"), + last_frame_outer_rect, + ) + .and_then(|window_interaction| { + // Calculate roughly how much larger the window size is compared to the inner rect + + let pointer_pos = ctx.input(|i| i.pointer.interact_pos())?; + let mut rect = window_interaction.start_rect; // prevent drift + + window_interaction.set_cursor(ctx); + if window_interaction.is_resize() { + ctx.viewport_command( + viewport_id, + ViewportCommand::Resize( + window_interaction.top, + window_interaction.bottom, + window_interaction.right, + window_interaction.left, + ), + ); + } else { + if ctx.input(|i| i.pointer.primary_pressed()) {} + } + ctx.memory_mut(|mem| mem.areas.move_to_top(area_layer_id)); + + Some(window_interaction) + }) + } 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); + + let mut size = Vec2::new(1.0, 1.0); + + let content_inner = { + // BEGIN FRAME -------------------------------- + let frame_stroke = frame.stroke; + let mut frame = frame.begin(&mut area_content_ui); + + let title_bar = if with_title_bar { + let title_bar = show_title_bar( + &mut frame.content_ui, + title, + show_close_button, + &mut collapsing, + collapsible, + ); + resize.min_size.x = + resize.min_size.x.at_least(title_bar.rect.width()); // Prevent making window smaller than title bar width + Some(title_bar) + } else { + None + }; + + let (content_inner, content_response) = collapsing + .show_body_unindented(&mut frame.content_ui, |ui| { + resize.show(ui, |ui| { + if title_bar.is_some() { + ui.add_space(title_content_spacing); + } + + if scroll.has_any_bar() { + scroll.show(ui, |ui| add_contents(ui)).inner + } else { + add_contents(ui) + } + }) + }) + .map_or((None, None), |ir| (Some(ir.inner), Some(ir.response))); + if let Some(content_response) = &content_response { + size = content_response.rect.size() + } + + let outer_rect = frame.end(&mut area_content_ui).rect; + paint_resize_corner( + &mut area_content_ui, + &possible, + outer_rect, + frame_stroke, + ); + + // END FRAME -------------------------------- + + if let Some(title_bar) = title_bar { + let res = title_bar.ui( + &mut area_content_ui, + outer_rect, + &content_response, + open, + &mut collapsing, + collapsible, + ); + if res.is_pointer_button_down_on() { + ctx.viewport_command(viewport_id, ViewportCommand::Drag); + } + } + + collapsing.store(ctx); + + if let Some(interaction) = interaction { + paint_frame_interaction( + &mut area_content_ui, + outer_rect, + interaction, + ctx.style().visuals.widgets.active, + ); + } else if let Some(hover_interaction) = hover_interaction { + if ctx.input(|i| i.pointer.has_pointer()) { + paint_frame_interaction( + &mut area_content_ui, + outer_rect, + hover_interaction, + ctx.style().visuals.widgets.hovered, + ); + } + } + content_inner + }; + + let full_response = area.end(ctx, area_content_ui); + + if !collapsing.is_open() { + let size = ctx.round_vec_to_pixels(full_response.rect.size()); + ctx.viewport_command( + viewport_id, + ViewportCommand::InnerSize(size.x as u32, size.y as u32), + ); + } + + // let size = ctx.round_vec_to_pixels(full_response.rect.size()); + if win_size.x < size.x { + println!("Set size!"); + ctx.viewport_command( + viewport_id, + ViewportCommand::InnerSize(size.x as u32, win_size.y as u32), + ); + } + if win_size.y < size.y { + println!("Set size!"); + ctx.viewport_command( + viewport_id, + ViewportCommand::InnerSize(win_size.x as u32, size.y as u32), + ); + } + if show_close_button && op != is_open { + ctx.data_mut(|data| data.insert_persisted(area_id.with("_open"), op)); + ctx.request_repaint_viewport(parent_viewport_id); + } + }, + ); + return; + } + } + let frame = frame.unwrap_or_else(|| Frame::window(&ctx.style())); + + area.show_open_close_animation(ctx, &frame, is_open); + + 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 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 + let resize = resize.resizable(false); // We move it manually + let mut resize = resize.id(resize_id); + + let mut area = area.begin(ctx); + + let title_content_spacing = 2.0 * ctx.style().spacing.item_spacing.y; + + // First interact (move etc) to avoid frame delay: + let last_frame_outer_rect = area.state().rect(); + let interaction = if possible.movable || possible.resizable() { + window_interaction( + ctx, + possible, + area_layer_id, + area_id.with("frame_resize"), + last_frame_outer_rect, + ) + .and_then(|window_interaction| { + // Calculate roughly how much larger the window size is compared to the inner rect + let title_bar_height = if with_title_bar { + let style = ctx.style(); + ctx.fonts(|f| title.font_height(f, &style)) + title_content_spacing + } else { + 0.0 + }; + let margins = frame.outer_margin.sum() + + frame.inner_margin.sum() + + vec2(0.0, title_bar_height); + + interact( + window_interaction, + ctx, + margins, + area_layer_id, + &mut area, + resize_id, + ) + }) + } 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); + + let mut size = Vec2::new(1.0, 1.0); + + let content_inner = { + // BEGIN FRAME -------------------------------- + let frame_stroke = frame.stroke; + let mut frame = frame.begin(&mut area_content_ui); + + let title_bar = if with_title_bar { + let title_bar = show_title_bar( + &mut frame.content_ui, + title, + show_close_button, + &mut collapsing, + collapsible, + ); + resize.min_size.x = resize.min_size.x.at_least(title_bar.rect.width()); // Prevent making window smaller than title bar width + Some(title_bar) + } else { + None + }; + + let (content_inner, content_response) = collapsing + .show_body_unindented(&mut frame.content_ui, |ui| { + resize.show(ui, |ui| { + if title_bar.is_some() { + ui.add_space(title_content_spacing); + } + + if scroll.has_any_bar() { + scroll.show(ui, |ui| add_contents(ui)).inner + } else { + add_contents(ui) + } + }) + }) + .map_or((None, None), |ir| (Some(ir.inner), Some(ir.response))); + if let Some(content_response) = &content_response { + size = content_response.rect.size() + } + + let outer_rect = frame.end(&mut area_content_ui).rect; + paint_resize_corner(&mut area_content_ui, &possible, outer_rect, frame_stroke); + + // END FRAME -------------------------------- + + if let Some(title_bar) = title_bar { + title_bar.ui( + &mut area_content_ui, + outer_rect, + &content_response, + open, + &mut collapsing, + collapsible, + ); + } + + collapsing.store(ctx); + + if let Some(interaction) = interaction { + paint_frame_interaction( + &mut area_content_ui, + outer_rect, + interaction, + ctx.style().visuals.widgets.active, + ); + } else if let Some(hover_interaction) = hover_interaction { + if ctx.input(|i| i.pointer.has_pointer()) { + paint_frame_interaction( + &mut area_content_ui, + outer_rect, + hover_interaction, + ctx.style().visuals.widgets.hovered, + ); + } + } + content_inner + }; + + { + let pos = ctx + .constrain_window_rect_to_area(area.state().rect(), area.drag_bounds()) + .left_top(); + area.state_mut().set_left_top_pos(pos); + } + + let full_response = area.end(ctx, area_content_ui); + ctx.data_mut(|data| data.insert_temp(area_id.with("size"), size)); + + let inner_response = InnerResponse { + inner: content_inner, + response: full_response, + }; + } } fn paint_resize_corner( diff --git a/examples/viewports/src/main.rs b/examples/viewports/src/main.rs index 90ed2924f..990fe53f2 100644 --- a/examples/viewports/src/main.rs +++ b/examples/viewports/src/main.rs @@ -14,6 +14,7 @@ fn main() { let mut embedded1 = false; let mut embedded2 = true; + let mut embedded3 = true; let _ = eframe::run_simple_native( "Viewports Examples", @@ -100,62 +101,66 @@ fn main() { } }); }); - egui::CollapsingHeader::new("Shout Test2").show(ui, |ui| { - egui::Window::new("Test2").show(ctx, move |ui| { - ui.label(format!("Frame: {}", ui.ctx().frame_nr())); - let mut embedded = ui.data_mut(|data| { - *data.get_temp_mut_or(Id::new("Test2").with("_embedded"), true) - }); - if ui.checkbox(&mut embedded, "Should embedd?").clicked() { - ui.ctx() - .request_repaint_viewport(ui.ctx().get_parent_viewport_id()); - } - ui.data_mut(|data| { - data.insert_persisted(Id::new("Test2").with("_embedded"), embedded) - }); - if to_repair { - ui.spinner(); - } - let ctx = ui.ctx().clone(); - ui.label(format!( - "Current rendering window: {}", - ctx.get_viewport_id() - )); + egui::CollapsingHeader::new("Async Test2").show(ui, |ui| { + egui::Window::new("Test2") + .embedded(&mut embedded2) + .show_async(ctx, move |ui| { + ui.label(format!("Frame: {}", ui.ctx().frame_nr())); + let mut embedded = ui.data_mut(|data| { + *data.get_temp_mut_or(Id::new("Test2").with("_is_embedded"), true) + }); + if ui.checkbox(&mut embedded, "Should embedd?").clicked() { + ui.ctx() + .request_repaint_viewport(ui.ctx().get_parent_viewport_id()); + } + ui.data_mut(|data| { + data.insert_persisted(Id::new("Test2").with("_embedded"), embedded) + }); + if to_repair { + ui.spinner(); + } + let ctx = ui.ctx().clone(); + ui.label(format!( + "Current rendering window: {}", + ctx.get_viewport_id() + )); - if ui.button("Drag").is_pointer_button_down_on() { - ctx.viewport_command( - ctx.get_viewport_id(), - egui::window::ViewportCommand::Drag, - ) - } - }); + if ui.button("Drag").is_pointer_button_down_on() { + ctx.viewport_command( + ctx.get_viewport_id(), + egui::window::ViewportCommand::Drag, + ) + } + }); }); - egui::CollapsingHeader::new("Shout Test3").show(ui, |ui| { - egui::Window::new("Test3").show(ctx, move |ui| { - ui.label(format!("Frame: {}", ui.ctx().frame_nr())); - let mut embedded = ui.data_mut(|data| { - *data.get_temp_mut_or(Id::new("Test3").with("_embedded"), true) - }); - if ui.checkbox(&mut embedded, "Should embedd?").clicked() { - ui.ctx() - .request_repaint_viewport(ui.ctx().get_parent_viewport_id()); - } - ui.data_mut(|data| { - data.insert_persisted(Id::new("Test3").with("_embedded"), embedded) - }); - let ctx = ui.ctx().clone(); - ui.label(format!( - "Current rendering window: {}", - ctx.get_viewport_id() - )); + egui::CollapsingHeader::new("Async Test3").show(ui, |ui| { + egui::Window::new("Test3") + .embedded(&mut embedded3) + .show_async(ctx, move |ui| { + ui.label(format!("Frame: {}", ui.ctx().frame_nr())); + let mut embedded = ui.data_mut(|data| { + *data.get_temp_mut_or(Id::new("Test3").with("_is_embedded"), true) + }); + if ui.checkbox(&mut embedded, "Should embedd?").clicked() { + ui.ctx() + .request_repaint_viewport(ui.ctx().get_parent_viewport_id()); + } + ui.data_mut(|data| { + data.insert_persisted(Id::new("Test3").with("_embedded"), embedded) + }); + let ctx = ui.ctx().clone(); + ui.label(format!( + "Current rendering window: {}", + ctx.get_viewport_id() + )); - if ui.button("Drag").is_pointer_button_down_on() { - ctx.viewport_command( - ctx.get_viewport_id(), - egui::window::ViewportCommand::Drag, - ) - } - }); + if ui.button("Drag").is_pointer_button_down_on() { + ctx.viewport_command( + ctx.get_viewport_id(), + egui::window::ViewportCommand::Drag, + ) + } + }); }); }); },