Store states of the sidebar and menu, hide and recover together on single center tap events.
This commit is contained in:
parent
6b11beff44
commit
97d2d6bd75
|
|
@ -2,6 +2,7 @@ use std::path::PathBuf;
|
|||
use std::sync::Arc;
|
||||
|
||||
use iced_wgpu::core::{Element, Theme};
|
||||
use iced_widget::scrollable::AbsoluteOffset;
|
||||
|
||||
use crate::analyzer::FrameData;
|
||||
use crate::analyzer_worker::AnalyzerWorker;
|
||||
|
|
@ -39,6 +40,21 @@ pub struct App {
|
|||
|
||||
/// modal status message shown over the UI while waiting on iOS file-coordinator caching.
|
||||
pub coordinating_message: Option<String>,
|
||||
|
||||
/// sidebar scroll offset mirrored from the on_scroll callback.
|
||||
pub sidebar_scroll: AbsoluteOffset,
|
||||
|
||||
/// settings panel scroll offset mirrored from the on_scroll callback.
|
||||
pub settings_scroll: AbsoluteOffset,
|
||||
|
||||
/// pending sidebar scroll restore against the live scrollable widget.
|
||||
pub restore_sidebar_scroll: bool,
|
||||
|
||||
/// pending settings scroll restore against the live scrollable widget.
|
||||
pub restore_settings_scroll: bool,
|
||||
|
||||
/// show_settings copy bridging the middle-tap collapse cycle.
|
||||
pub saved_show_settings: bool,
|
||||
}
|
||||
|
||||
/// every visualizer toggle, slider value, and DSP parameter the settings panel exposes.
|
||||
|
|
@ -106,7 +122,10 @@ pub enum Message {
|
|||
|
||||
Seek(f32),
|
||||
ToggleImmersive,
|
||||
ToggleChrome,
|
||||
ToggleSettings,
|
||||
SidebarScrolled(AbsoluteOffset),
|
||||
SettingsScrolled(AbsoluteOffset),
|
||||
SetGlass(bool),
|
||||
SetEntropy(bool),
|
||||
SetAlbumColors(bool),
|
||||
|
|
@ -158,6 +177,11 @@ impl App {
|
|||
track_loading: false,
|
||||
library_progress: None,
|
||||
coordinating_message: None,
|
||||
sidebar_scroll: AbsoluteOffset::default(),
|
||||
settings_scroll: AbsoluteOffset::default(),
|
||||
restore_sidebar_scroll: false,
|
||||
restore_settings_scroll: false,
|
||||
saved_show_settings: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -552,7 +576,28 @@ impl App {
|
|||
}
|
||||
}
|
||||
Message::ToggleImmersive => self.immersive = !self.immersive,
|
||||
Message::ToggleSettings => self.show_settings = !self.show_settings,
|
||||
Message::ToggleChrome => {
|
||||
if self.immersive {
|
||||
self.immersive = false;
|
||||
self.restore_sidebar_scroll = true;
|
||||
if self.saved_show_settings {
|
||||
self.show_settings = true;
|
||||
self.restore_settings_scroll = true;
|
||||
}
|
||||
} else {
|
||||
self.saved_show_settings = self.show_settings;
|
||||
self.show_settings = false;
|
||||
self.immersive = true;
|
||||
}
|
||||
}
|
||||
Message::ToggleSettings => {
|
||||
self.show_settings = !self.show_settings;
|
||||
if self.show_settings {
|
||||
self.restore_settings_scroll = true;
|
||||
}
|
||||
}
|
||||
Message::SidebarScrolled(off) => self.sidebar_scroll = off,
|
||||
Message::SettingsScrolled(off) => self.settings_scroll = off,
|
||||
Message::SetGlass(on) => self.settings.glass = on,
|
||||
Message::SetEntropy(on) => self.settings.entropy_on = on,
|
||||
Message::SetAlbumColors(on) => self.settings.album_colors = on,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::ops::RangeInclusive;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use iced_wgpu::core::widget::Id as WidgetId;
|
||||
use iced_wgpu::core::{Background, Border, Color, Element, Length, Padding, Theme};
|
||||
use iced_widget::{
|
||||
button::{self, Status as ButtonStatus},
|
||||
|
|
@ -30,6 +31,16 @@ pub const TRANSPORT_H: f32 = 72.0;
|
|||
const ROW_H: f32 = 56.0;
|
||||
const THUMB: f32 = 40.0;
|
||||
|
||||
/// stable id of the sidebar scrollable.
|
||||
pub fn sidebar_scroll_id() -> WidgetId {
|
||||
WidgetId::new("yrx-sidebar-scroll")
|
||||
}
|
||||
|
||||
/// stable id of the settings-panel scrollable.
|
||||
pub fn settings_scroll_id() -> WidgetId {
|
||||
WidgetId::new("yrx-settings-scroll")
|
||||
}
|
||||
|
||||
/// assembles the top bar, sidebar, transport, and visualizer into the active layout.
|
||||
pub fn view(app: &App) -> Element<'_, Message, Theme, iced_wgpu::Renderer> {
|
||||
let body: Element<'_, Message, Theme, iced_wgpu::Renderer> = if app.immersive {
|
||||
|
|
@ -173,7 +184,10 @@ fn sidebar(app: &App) -> Element<'_, Message, Theme, iced_wgpu::Renderer> {
|
|||
for (i, t) in tracks.iter().enumerate() {
|
||||
col = col.push(track_row_owned(i, t.clone(), selected == Some(i)));
|
||||
}
|
||||
scrollable(col).height(Length::Fill)
|
||||
scrollable(col)
|
||||
.id(sidebar_scroll_id())
|
||||
.on_scroll(|vp| Message::SidebarScrolled(vp.absolute_offset()))
|
||||
.height(Length::Fill)
|
||||
});
|
||||
|
||||
container(inner)
|
||||
|
|
@ -303,7 +317,7 @@ fn visualiser(app: &App) -> Element<'_, Message, Theme, iced_wgpu::Renderer> {
|
|||
..Default::default()
|
||||
});
|
||||
|
||||
mouse_area(bordered).on_press(Message::ToggleImmersive).into()
|
||||
mouse_area(bordered).on_press(Message::ToggleChrome).into()
|
||||
}
|
||||
|
||||
/// animated cog and label overlay shown during track decode.
|
||||
|
|
@ -392,15 +406,9 @@ pub const SETTINGS_W: f32 = 340.0;
|
|||
fn settings_overlay(app: &App) -> Element<'_, Message, Theme, iced_wgpu::Renderer> {
|
||||
let s = &app.settings;
|
||||
|
||||
let header = row![
|
||||
text("Settings").size(15).color(palette::text()),
|
||||
Space::new().width(Length::Fill),
|
||||
chip_button("Close", Message::ToggleSettings),
|
||||
]
|
||||
.align_y(iced_wgpu::core::Alignment::Center);
|
||||
|
||||
let body = column![
|
||||
header,
|
||||
Space::new().height(Length::Fixed(TOP_BAR_H + 4.0)),
|
||||
text("Settings").size(15).color(palette::text()),
|
||||
Space::new().height(Length::Fixed(10.0)),
|
||||
section_label("style"),
|
||||
toggle_row("glass", s.glass, Message::SetGlass),
|
||||
|
|
@ -504,9 +512,19 @@ fn settings_overlay(app: &App) -> Element<'_, Message, Theme, iced_wgpu::Rendere
|
|||
.padding(Padding::from(16))
|
||||
.width(Length::Fixed(SETTINGS_W));
|
||||
|
||||
let scroll = scrollable(body).height(Length::Fill);
|
||||
let scroll = scrollable(body)
|
||||
.id(settings_scroll_id())
|
||||
.on_scroll(|vp| Message::SettingsScrolled(vp.absolute_offset()))
|
||||
.height(Length::Fill);
|
||||
|
||||
let panel = container(scroll)
|
||||
let close = container(icon_chip_button(SETTINGS_SVG, Message::ToggleSettings))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(TOP_BAR_H))
|
||||
.padding(Padding::from([0, 16]))
|
||||
.align_x(iced_wgpu::core::alignment::Horizontal::Right)
|
||||
.align_y(iced_wgpu::core::alignment::Vertical::Center);
|
||||
|
||||
let panel = container(stack![scroll, close])
|
||||
.width(Length::Fixed(SETTINGS_W))
|
||||
.height(Length::Fill)
|
||||
.style(settings_panel_style);
|
||||
|
|
|
|||
|
|
@ -4,14 +4,16 @@ use iced_graphics::{Shell as GShell, Viewport};
|
|||
use iced_runtime::user_interface::{self, UserInterface};
|
||||
use iced_wgpu::core::renderer::Style;
|
||||
use iced_wgpu::core::time::Instant;
|
||||
use iced_wgpu::core::widget::operation::scrollable as scrollable_op;
|
||||
use iced_wgpu::core::{
|
||||
clipboard, keyboard, mouse, window, Color, Event, Font, Pixels, Point, Size, SmolStr,
|
||||
Theme,
|
||||
};
|
||||
use iced_wgpu::Engine;
|
||||
use iced_widget::scrollable::AbsoluteOffset;
|
||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
||||
|
||||
use crate::ui::{theme, App, Message};
|
||||
use crate::ui::{player, theme, App, Message};
|
||||
|
||||
/// per-window bundle of the wgpu surface, iced renderer, App state, and pending input event queue.
|
||||
pub struct ViewportHandle {
|
||||
|
|
@ -534,6 +536,8 @@ fn render(handle: &mut ViewportHandle) {
|
|||
.events
|
||||
.push(Event::Window(window::Event::RedrawRequested(Instant::now())));
|
||||
|
||||
let pre_restore = take_pending_scroll_restores(&mut handle.state);
|
||||
|
||||
let cache = std::mem::take(&mut handle.cache);
|
||||
let mut ui = UserInterface::build(
|
||||
handle.state.view(),
|
||||
|
|
@ -559,6 +563,7 @@ fn render(handle: &mut ViewportHandle) {
|
|||
};
|
||||
|
||||
if messages.is_empty() {
|
||||
apply_scroll_restores(&mut ui, &handle.renderer, pre_restore);
|
||||
ui.draw(&mut handle.renderer, &theme, &style, handle.cursor);
|
||||
handle.cache = ui.into_cache();
|
||||
} else {
|
||||
|
|
@ -566,12 +571,18 @@ fn render(handle: &mut ViewportHandle) {
|
|||
for msg in messages.drain(..) {
|
||||
handle.state.update(msg);
|
||||
}
|
||||
let post_restore = take_pending_scroll_restores(&mut handle.state);
|
||||
let combined = ScrollRestore {
|
||||
sidebar: post_restore.sidebar.or(pre_restore.sidebar),
|
||||
settings: post_restore.settings.or(pre_restore.settings),
|
||||
};
|
||||
let mut ui = UserInterface::build(
|
||||
handle.state.view(),
|
||||
Size::new(logical_size.width, logical_size.height),
|
||||
cache,
|
||||
&mut handle.renderer,
|
||||
);
|
||||
apply_scroll_restores(&mut ui, &handle.renderer, combined);
|
||||
ui.draw(&mut handle.renderer, &theme, &style, handle.cursor);
|
||||
handle.cache = ui.into_cache();
|
||||
}
|
||||
|
|
@ -583,3 +594,42 @@ fn render(handle: &mut ViewportHandle) {
|
|||
frame.present();
|
||||
handle.needs_redraw = false;
|
||||
}
|
||||
|
||||
/// drains scroll-restore flags from App state into a side-channel struct.
|
||||
fn take_pending_scroll_restores(state: &mut App) -> ScrollRestore {
|
||||
let r = ScrollRestore {
|
||||
sidebar: state.restore_sidebar_scroll.then_some(state.sidebar_scroll),
|
||||
settings: state.restore_settings_scroll.then_some(state.settings_scroll),
|
||||
};
|
||||
state.restore_sidebar_scroll = false;
|
||||
state.restore_settings_scroll = false;
|
||||
r
|
||||
}
|
||||
|
||||
/// pushes the stored sidebar and settings scroll offsets onto the UserInterface.
|
||||
fn apply_scroll_restores(
|
||||
ui: &mut UserInterface<'_, Message, Theme, iced_wgpu::Renderer>,
|
||||
renderer: &iced_wgpu::Renderer,
|
||||
restore: ScrollRestore,
|
||||
) {
|
||||
if let Some(off) = restore.sidebar {
|
||||
let mut op = scrollable_op::scroll_to::<()>(
|
||||
player::sidebar_scroll_id(),
|
||||
AbsoluteOffset { x: Some(off.x), y: Some(off.y) },
|
||||
);
|
||||
ui.operate(renderer, &mut op);
|
||||
}
|
||||
if let Some(off) = restore.settings {
|
||||
let mut op = scrollable_op::scroll_to::<()>(
|
||||
player::settings_scroll_id(),
|
||||
AbsoluteOffset { x: Some(off.x), y: Some(off.y) },
|
||||
);
|
||||
ui.operate(renderer, &mut op);
|
||||
}
|
||||
}
|
||||
|
||||
/// scroll offsets staged for the UserInterface::operate pass.
|
||||
struct ScrollRestore {
|
||||
sidebar: Option<AbsoluteOffset>,
|
||||
settings: Option<AbsoluteOffset>,
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue