From 7b288167d76fcb7fe1be5c00b15eab388e88e086 Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Sat, 13 Jan 2024 19:02:35 +0000 Subject: [PATCH] Scroll to top before closing search bar --- Cargo.lock | 1 + iced | 2 +- shalom/src/magic/header_search.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++------------------------------ shalom/src/pages/room.rs | 30 ++++++++++++++++++++++++++++-- shalom/src/pages/room/listen.rs | 8 ++++---- 5 files changed, 79 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a57cfa..16ababb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1394,6 +1394,7 @@ dependencies = [ "iced_renderer", "iced_runtime", "iced_style", + "keyframe", "num-traits", "ouroboros", "thiserror", diff --git a/iced b/iced index c30197d..f8a73b7 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit c30197d3c7e46dde933f6839662023222f07d30a +Subproject commit f8a73b7de909d72a90f9908b61fbdde88e0debcb diff --git a/shalom/src/magic/header_search.rs b/shalom/src/magic/header_search.rs index d0aae8d..094dede 100644 --- a/shalom/src/magic/header_search.rs +++ b/shalom/src/magic/header_search.rs @@ -27,7 +27,7 @@ const INITIAL_SEARCH_BOX_SIZE: Size = Size::new(78., 78.); pub fn header_search<'a, M>( on_input: fn(String) -> M, - on_state_change: fn(bool) -> M, + open_state_toggle: M, open: bool, search_query: &str, mut header: Text<'a, Renderer>, @@ -49,12 +49,13 @@ where original_header: header.clone(), header, current_search_box_size, + open, + open_state_toggle, input: iced::widget::text_input("Search...", search_query) .id(Id::unique()) .on_input(on_input) .style(iced::theme::TextInput::Custom(Box::new(InputStyle))) .into(), - on_state_change, search_icon: Element::from(Icon::Search.canvas(Color::BLACK)), close_icon: Element::from(Icon::Close.canvas(Color::BLACK)), dy_mult, @@ -72,11 +73,12 @@ pub struct HeaderSearch<'a, M> { original_header: Text<'a, Renderer>, header: Text<'a, Renderer>, current_search_box_size: BoxSize, - on_state_change: fn(bool) -> M, input: Element<'a, M, Renderer>, search_icon: Element<'a, M, Renderer>, close_icon: Element<'a, M, Renderer>, dy_mult: f32, + open: bool, + open_state_toggle: M, } impl<'a, M> Widget for HeaderSearch<'a, M> @@ -219,9 +221,10 @@ where } } + #[allow(clippy::too_many_lines)] fn on_event( &mut self, - state: &mut Tree, + tree_state: &mut Tree, event: iced::Event, layout: Layout<'_>, cursor: Cursor, @@ -230,6 +233,20 @@ where shell: &mut Shell<'_, M>, viewport: &Rectangle, ) -> Status { + let state = tree_state.state.downcast_mut::(); + + match state { + State::Open if !self.open => { + *state = State::close(); + shell.request_redraw(RedrawRequest::NextFrame); + } + State::Closed if self.open => { + *state = State::open(); + shell.request_redraw(RedrawRequest::NextFrame); + } + _ => {} + } + let status = match event { iced::Event::Mouse(iced::mouse::Event::ButtonPressed(mouse::Button::Left)) | iced::Event::Touch(iced::touch::Event::FingerPressed { .. }) @@ -244,13 +261,13 @@ where .bounds(), ) => { - let state = state.state.downcast_mut::(); - *state = state.clone().flip(); - shell.request_redraw(RedrawRequest::NextFrame); + if !matches!(state, State::Animate { .. }) { + shell.publish(self.open_state_toggle.clone()); + } + Status::Captured } iced::Event::Window(iced::window::Event::RedrawRequested(_)) => { - let state = state.state.downcast_mut::(); let State::Animate { last_draw, next_state, @@ -301,11 +318,9 @@ where match &state { State::Open => { - shell.publish((self.on_state_change)(true)); self.current_search_box_size = BoxSize::Fill; } State::Closed => { - shell.publish((self.on_state_change)(false)); self.current_search_box_size = BoxSize::Min; } State::Animate { .. } => {} @@ -321,7 +336,7 @@ where if status == Status::Ignored { self.input.as_widget_mut().on_event( - &mut state.children[0], + &mut tree_state.children[0], event, layout.children().nth(1).unwrap().children().nth(1).unwrap(), cursor, @@ -396,25 +411,25 @@ pub enum State { } impl State { - fn flip(self) -> Self { - match self { - State::Closed => Self::Animate { - last_draw: Instant::now(), - next_state: Box::new(State::Open), - text_opacity: keyframes![(1.0, 0.0, EaseOutQuint), (0.0, 0.5)], - search_box_size: keyframes![(0.0, 0.0, EaseOutQuint), (0.0, 0.1), (1.0, 0.5)], - search_icon: keyframes![(1.0, 0.0, EaseOutQuint), (0.0, 0.5)], - close_icon: keyframes![(0.0, 0.0, EaseOutQuint), (0.0, 0.1), (1.0, 0.5)], - }, - State::Open => Self::Animate { - last_draw: Instant::now(), - next_state: Box::new(State::Closed), - text_opacity: keyframes![(0.0, 0.0, EaseOutQuint), (0.0, 0.1), (1.0, 0.5)], - search_box_size: keyframes![(1.0, 0.0, EaseOutQuint), (0.0, 0.5)], - search_icon: keyframes![(0.0, 0.0, EaseOutQuint), (0.0, 0.1), (1.0, 0.5)], - close_icon: keyframes![(1.0, 0.0, EaseOutQuint), (0.0, 0.5)], - }, - v @ State::Animate { .. } => v, + fn open() -> Self { + Self::Animate { + last_draw: Instant::now(), + next_state: Box::new(State::Open), + text_opacity: keyframes![(1.0, 0.0, EaseOutQuint), (0.0, 0.5)], + search_box_size: keyframes![(0.0, 0.0, EaseOutQuint), (0.0, 0.1), (1.0, 0.5)], + search_icon: keyframes![(1.0, 0.0, EaseOutQuint), (0.0, 0.5)], + close_icon: keyframes![(0.0, 0.0, EaseOutQuint), (0.0, 0.1), (1.0, 0.5)], + } + } + + fn close() -> Self { + Self::Animate { + last_draw: Instant::now(), + next_state: Box::new(State::Closed), + text_opacity: keyframes![(0.0, 0.0, EaseOutQuint), (0.0, 0.1), (1.0, 0.5)], + search_box_size: keyframes![(1.0, 0.0, EaseOutQuint), (0.0, 0.5)], + search_icon: keyframes![(0.0, 0.0, EaseOutQuint), (0.0, 0.1), (1.0, 0.5)], + close_icon: keyframes![(1.0, 0.0, EaseOutQuint), (0.0, 0.5)], } } } diff --git a/shalom/src/pages/room.rs b/shalom/src/pages/room.rs index 71389c3..f80add8 100644 --- a/shalom/src/pages/room.rs +++ b/shalom/src/pages/room.rs @@ -36,6 +36,7 @@ pub struct Room { listen: listen::Listen, current_page: Page, dy: f32, + pending_visible_toggle: bool, } impl Room { @@ -49,6 +50,7 @@ impl Room { room, current_page: Page::Listen, dy: 0.0, + pending_visible_toggle: false, } } @@ -59,16 +61,38 @@ impl Room { pub fn update(&mut self, event: Message) -> Option { match event { Message::Lights(v) => self.lights.update(v).map(Event::Lights), + Message::Listen(listen::Message::OnSearchVisibleToggle) + if self.listen.search.is_open() && self.dy > 0.0 => + { + // intercept search toggles on listen so we can scroll our scrollable to + // the top first + self.pending_visible_toggle = true; + None + } Message::Listen(v) => self.listen.update(v).map(Event::Listen), Message::ChangePage(page) => { + self.dy = 0.0; self.current_page = page; None } - Message::Exit => Some(Event::Exit), + Message::Exit => { + self.dy = 0.0; + Some(Event::Exit) + } Message::OnContentScroll(viewport) => { self.dy = viewport.absolute_offset().y; None } + Message::OnContentAnimateFinished => { + if self.pending_visible_toggle { + self.pending_visible_toggle = false; + self.listen + .update(listen::Message::OnSearchVisibleToggle) + .map(Event::Listen) + } else { + None + } + } } } @@ -120,7 +144,6 @@ impl Room { .spacing(20.0 * (1.0 - padding_mult)) .push(header); - // TODO: on close, we need to animate the scrollback by padding the container up to dy if needs_scrollable { current = scrollable(container(current).width(Length::Fill).padding([ f32::from(PADDING + 30) * padding_mult, @@ -132,6 +155,8 @@ impl Room { Properties::default().scroller_width(0).width(0), )) .on_scroll(Message::OnContentScroll) + .on_animate_finished(Message::OnContentAnimateFinished) + .scroll_to_top(self.pending_visible_toggle) .into(); } @@ -183,5 +208,6 @@ pub enum Message { Listen(listen::Message), ChangePage(Page), OnContentScroll(Viewport), + OnContentAnimateFinished, Exit, } diff --git a/shalom/src/pages/room/listen.rs b/shalom/src/pages/room/listen.rs index 44016f0..0d3df6e 100644 --- a/shalom/src/pages/room/listen.rs +++ b/shalom/src/pages/room/listen.rs @@ -69,7 +69,7 @@ impl Listen { header_search( Message::OnSearchTerm, - Message::OnSearchVisibleChange, + Message::OnSearchVisibleToggle, open, query, text.clone(), @@ -180,8 +180,8 @@ impl Listen { self.search = self.search.open(v); None } - Message::OnSearchVisibleChange(v) => { - self.search = if v { + Message::OnSearchVisibleToggle => { + self.search = if matches!(self.search, SearchState::Closed) { SearchState::Open { search: String::new(), results_search: String::new(), @@ -421,7 +421,7 @@ pub enum Message { OnSpeakerNextTrack, OnSpeakerPreviousTrack, OnSearchTerm(String), - OnSearchVisibleChange(bool), + OnSearchVisibleToggle, SpotifySearchResult((SearchResult, String)), SpotifySearchResultError((String, String)), OnPlayTrack(String), -- libgit2 1.7.2