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 std::sync::Arc;
|
||||||
|
|
||||||
use iced_wgpu::core::{Element, Theme};
|
use iced_wgpu::core::{Element, Theme};
|
||||||
|
use iced_widget::scrollable::AbsoluteOffset;
|
||||||
|
|
||||||
use crate::analyzer::FrameData;
|
use crate::analyzer::FrameData;
|
||||||
use crate::analyzer_worker::AnalyzerWorker;
|
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.
|
/// modal status message shown over the UI while waiting on iOS file-coordinator caching.
|
||||||
pub coordinating_message: Option<String>,
|
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.
|
/// every visualizer toggle, slider value, and DSP parameter the settings panel exposes.
|
||||||
|
|
@ -106,7 +122,10 @@ pub enum Message {
|
||||||
|
|
||||||
Seek(f32),
|
Seek(f32),
|
||||||
ToggleImmersive,
|
ToggleImmersive,
|
||||||
|
ToggleChrome,
|
||||||
ToggleSettings,
|
ToggleSettings,
|
||||||
|
SidebarScrolled(AbsoluteOffset),
|
||||||
|
SettingsScrolled(AbsoluteOffset),
|
||||||
SetGlass(bool),
|
SetGlass(bool),
|
||||||
SetEntropy(bool),
|
SetEntropy(bool),
|
||||||
SetAlbumColors(bool),
|
SetAlbumColors(bool),
|
||||||
|
|
@ -158,6 +177,11 @@ impl App {
|
||||||
track_loading: false,
|
track_loading: false,
|
||||||
library_progress: None,
|
library_progress: None,
|
||||||
coordinating_message: 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::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::SetGlass(on) => self.settings.glass = on,
|
||||||
Message::SetEntropy(on) => self.settings.entropy_on = on,
|
Message::SetEntropy(on) => self.settings.entropy_on = on,
|
||||||
Message::SetAlbumColors(on) => self.settings.album_colors = on,
|
Message::SetAlbumColors(on) => self.settings.album_colors = on,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
use std::path::PathBuf;
|
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_wgpu::core::{Background, Border, Color, Element, Length, Padding, Theme};
|
||||||
use iced_widget::{
|
use iced_widget::{
|
||||||
button::{self, Status as ButtonStatus},
|
button::{self, Status as ButtonStatus},
|
||||||
|
|
@ -30,6 +31,16 @@ pub const TRANSPORT_H: f32 = 72.0;
|
||||||
const ROW_H: f32 = 56.0;
|
const ROW_H: f32 = 56.0;
|
||||||
const THUMB: f32 = 40.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.
|
/// assembles the top bar, sidebar, transport, and visualizer into the active layout.
|
||||||
pub fn view(app: &App) -> Element<'_, Message, Theme, iced_wgpu::Renderer> {
|
pub fn view(app: &App) -> Element<'_, Message, Theme, iced_wgpu::Renderer> {
|
||||||
let body: Element<'_, Message, Theme, iced_wgpu::Renderer> = if app.immersive {
|
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() {
|
for (i, t) in tracks.iter().enumerate() {
|
||||||
col = col.push(track_row_owned(i, t.clone(), selected == Some(i)));
|
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)
|
container(inner)
|
||||||
|
|
@ -303,7 +317,7 @@ fn visualiser(app: &App) -> Element<'_, Message, Theme, iced_wgpu::Renderer> {
|
||||||
..Default::default()
|
..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.
|
/// 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> {
|
fn settings_overlay(app: &App) -> Element<'_, Message, Theme, iced_wgpu::Renderer> {
|
||||||
let s = &app.settings;
|
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![
|
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)),
|
Space::new().height(Length::Fixed(10.0)),
|
||||||
section_label("style"),
|
section_label("style"),
|
||||||
toggle_row("glass", s.glass, Message::SetGlass),
|
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))
|
.padding(Padding::from(16))
|
||||||
.width(Length::Fixed(SETTINGS_W));
|
.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))
|
.width(Length::Fixed(SETTINGS_W))
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.style(settings_panel_style);
|
.style(settings_panel_style);
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,16 @@ use iced_graphics::{Shell as GShell, Viewport};
|
||||||
use iced_runtime::user_interface::{self, UserInterface};
|
use iced_runtime::user_interface::{self, UserInterface};
|
||||||
use iced_wgpu::core::renderer::Style;
|
use iced_wgpu::core::renderer::Style;
|
||||||
use iced_wgpu::core::time::Instant;
|
use iced_wgpu::core::time::Instant;
|
||||||
|
use iced_wgpu::core::widget::operation::scrollable as scrollable_op;
|
||||||
use iced_wgpu::core::{
|
use iced_wgpu::core::{
|
||||||
clipboard, keyboard, mouse, window, Color, Event, Font, Pixels, Point, Size, SmolStr,
|
clipboard, keyboard, mouse, window, Color, Event, Font, Pixels, Point, Size, SmolStr,
|
||||||
Theme,
|
Theme,
|
||||||
};
|
};
|
||||||
use iced_wgpu::Engine;
|
use iced_wgpu::Engine;
|
||||||
|
use iced_widget::scrollable::AbsoluteOffset;
|
||||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
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.
|
/// per-window bundle of the wgpu surface, iced renderer, App state, and pending input event queue.
|
||||||
pub struct ViewportHandle {
|
pub struct ViewportHandle {
|
||||||
|
|
@ -534,6 +536,8 @@ fn render(handle: &mut ViewportHandle) {
|
||||||
.events
|
.events
|
||||||
.push(Event::Window(window::Event::RedrawRequested(Instant::now())));
|
.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 cache = std::mem::take(&mut handle.cache);
|
||||||
let mut ui = UserInterface::build(
|
let mut ui = UserInterface::build(
|
||||||
handle.state.view(),
|
handle.state.view(),
|
||||||
|
|
@ -559,6 +563,7 @@ fn render(handle: &mut ViewportHandle) {
|
||||||
};
|
};
|
||||||
|
|
||||||
if messages.is_empty() {
|
if messages.is_empty() {
|
||||||
|
apply_scroll_restores(&mut ui, &handle.renderer, pre_restore);
|
||||||
ui.draw(&mut handle.renderer, &theme, &style, handle.cursor);
|
ui.draw(&mut handle.renderer, &theme, &style, handle.cursor);
|
||||||
handle.cache = ui.into_cache();
|
handle.cache = ui.into_cache();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -566,12 +571,18 @@ fn render(handle: &mut ViewportHandle) {
|
||||||
for msg in messages.drain(..) {
|
for msg in messages.drain(..) {
|
||||||
handle.state.update(msg);
|
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(
|
let mut ui = UserInterface::build(
|
||||||
handle.state.view(),
|
handle.state.view(),
|
||||||
Size::new(logical_size.width, logical_size.height),
|
Size::new(logical_size.width, logical_size.height),
|
||||||
cache,
|
cache,
|
||||||
&mut handle.renderer,
|
&mut handle.renderer,
|
||||||
);
|
);
|
||||||
|
apply_scroll_restores(&mut ui, &handle.renderer, combined);
|
||||||
ui.draw(&mut handle.renderer, &theme, &style, handle.cursor);
|
ui.draw(&mut handle.renderer, &theme, &style, handle.cursor);
|
||||||
handle.cache = ui.into_cache();
|
handle.cache = ui.into_cache();
|
||||||
}
|
}
|
||||||
|
|
@ -583,3 +594,42 @@ fn render(handle: &mut ViewportHandle) {
|
||||||
frame.present();
|
frame.present();
|
||||||
handle.needs_redraw = false;
|
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