From 6711e9770831ac091f7c923a4b196b3daab41c7f Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Thu, 2 Apr 2026 16:40:28 -0700 Subject: [PATCH 1/6] Improve the EditingPane animation so it's not a janky margin-based slide --- src/home/editing_pane.rs | 163 ++++++++++++++++++++++++++++--------- src/room/room_input_bar.rs | 28 +++---- 2 files changed, 137 insertions(+), 54 deletions(-) diff --git a/src/home/editing_pane.rs b/src/home/editing_pane.rs index ae89492b0..e0e1765d1 100644 --- a/src/home/editing_pane.rs +++ b/src/home/editing_pane.rs @@ -21,16 +21,24 @@ script_mod! { use mod.widgets.* - mod.widgets.EditingContent = View { + mod.widgets.EditingContent = RoundedView { width: Fill, - height: Fit{max: FitBound.Rel{base: Base.Full, factor: 0.75}} - align: Align{x: 0.5, y: 1.0}, // centered horizontally, bottom-aligned + height: Fit, padding: Inset{ left: 20, right: 20, top: 10, bottom: 10 } - margin: Inset{top: 2} spacing: 10, flow: Down, - show_bg: false // don't cover up the RoomInputBar + // this must match the RoomInputBar exactly such that it overlaps atop it. + show_bg: true, + draw_bg +: { + color: (COLOR_PRIMARY) + border_radius: 5.0 + border_color: #F00 // (COLOR_SECONDARY) + border_size: 5.0 // 2.0 + // shadow_color: #0006 + // shadow_radius: 0.0 + // shadow_offset: vec2(0.0,0.0) + } View { width: Fill, height: Fit @@ -83,34 +91,31 @@ script_mod! { mod.widgets.EditingPane = #(EditingPane::register_widget(vm)) { + ..mod.widgets.RoundedView + visible: false, width: Fill, height: Fit{max: FitBound.Rel{base: Base.Full, factor: 0.75}} align: Align{x: 0.5, y: 1.0} - // TODO: FIXME: this is a hack to make the editing pane - // able to slide out of the bottom of the screen. - // (Waiting on a Makepad-level fix for this.) - margin: Inset{top: 1000} editing_content := mod.widgets.EditingContent { } + + slide: 1.0, animator: Animator{ panel: { default: @hide show: AnimatorState{ redraw: true, - from: {all: Forward {duration: 0.8}} + from: {all: Forward {duration: 0.5}} ease: ExpDecay {d1: 0.80, d2: 0.97} - apply: { margin: Inset{top: 0} } + apply: { slide: 0.0 } } hide: AnimatorState{ redraw: true, - from: {all: Forward {duration: 0.8}} + from: {all: Forward {duration: 0.5}} ease: ExpDecay {d1: 0.80, d2: 0.97} - // TODO: FIXME: this is a hack to make the editing pane - // able to slide out of the bottom of the screen. - // (Waiting on a Makepad-level fix for this.) - apply: { margin: Inset{top: 1000} } + apply: { slide: 1.0 } } } } @@ -120,7 +125,9 @@ script_mod! { /// Action emitted by the EditingPane widget. #[derive(Clone, Default, Debug)] pub enum EditingPaneAction { - /// The editing pane has been closed/hidden. + /// The editing pane's hide animation has started. + HideAnimationStarted, + /// The editing pane has been fully closed/hidden. Hidden, #[default] None, @@ -145,13 +152,25 @@ pub struct EditingPane { #[source] source: ScriptObjectRef, #[deref] view: View, #[apply_default] animator: Animator, + #[live] slide: f32, #[rust] info: Option, #[rust] is_animating_out: bool, + #[rust] last_content_height: f64, + /// A pending next-frame request used to force a parent relayout + /// after the hide animation completes. + #[rust] next_frame: NextFrame, } impl Widget for EditingPane { fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { + // Handle the next-frame event scheduled after hide animation completes. + // This forces a full redraw cycle so the parent relayouts properly. + if self.next_frame.is_event(event).is_some() { + log!("EditingPane: NextFrame fired, calling redraw_all"); + cx.redraw_all(); + } + self.view.handle_event(cx, event, scope); if !self.visible { return; } @@ -160,27 +179,24 @@ impl Widget for EditingPane { if animator_action.must_redraw() { self.redraw(cx); } - // If the animator is in the `hide` state and has finished animating out, - // that means it has fully animated off-screen and can be set to invisible. - if self.animator_in_state(cx, ids!(panel.hide)) { - match ( - self.is_animating_out, - matches!(animator_action, AnimatorAction::Animating { .. }), - ) { - (true, false) => { - self.visible = false; - self.info = None; - cx.widget_action(self.widget_uid(), EditingPaneAction::Hidden); - cx.revert_key_focus(); - self.redraw(cx); - return; - }, - (false, true) => { - self.is_animating_out = true; - return; - }, - _ => {}, + // If we started animating the hide, check if the track has finished. + // `is_track_animating` returns false once the track has fully completed, + // even on the same frame that returned the last `Animating` action. + if self.is_animating_out { + if !self.animator.is_track_animating(id!(panel)) { + self.visible = false; + self.is_animating_out = false; + self.info = None; + cx.widget_action(self.widget_uid(), EditingPaneAction::Hidden); + cx.revert_key_focus(); + self.redraw(cx); + self.next_frame = cx.new_next_frame(); + return; } + } else if self.animator_in_state(cx, ids!(panel.hide)) + && matches!(animator_action, AnimatorAction::Animating { .. }) + { + self.is_animating_out = true; } if let Event::Actions(actions) = event { @@ -195,6 +211,7 @@ impl Widget for EditingPane { || edit_text_input.escaped(actions) { self.animator_play(cx, ids!(panel.hide)); + cx.widget_action(self.widget_uid(), EditingPaneAction::HideAnimationStarted); self.redraw(cx); return; } @@ -283,6 +300,7 @@ impl Widget for EditingPane { None, ); self.animator_play(cx, ids!(panel.hide)); + cx.widget_action(self.widget_uid(), EditingPaneAction::HideAnimationStarted); self.redraw(cx); return; }, @@ -367,11 +385,60 @@ impl Widget for EditingPane { } } - fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { + fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, mut walk: Walk) -> DrawStep { if self.info.is_none() { self.visible = false; }; - self.view.draw_walk(cx, scope, walk) + + // Animate both the layout height and the content position: + // 1. walk.height grows from 0 to ch — the RoomInputBar border + // grows smoothly alongside. + // 2. Balanced margins on editing_content slide it up from below + // the clip boundary (show_bg). + // + // When fully shown (slide ~= 0), walk.height is NOT overridden, + // so the pane uses its natural Fit height. This ensures the + // opaque background covers the full content area (and the + // input_bar beneath it in the overlay). + // Slide editing_content within the pane using balanced margins. + // margin.top pushes content below the pane's clip boundary; + // margin.bottom compensates so the Fit height stays constant. + // The pane's show_bg provides clipping. + let ch = self.last_content_height; + if self.slide > 0.001 { + let offset = if ch > 0.0 { + ch * self.slide as f64 + } else { + 10000.0 + }; + if let Some(mut ec) = self.view(cx, ids!(editing_content)).borrow_mut() { + ec.walk.margin.top = offset; + ec.walk.margin.bottom = -offset; + } + // Animate the layout height alongside the content slide, + // so the RoomInputBar border grows/shrinks smoothly. + if ch > 0.0 { + walk.height = Size::Fixed((ch * (1.0 - self.slide as f64)).max(0.0)); + } else { + walk.height = Size::Fixed(0.0); + } + } else { + // Fully shown or not animating: reset margins. + if let Some(mut ec) = self.view(cx, ids!(editing_content)).borrow_mut() { + ec.walk.margin.top = 0.0; + ec.walk.margin.bottom = 0.0; + } + } + + let step = self.view.draw_walk(cx, scope, walk); + + // Read area rect AFTER drawing to capture this frame's layout. + let ec_height = self.view(cx, ids!(editing_content)).area().rect(cx).size.y; + if ec_height > 0.0 { + self.last_content_height = ec_height; + } + + step } } @@ -402,6 +469,7 @@ impl EditingPane { match edit_result { Ok(()) => { self.animator_play(cx, ids!(panel.hide)); + cx.widget_action(self.widget_uid(), EditingPaneAction::HideAnimationStarted); }, Err(e) => { enqueue_popup_notification( @@ -450,7 +518,15 @@ impl EditingPane { timeline_kind, }); + // Reset editing_content to Fit before starting the animation, + // in case a previous hide animation left it at Fixed(ch). + // This ensures the first draw frame can measure the real content height. + if let Some(mut ec) = self.view(cx, ids!(editing_content)).borrow_mut() { + ec.walk.height = Size::fit(); + } + self.visible = true; + self.is_animating_out = false; self.button(cx, ids!(accept_button)).reset_hover(cx); self.button(cx, ids!(cancel_button)).reset_hover(cx); self.animator_play(cx, ids!(panel.show)); @@ -496,6 +572,7 @@ impl EditingPane { timeline_kind, }); self.visible = true; + self.is_animating_out = false; self.button(cx, ids!(accept_button)).reset_hover(cx); self.button(cx, ids!(cancel_button)).reset_hover(cx); self.animator_play(cx, ids!(panel.show)); @@ -537,6 +614,14 @@ impl EditingPaneRef { ) } + /// Returns whether this `EditingPane`'s hide animation started in the given actions. + pub fn was_hide_animation_started(&self, actions: &Actions) -> bool { + matches!( + actions.find_widget_action(self.widget_uid()).cast_ref(), + EditingPaneAction::HideAnimationStarted, + ) + } + /// See [`EditingPane::show()`]. pub fn show( &self, diff --git a/src/room/room_input_bar.rs b/src/room/room_input_bar.rs index 93b8d4a9d..e69b8a2a8 100644 --- a/src/room/room_input_bar.rs +++ b/src/room/room_input_bar.rs @@ -359,8 +359,15 @@ impl RoomInputBar { } } - // If the EditingPane has been hidden, handle that. - if self.view.editing_pane(cx, ids!(editing_pane)).was_hidden(actions) { + let editing_pane = self.view.editing_pane(cx, ids!(editing_pane)); + // When the hide animation starts, restore the input_bar immediately + // so it's visible as the editing pane slides away. + if editing_pane.was_hide_animation_started(actions) { + self.view.view(cx, ids!(input_bar)).set_visible(cx, true); + self.redraw(cx); + } + // When the hide animation fully completes, restore the replying preview. + if editing_pane.was_hidden(actions) { self.on_editing_pane_hidden(cx); } } @@ -434,13 +441,9 @@ impl RoomInputBar { behavior: ShowEditingPaneBehavior, timeline_kind: TimelineKind, ) { - // We must hide the input_bar while the editing pane is shown, - // otherwise a very-tall inputted message might show up underneath a shorter editing pane. + // Hide the input_bar, replying preview, and location preview + // while the editing pane is shown. self.view.view(cx, ids!(input_bar)).set_visible(cx, false); - - // Similarly, we must hide the replying preview and location preview, - // since those are not relevant to editing an existing message, - // so keeping them visible might confuse the user. let replying_preview = self.view.view(cx, ids!(replying_preview)); self.was_replying_preview_visible = replying_preview.visible(); replying_preview.set_visible(cx, false); @@ -461,10 +464,7 @@ impl RoomInputBar { /// This should be invoked after the EditingPane has been fully hidden. fn on_editing_pane_hidden(&mut self, cx: &mut Cx) { - // In `show_editing_pane()` above, we hid the input_bar while the editing pane - // was being shown, so here we need to make it visible again. - // Same goes for the replying_preview, if it was previously shown. - self.view.view(cx, ids!(input_bar)).set_visible(cx, true); + // Restore the replying_preview. if self.was_replying_preview_visible && self.replying_to.is_some() { self.view.view(cx, ids!(replying_preview)).set_visible(cx, true); } @@ -489,9 +489,7 @@ impl RoomInputBar { input_bar.set_visible(cx, false); } else { tombstone_footer.hide(cx); - if !self.editing_pane(cx, ids!(editing_pane)).is_currently_shown(cx) { - input_bar.set_visible(cx, true); - } + input_bar.set_visible(cx, true); } } From fe6e27d85a4401d1a41dd3d622cf09635ca64640 Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Thu, 2 Apr 2026 17:21:09 -0700 Subject: [PATCH 2/6] more improvements to EditingPane/input_bar animation --- src/home/editing_pane.rs | 5 +++++ src/room/room_input_bar.rs | 37 ++++++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/home/editing_pane.rs b/src/home/editing_pane.rs index e0e1765d1..6ff999906 100644 --- a/src/home/editing_pane.rs +++ b/src/home/editing_pane.rs @@ -594,6 +594,11 @@ impl EditingPaneRef { inner.is_currently_shown(cx) } + /// Returns the current slide value (0.0 = fully shown, 1.0 = fully hidden). + pub fn slide(&self) -> f32 { + self.borrow().map_or(1.0, |inner| inner.slide) + } + /// See [`EditingPane::handle_edit_result()`]. pub fn handle_edit_result( &self, diff --git a/src/room/room_input_bar.rs b/src/room/room_input_bar.rs index e69b8a2a8..398f19d83 100644 --- a/src/room/room_input_bar.rs +++ b/src/room/room_input_bar.rs @@ -207,6 +207,28 @@ impl Widget for RoomInputBar { } fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { + // Shrink the input_bar's height as the editing pane slides in, + // and grow it back as the editing pane slides out. + // slide=1.0 → editing pane hidden → input_bar at full Fit height. + // slide=0.0 → editing pane shown → input_bar at zero height. + let slide = self.editing_pane(cx, ids!(editing_pane)).slide(); + let input_bar = self.view.view(cx, ids!(input_bar)); + if slide < 0.999 { + let input_bar_height = input_bar.area().rect(cx).size.y; + let h = if input_bar_height > 0.0 { + input_bar_height * slide as f64 + } else { + 0.0 + }; + if let Some(mut inner) = input_bar.borrow_mut() { + inner.walk.height = Size::Fixed(h); + } + } else { + if let Some(mut inner) = input_bar.borrow_mut() { + inner.walk.height = Size::fit(); + } + } + self.view.draw_walk(cx, scope, walk) } } @@ -359,15 +381,8 @@ impl RoomInputBar { } } - let editing_pane = self.view.editing_pane(cx, ids!(editing_pane)); - // When the hide animation starts, restore the input_bar immediately - // so it's visible as the editing pane slides away. - if editing_pane.was_hide_animation_started(actions) { - self.view.view(cx, ids!(input_bar)).set_visible(cx, true); - self.redraw(cx); - } // When the hide animation fully completes, restore the replying preview. - if editing_pane.was_hidden(actions) { + if self.view.editing_pane(cx, ids!(editing_pane)).was_hidden(actions) { self.on_editing_pane_hidden(cx); } } @@ -441,9 +456,9 @@ impl RoomInputBar { behavior: ShowEditingPaneBehavior, timeline_kind: TimelineKind, ) { - // Hide the input_bar, replying preview, and location preview - // while the editing pane is shown. - self.view.view(cx, ids!(input_bar)).set_visible(cx, false); + // Hide the replying preview and location preview while the editing + // pane is shown. The input_bar is not hidden; instead it is slid out + // of view in draw_walk using the EditingPane's slide value. let replying_preview = self.view.view(cx, ids!(replying_preview)); self.was_replying_preview_visible = replying_preview.visible(); replying_preview.set_visible(cx, false); From 5a28e45928e95305b24f60c488aec097f0a3c5c4 Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Thu, 2 Apr 2026 18:08:10 -0700 Subject: [PATCH 3/6] improving animations --- src/home/editing_pane.rs | 4 +++- src/room/room_input_bar.rs | 30 +++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/home/editing_pane.rs b/src/home/editing_pane.rs index 6ff999906..1a218f50f 100644 --- a/src/home/editing_pane.rs +++ b/src/home/editing_pane.rs @@ -177,7 +177,9 @@ impl Widget for EditingPane { let animator_action = self.animator_handle_event(cx, event); if animator_action.must_redraw() { - self.redraw(cx); + // Redraw the entire UI so the parent RoomInputBar can + // animate the input_bar height in its draw_walk. + cx.redraw_all(); } // If we started animating the hide, check if the track has finished. // `is_track_animating` returns false once the track has fully completed, diff --git a/src/room/room_input_bar.rs b/src/room/room_input_bar.rs index 398f19d83..07522565e 100644 --- a/src/room/room_input_bar.rs +++ b/src/room/room_input_bar.rs @@ -169,6 +169,9 @@ pub struct RoomInputBar { #[rust] was_replying_preview_visible: bool, /// Info about the message event that the user is currently replying to, if any. #[rust] replying_to: Option<(EventTimelineItem, EmbeddedEvent)>, + /// Cached natural Fit height of the input_bar, used as the animation + /// target when the editing pane is being hidden. + #[rust] input_bar_natural_height: f64, } impl Widget for RoomInputBar { @@ -213,19 +216,22 @@ impl Widget for RoomInputBar { // slide=0.0 → editing pane shown → input_bar at zero height. let slide = self.editing_pane(cx, ids!(editing_pane)).slide(); let input_bar = self.view.view(cx, ids!(input_bar)); - if slide < 0.999 { - let input_bar_height = input_bar.area().rect(cx).size.y; - let h = if input_bar_height > 0.0 { - input_bar_height * slide as f64 - } else { - 0.0 - }; + + if slide > 0.999 { + // Editing pane fully hidden: input_bar at natural Fit height. + // Update the cached height for future animations. + let h = input_bar.area().rect(cx).size.y; + if h > 0.0 { + self.input_bar_natural_height = h; + } if let Some(mut inner) = input_bar.borrow_mut() { - inner.walk.height = Size::Fixed(h); + inner.walk.height = Size::fit(); } } else { + // Editing pane visible or animating: shrink/grow input_bar. + let target = self.input_bar_natural_height; if let Some(mut inner) = input_bar.borrow_mut() { - inner.walk.height = Size::fit(); + inner.walk.height = Size::Fixed((target * slide as f64).max(0.0)); } } @@ -456,6 +462,12 @@ impl RoomInputBar { behavior: ShowEditingPaneBehavior, timeline_kind: TimelineKind, ) { + // Cache the input_bar's natural height before the animation shrinks it. + let input_bar_height = self.view.view(cx, ids!(input_bar)).area().rect(cx).size.y; + if input_bar_height > 0.0 { + self.input_bar_natural_height = input_bar_height; + } + // Hide the replying preview and location preview while the editing // pane is shown. The input_bar is not hidden; instead it is slid out // of view in draw_walk using the EditingPane's slide value. From 5dc90c0fa2823278999f2f52df1a9628dad8e616 Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Thu, 2 Apr 2026 18:26:17 -0700 Subject: [PATCH 4/6] fully fix EditingPane + input_bar animation in both directions --- src/room/room_input_bar.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/room/room_input_bar.rs b/src/room/room_input_bar.rs index 07522565e..5ed7e9f29 100644 --- a/src/room/room_input_bar.rs +++ b/src/room/room_input_bar.rs @@ -217,8 +217,12 @@ impl Widget for RoomInputBar { let slide = self.editing_pane(cx, ids!(editing_pane)).slide(); let input_bar = self.view.view(cx, ids!(input_bar)); - if slide > 0.999 { - // Editing pane fully hidden: input_bar at natural Fit height. + // Remap slide through a steeper curve so the input_bar reaches + // its full target height before the ExpDecay tail. + let remapped = (slide as f64 * 1.25).min(1.0); + if remapped >= 1.0 { + // Input_bar has reached its full natural height: switch to Fit + // so it can respond to content changes normally. // Update the cached height for future animations. let h = input_bar.area().rect(cx).size.y; if h > 0.0 { @@ -228,10 +232,9 @@ impl Widget for RoomInputBar { inner.walk.height = Size::fit(); } } else { - // Editing pane visible or animating: shrink/grow input_bar. let target = self.input_bar_natural_height; if let Some(mut inner) = input_bar.borrow_mut() { - inner.walk.height = Size::Fixed((target * slide as f64).max(0.0)); + inner.walk.height = Size::Fixed((target * remapped).max(0.0)); } } From ed447cfa5dc153675f56b943ab00c185e9aca12d Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Thu, 2 Apr 2026 18:29:40 -0700 Subject: [PATCH 5/6] Finalize formatting of EditingPane to match border of RoomInputBar --- src/home/editing_pane.rs | 4 ++-- src/room/room_input_bar.rs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/home/editing_pane.rs b/src/home/editing_pane.rs index 1a218f50f..0cbd92140 100644 --- a/src/home/editing_pane.rs +++ b/src/home/editing_pane.rs @@ -33,8 +33,8 @@ script_mod! { draw_bg +: { color: (COLOR_PRIMARY) border_radius: 5.0 - border_color: #F00 // (COLOR_SECONDARY) - border_size: 5.0 // 2.0 + border_color: (COLOR_SECONDARY) + border_size: 2.0 // shadow_color: #0006 // shadow_radius: 0.0 // shadow_offset: vec2(0.0,0.0) diff --git a/src/room/room_input_bar.rs b/src/room/room_input_bar.rs index 5ed7e9f29..68a9f66c3 100644 --- a/src/room/room_input_bar.rs +++ b/src/room/room_input_bar.rs @@ -42,7 +42,6 @@ script_mod! { // This only works if the border_color is the same as its parents, // which is currently `COLOR_SECONDARY`. margin: Inset{left: -4, right: -4, bottom: -4 } - show_bg: true, draw_bg +: { color: (COLOR_PRIMARY) From 8100cef8c67c0f69f6ee7bc50f46c978dba6ea32 Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Thu, 2 Apr 2026 18:38:58 -0700 Subject: [PATCH 6/6] cleanup and formatting improvements --- src/home/editing_pane.rs | 50 +++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/home/editing_pane.rs b/src/home/editing_pane.rs index 0cbd92140..0816aba5b 100644 --- a/src/home/editing_pane.rs +++ b/src/home/editing_pane.rs @@ -29,6 +29,7 @@ script_mod! { flow: Down, // this must match the RoomInputBar exactly such that it overlaps atop it. + margin: Inset{left: -4, right: -4, bottom: -4 } show_bg: true, draw_bg +: { color: (COLOR_PRIMARY) @@ -167,7 +168,6 @@ impl Widget for EditingPane { // Handle the next-frame event scheduled after hide animation completes. // This forces a full redraw cycle so the parent relayouts properly. if self.next_frame.is_event(event).is_some() { - log!("EditingPane: NextFrame fired, calling redraw_all"); cx.redraw_all(); } @@ -177,9 +177,14 @@ impl Widget for EditingPane { let animator_action = self.animator_handle_event(cx, event); if animator_action.must_redraw() { - // Redraw the entire UI so the parent RoomInputBar can - // animate the input_bar height in its draw_walk. - cx.redraw_all(); + // During hide, redraw the entire UI so the parent RoomInputBar + // can animate the input_bar height in its draw_walk. + // During show, only this widget needs to redraw. + if self.is_animating_out { + cx.redraw_all(); + } else { + self.redraw(cx); + } } // If we started animating the hide, check if the track has finished. // `is_track_animating` returns false once the track has fully completed, @@ -392,20 +397,13 @@ impl Widget for EditingPane { self.visible = false; }; - // Animate both the layout height and the content position: - // 1. walk.height grows from 0 to ch — the RoomInputBar border - // grows smoothly alongside. - // 2. Balanced margins on editing_content slide it up from below - // the clip boundary (show_bg). - // - // When fully shown (slide ~= 0), walk.height is NOT overridden, - // so the pane uses its natural Fit height. This ensures the - // opaque background covers the full content area (and the - // input_bar beneath it in the overlay). - // Slide editing_content within the pane using balanced margins. - // margin.top pushes content below the pane's clip boundary; - // margin.bottom compensates so the Fit height stays constant. - // The pane's show_bg provides clipping. + // Animate both the layout height and content position simultaneously: + // 1. walk.height grows from 0 to ch (and shrinks back during hide), + // so the RoomInputBar border grows/shrinks smoothly. + // 2. Balanced margins on editing_content slide it within the pane: + // margin.top pushes content below the clip boundary, + // margin.bottom compensates so the Fit height stays constant. + // The pane's show_bg provides the clipping. let ch = self.last_content_height; if self.slide > 0.001 { let offset = if ch > 0.0 { @@ -520,13 +518,6 @@ impl EditingPane { timeline_kind, }); - // Reset editing_content to Fit before starting the animation, - // in case a previous hide animation left it at Fixed(ch). - // This ensures the first draw frame can measure the real content height. - if let Some(mut ec) = self.view(cx, ids!(editing_content)).borrow_mut() { - ec.walk.height = Size::fit(); - } - self.visible = true; self.is_animating_out = false; self.button(cx, ids!(accept_button)).reset_hover(cx); @@ -667,7 +658,14 @@ impl EditingPaneRef { inner.animator_cut(cx, ids!(panel.hide)); inner.is_animating_out = false; inner.info = None; - inner.redraw(cx); + // Reset editing_content margins in case we interrupted an animation. + if let Some(mut ec) = inner.view(cx, ids!(editing_content)).borrow_mut() { + ec.walk.margin.top = 0.0; + ec.walk.margin.bottom = 0.0; + } + // Redraw all so the parent RoomInputBar restores the input_bar + // height (its draw_walk reads the slide value, which is now 1.0). + cx.redraw_all(); } }