diff --git a/emigui/src/containers/area.rs b/emigui/src/containers/area.rs index 3ffb3b9a..05f2e703 100644 --- a/emigui/src/containers/area.rs +++ b/emigui/src/containers/area.rs @@ -91,13 +91,15 @@ impl Area { } } -impl Area { - // TODO - // pub fn show(self, ui: &Ui, add_contents: impl FnOnce(&mut Ui)) { - // let default_pos = self.default_pos.unwrap_or_else(|| ui.top_left() + pos2(100.0, 100.0)); // TODO - // } +struct Prepared { + layer: Layer, + state: State, + movable: bool, + content_ui: Ui, +} - pub fn show(self, ctx: &Arc, add_contents: impl FnOnce(&mut Ui)) -> InteractInfo { +impl Area { + fn prepare(self, ctx: &Arc) -> Prepared { let Area { id, movable, @@ -120,19 +122,45 @@ impl Area { state.pos = fixed_pos.unwrap_or(state.pos); state.pos = state.pos.round(); - let mut ui = Ui::new( + let content_ui = Ui::new( ctx.clone(), layer, id, Rect::from_min_size(state.pos, Vec2::infinity()), ); - add_contents(&mut ui); - state.size = (ui.child_bounds().max - state.pos).ceil(); + + Prepared { + layer, + state, + movable, + content_ui, + } + } + + pub fn show(self, ctx: &Arc, add_contents: impl FnOnce(&mut Ui)) -> InteractInfo { + let mut prepared = self.prepare(ctx); + add_contents(&mut prepared.content_ui); + Self::finish(ctx, prepared) + } + + fn finish(ctx: &Arc, prepared: Prepared) -> InteractInfo { + let Prepared { + layer, + mut state, + movable, + content_ui, + } = prepared; + + state.size = (content_ui.child_bounds().max - state.pos).ceil(); let rect = Rect::from_min_size(state.pos, state.size); let clip_rect = Rect::everything(); // TODO: get from context - let interact_id = if movable { Some(id.with("move")) } else { None }; + let interact_id = if movable { + Some(layer.id.with("move")) + } else { + None + }; let move_interact = ctx.interact(layer, clip_rect, rect, interact_id); let input = ctx.input(); diff --git a/emigui/src/containers/collapsing_header.rs b/emigui/src/containers/collapsing_header.rs index 7fee1955..f9af3e61 100644 --- a/emigui/src/containers/collapsing_header.rs +++ b/emigui/src/containers/collapsing_header.rs @@ -162,8 +162,13 @@ impl CollapsingHeader { } } +struct Prepared { + id: Id, + state: State, +} + impl CollapsingHeader { - pub fn show(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> Option { + fn prepare(self, ui: &mut Ui) -> Prepared { assert!( ui.layout().dir() == Direction::Vertical, "Horizontal collapsing is unimplemented" @@ -231,14 +236,14 @@ impl CollapsingHeader { }, ); - ui.expand_to_include_child(interact.rect); // TODO: remove, just a test + Prepared { id, state } + } + pub fn show(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> Option { + let Prepared { id, mut state } = self.prepare(ui); let r_interact = state.add_contents(ui, |ui| ui.indent(id, add_contents).0); let ret = r_interact.map(|ri| ri.0); - ui.memory().collapsing_headers.insert(id, state); - ui.response(interact); - ret } } diff --git a/emigui/src/containers/resize.rs b/emigui/src/containers/resize.rs index 2ef54bef..d41372e5 100644 --- a/emigui/src/containers/resize.rs +++ b/emigui/src/containers/resize.rs @@ -168,8 +168,16 @@ impl Resize { } } +struct Prepared { + id: Id, + state: State, + is_new: bool, + corner_interact: Option, + content_ui: Ui, +} + impl Resize { - pub fn show(mut self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R { + fn prepare(&mut self, ui: &mut Ui) -> Prepared { let id = self.id.unwrap_or_else(|| ui.make_child_id("resize")); self.min_size = self.min_size.min(ui.available().size()); self.max_size = self.max_size.min(ui.available().size()); @@ -232,27 +240,49 @@ impl Resize { let inner_rect = Rect::from_min_size(position, state.size); - let (ret, desired_size); - { - let mut content_clip_rect = ui - .clip_rect() - .intersect(inner_rect.expand(ui.style().clip_rect_margin)); + let mut content_clip_rect = ui + .clip_rect() + .intersect(inner_rect.expand(ui.style().clip_rect_margin)); - // If we pull the resize handle to shrink, we want to TRY to shink it. - // After laying out the contents, we might be much bigger. - // In those cases we don't want the clip_rect to be smaller, because - // then we will clip the contents of the region even thought the result gets larger. This is simply ugly! - // So we use the memory of last_frame_size to make the clip rect large enough. - content_clip_rect.max = content_clip_rect - .max - .max(content_clip_rect.min + last_frame_size) - .min(ui.clip_rect().max); // Respect parent region + // If we pull the resize handle to shrink, we want to TRY to shink it. + // After laying out the contents, we might be much bigger. + // In those cases we don't want the clip_rect to be smaller, because + // then we will clip the contents of the region even thought the result gets larger. This is simply ugly! + // So we use the memory of last_frame_size to make the clip rect large enough. + content_clip_rect.max = content_clip_rect + .max + .max(content_clip_rect.min + last_frame_size) + .min(ui.clip_rect().max); // Respect parent region - let mut contents_ui = ui.child_ui(inner_rect); - contents_ui.set_clip_rect(content_clip_rect); - ret = add_contents(&mut contents_ui); - desired_size = contents_ui.bounding_size(); - }; + let mut content_ui = ui.child_ui(inner_rect); + content_ui.set_clip_rect(content_clip_rect); + + Prepared { + id, + state, + is_new, + corner_interact, + content_ui, + } + } + + pub fn show(mut self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R { + let mut prepared = self.prepare(ui); + let ret = add_contents(&mut prepared.content_ui); + self.finish(ui, prepared); + ret + } + + fn finish(self, ui: &mut Ui, prepared: Prepared) { + let Prepared { + id, + mut state, + is_new, + corner_interact, + content_ui, + } = prepared; + + let desired_size = content_ui.bounding_size(); let desired_size = desired_size.ceil(); // Avoid rounding errors in math // ------------------------------ @@ -279,7 +309,7 @@ impl Resize { // ------------------------------ if self.outline && corner_interact.is_some() { - let rect = Rect::from_min_size(position, state.size); + let rect = Rect::from_min_size(content_ui.top_left(), state.size); let rect = rect.expand(2.0); // breathing room for content ui.add_paint_cmd(paint::PaintCmd::Rect { rect, @@ -298,8 +328,6 @@ impl Resize { } ui.memory().resize.insert(id, state); - - ret } } diff --git a/emigui/src/containers/scroll_area.rs b/emigui/src/containers/scroll_area.rs index 1a7bfab7..0ec34211 100644 --- a/emigui/src/containers/scroll_area.rs +++ b/emigui/src/containers/scroll_area.rs @@ -44,15 +44,30 @@ impl ScrollArea { } } -impl ScrollArea { - pub fn show(self, outer_ui: &mut Ui, add_contents: impl FnOnce(&mut Ui)) { - let ctx = outer_ui.ctx().clone(); +struct Prepared { + id: Id, + state: State, + current_scroll_bar_width: f32, + always_show_scroll: bool, + inner_rect: Rect, + content_ui: Ui, +} - let scroll_area_id = outer_ui.make_child_id("scroll_area"); - let mut state = ctx +impl ScrollArea { + fn prepare(self, ui: &mut Ui) -> Prepared { + let Self { + max_height, + always_show_scroll, + auto_hide_scroll, + } = self; + + let ctx = ui.ctx().clone(); + + let id = ui.make_child_id("scroll_area"); + let state = ctx .memory() .scroll_areas - .get(&scroll_area_id) + .get(&id) .cloned() .unwrap_or_default(); @@ -62,29 +77,55 @@ impl ScrollArea { let max_scroll_bar_width = 16.0; - let current_scroll_bar_width = if state.show_scroll || !self.auto_hide_scroll { + let current_scroll_bar_width = if state.show_scroll || !auto_hide_scroll { max_scroll_bar_width // TODO: animate? } else { 0.0 }; let outer_size = vec2( - outer_ui.available().width(), - outer_ui.available().height().min(self.max_height), + ui.available().width(), + ui.available().height().min(max_height), ); let inner_size = outer_size - vec2(current_scroll_bar_width, 0.0); - let inner_rect = Rect::from_min_size(outer_ui.available().min, inner_size); + let inner_rect = Rect::from_min_size(ui.available().min, inner_size); - let mut content_ui = outer_ui.child_ui(Rect::from_min_size( + let mut content_ui = ui.child_ui(Rect::from_min_size( inner_rect.min - state.offset, vec2(inner_size.x, f32::INFINITY), )); - let mut content_clip_rect = outer_ui.clip_rect().intersect(inner_rect); - content_clip_rect.max.x = outer_ui.clip_rect().max.x - current_scroll_bar_width; // Nice handling of forced resizing beyond the possible + let mut content_clip_rect = ui.clip_rect().intersect(inner_rect); + content_clip_rect.max.x = ui.clip_rect().max.x - current_scroll_bar_width; // Nice handling of forced resizing beyond the possible content_ui.set_clip_rect(content_clip_rect); - add_contents(&mut content_ui); + Prepared { + id, + state, + always_show_scroll, + inner_rect, + current_scroll_bar_width, + content_ui, + } + } + + pub fn show(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R { + let mut prepared = self.prepare(ui); + let ret = add_contents(&mut prepared.content_ui); + Self::finish(ui, prepared); + ret + } + + fn finish(ui: &mut Ui, prepared: Prepared) { + let Prepared { + id, + mut state, + inner_rect, + always_show_scroll, + current_scroll_bar_width, + content_ui, + } = prepared; + let content_size = content_ui.bounding_size(); let inner_rect = Rect::from_min_size( @@ -100,22 +141,22 @@ impl ScrollArea { inner_rect.size() + vec2(current_scroll_bar_width, 0.0), ); - let content_is_too_small = content_size.y > inner_size.y; + let content_is_too_small = content_size.y > inner_rect.height(); if content_is_too_small { // Dragg contents to scroll (for touch screens mostly): - let content_interact = outer_ui.interact_rect(inner_rect, scroll_area_id.with("area")); + let content_interact = ui.interact_rect(inner_rect, id.with("area")); if content_interact.active { - state.offset.y -= ctx.input().mouse_move.y; + state.offset.y -= ui.input().mouse_move.y; } } // TODO: check that nothing else is being inteacted with - if outer_ui.contains_mouse(outer_rect) && ctx.memory().active_id.is_none() { - state.offset.y -= ctx.input().scroll_delta.y; + if ui.contains_mouse(outer_rect) && ui.memory().active_id.is_none() { + state.offset.y -= ui.input().scroll_delta.y; } - let show_scroll_this_frame = content_is_too_small || self.always_show_scroll; + let show_scroll_this_frame = content_is_too_small || always_show_scroll; if show_scroll_this_frame || state.show_scroll { let left = inner_rect.right() + 2.0; let right = outer_rect.right(); @@ -137,18 +178,18 @@ impl ScrollArea { ); // intentionally use same id for inside and outside of handle - let interact_id = scroll_area_id.with("vertical"); - let handle_interact = outer_ui.interact_rect(handle_rect, interact_id); + let interact_id = id.with("vertical"); + let handle_interact = ui.interact_rect(handle_rect, interact_id); - if let Some(mouse_pos) = ctx.input().mouse_pos { + if let Some(mouse_pos) = ui.input().mouse_pos { if handle_interact.active { if inner_rect.top() <= mouse_pos.y && mouse_pos.y <= inner_rect.bottom() { state.offset.y += - ctx.input().mouse_move.y * content_size.y / inner_rect.height(); + ui.input().mouse_move.y * content_size.y / inner_rect.height(); } } else { // Check for mouse down outside handle: - let scroll_bg_interact = outer_ui.interact_rect(outer_scroll_rect, interact_id); + let scroll_bg_interact = ui.interact_rect(outer_scroll_rect, interact_id); if scroll_bg_interact.active { // Center scroll at mouse pos: @@ -167,18 +208,18 @@ impl ScrollArea { pos2(right, from_content(state.offset.y + inner_rect.height())), ); - let style = outer_ui.style(); + let style = ui.style(); let handle_fill_color = style.interact(&handle_interact).fill_color; let handle_outline = style.interact(&handle_interact).rect_outline; - outer_ui.add_paint_cmd(paint::PaintCmd::Rect { + ui.add_paint_cmd(paint::PaintCmd::Rect { rect: outer_scroll_rect, corner_radius, - fill_color: Some(outer_ui.style().dark_bg_color), + fill_color: Some(ui.style().dark_bg_color), outline: None, }); - outer_ui.add_paint_cmd(paint::PaintCmd::Rect { + ui.add_paint_cmd(paint::PaintCmd::Rect { rect: handle_rect.expand(-2.0), corner_radius, fill_color: Some(handle_fill_color), @@ -192,12 +233,12 @@ impl ScrollArea { // content_size.y.min(inner_rect.size().y), // respect vertical height. // ); let size = outer_rect.size(); - outer_ui.reserve_space(size, None); + ui.reserve_space(size, None); state.offset.y = state.offset.y.min(content_size.y - inner_rect.height()); state.offset.y = state.offset.y.max(0.0); state.show_scroll = show_scroll_this_frame; - outer_ui.memory().scroll_areas.insert(scroll_area_id, state); + ui.memory().scroll_areas.insert(id, state); } } diff --git a/emigui/src/ui.rs b/emigui/src/ui.rs index ce6ee1ef..21e50c2f 100644 --- a/emigui/src/ui.rs +++ b/emigui/src/ui.rs @@ -313,6 +313,7 @@ impl Ui { .interact(self.layer, self.clip_rect, rect, Some(id)) } + #[must_use] pub fn response(&mut self, interact: InteractInfo) -> GuiResponse { // TODO: unify GuiResponse and InteractInfo. They are the same thing! GuiResponse {