mirror of
https://github.com/danbulant/despot
synced 2026-06-15 20:41:05 +00:00
first attempt at virtual list
This commit is contained in:
parent
4d8a702380
commit
e51e057d85
5 changed files with 282 additions and 171 deletions
335
Cargo.lock
generated
335
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
23
src/main.rs
23
src/main.rs
|
|
@ -4,10 +4,10 @@ use api::SpotifyContext;
|
|||
use auth::get_token;
|
||||
use clap::Parser;
|
||||
use cli::Args;
|
||||
use cushy::{value::Dynamic, widget::MakeWidget, window::MakeWindow, Application, Open, PendingApp, Run, TokioRuntime};
|
||||
use cushy::{figures::units::Lp, value::Dynamic, widget::MakeWidget, window::MakeWindow, Application, Open, PendingApp, Run, TokioRuntime};
|
||||
use librespot_core::{authentication::Credentials, Session, SessionConfig};
|
||||
use librespot_playback::{audio_backend, config::{AudioFormat, PlayerConfig}, mixer::NoOpVolume, player::Player};
|
||||
use widgets::{library::playlist::playlists_widget, ActivePage};
|
||||
use widgets::{library::playlist::playlists_widget, virtual_list::{virtual_list, VirtualList}, ActivePage};
|
||||
|
||||
mod vibrancy;
|
||||
mod theme;
|
||||
|
|
@ -17,6 +17,21 @@ mod widgets;
|
|||
mod rt;
|
||||
mod api;
|
||||
|
||||
struct TestVirtualList;
|
||||
|
||||
impl VirtualList for TestVirtualList {
|
||||
fn item_count(&self) -> impl cushy::value::IntoDynamic<usize> {
|
||||
Dynamic::new(100)
|
||||
}
|
||||
fn item_height(&self) -> impl cushy::value::IntoDynamic<cushy::styles::Dimension> {
|
||||
Dynamic::new(cushy::styles::Dimension::Lp(Lp::inches_f(0.5)))
|
||||
}
|
||||
fn widget_at(&self, index: usize) -> impl MakeWidget {
|
||||
// println!("Creating item {}", index);
|
||||
format!("Item {}", index)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> cushy::Result {
|
||||
let args = Args::parse();
|
||||
let mut app = PendingApp::new(TokioRuntime::default());
|
||||
|
|
@ -70,6 +85,10 @@ fn main() -> cushy::Result {
|
|||
let selected_page = Dynamic::new(ActivePage::default());
|
||||
|
||||
playlists_widget(playlists.items, selected_page)
|
||||
.and(
|
||||
virtual_list(TestVirtualList)
|
||||
)
|
||||
.into_columns()
|
||||
.make_window()
|
||||
.open(&mut app)
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ use rspotify::model::{SimplifiedAlbum, SimplifiedPlaylist};
|
|||
|
||||
pub mod image;
|
||||
pub mod library;
|
||||
pub mod virtual_list;
|
||||
pub mod probe;
|
||||
|
||||
#[derive(PartialEq, Debug, Default)]
|
||||
pub enum ActivePage {
|
||||
|
|
|
|||
35
src/widgets/probe.rs
Normal file
35
src/widgets/probe.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use cushy::{figures::Fraction, value::{Destination, Dynamic, DynamicReader, IntoReadOnly, ReadOnly}, widget::{MakeWidget, Widget, WidgetRef}};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScalingProbe {
|
||||
child: WidgetRef,
|
||||
scale: Dynamic<Fraction>
|
||||
}
|
||||
|
||||
impl ScalingProbe {
|
||||
pub fn new(child: impl MakeWidget) -> Self {
|
||||
Self {
|
||||
child: WidgetRef::new(child),
|
||||
scale: Dynamic::new(Fraction::new_whole(1))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale(&self) -> DynamicReader<Fraction> {
|
||||
self.scale.create_reader()
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for ScalingProbe {
|
||||
fn redraw(&mut self, context: &mut cushy::context::GraphicsContext<'_, '_, '_, '_>) {
|
||||
self.scale.set(context.gfx.scale());
|
||||
context.for_other(&self.child).expect("A child").redraw();
|
||||
}
|
||||
fn layout(
|
||||
&mut self,
|
||||
available_space: cushy::figures::Size<cushy::ConstraintLimit>,
|
||||
context: &mut cushy::context::LayoutContext<'_, '_, '_, '_>,
|
||||
) -> cushy::figures::Size<cushy::figures::units::UPx> {
|
||||
let child = self.child.mounted(context);
|
||||
context.for_other(&child).layout(available_space)
|
||||
}
|
||||
}
|
||||
58
src/widgets/virtual_list.rs
Normal file
58
src/widgets/virtual_list.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use cushy::{figures::{Round, ScreenScale, Size, Zero}, styles::{Dimension, DimensionRange, Edges}, value::{Destination, Dynamic, ForEach, IntoDynamic, MapEach, Source}, widget::{MakeWidget, WidgetList}, widgets::{Container, Scroll, Stack}};
|
||||
use crate::widgets::probe::ScalingProbe;
|
||||
|
||||
pub trait VirtualList {
|
||||
fn item_height(&self) -> impl IntoDynamic<Dimension>;
|
||||
// fn width(&self) -> impl IntoDynamic<DimensionRange>;
|
||||
fn item_count(&self) -> impl IntoDynamic<usize>;
|
||||
fn widget_at(&self, index: usize) -> impl MakeWidget;
|
||||
}
|
||||
|
||||
pub fn virtual_list<T>(list: T) -> impl MakeWidget
|
||||
where
|
||||
T: VirtualList + Send + 'static
|
||||
{
|
||||
let contents = Dynamic::default();
|
||||
let stack = Stack::rows(contents.clone());
|
||||
let padding = Dynamic::default();
|
||||
let container = Container::new(stack).transparent().pad_by(padding.clone());
|
||||
let scroll = Scroll::vertical(container);
|
||||
|
||||
// Current scroll position
|
||||
let current_scroll = scroll.scroll.clone().map_each(|scroll| scroll.y);
|
||||
// height of the scroll widget
|
||||
let visible_size = scroll.control_size().map_each(|size| size.height);
|
||||
// max scroll position. Height of contents is max_scroll + visible_size
|
||||
// let max_scroll = scroll.max_scroll().map_each(|size| size.y);
|
||||
|
||||
let item_height = list.item_height().into_dynamic();
|
||||
let item_count = list.item_count().into_dynamic();
|
||||
|
||||
let probe = ScalingProbe::new(scroll);
|
||||
let scale = probe.scale();
|
||||
|
||||
// let width = list.width().into_dynamic();
|
||||
|
||||
// (&width, &item_height, &item_count, &scale).map_each(|(width, item_height, item_count, scale)| {
|
||||
// Size::new(*width, *item_height.into_upx(*scale) * *item_count as f32)
|
||||
// });
|
||||
|
||||
let handle = (¤t_scroll, &item_height, &item_count, &scale, &visible_size).for_each(move |(current_scroll, item_height, item_count, scale, visible_size)| {
|
||||
let start = (*current_scroll / item_height.into_upx(*scale)).floor().get();
|
||||
let end = ((*current_scroll + *visible_size) / item_height.into_upx(*scale)).ceil().get().min(*item_count as _);
|
||||
println!("Start: {}, End: {}", start, end);
|
||||
|
||||
let list = (start as usize..end as usize).map(|index| list.widget_at(index));
|
||||
|
||||
let padding_start = *item_height * start as i32;
|
||||
let items_end = (*item_count as u32).saturating_sub(end);
|
||||
let padding_end = *item_height * items_end as i32;
|
||||
|
||||
padding.set(Edges::ZERO.with_top(padding_start).with_bottom(padding_end));
|
||||
|
||||
contents.set(WidgetList::from_iter(list));
|
||||
});
|
||||
handle.persist();
|
||||
|
||||
probe
|
||||
}
|
||||
Loading…
Reference in a new issue