mirror of
https://github.com/danbulant/cushy
synced 2026-06-19 14:31:04 +00:00
Add a simple example about reading and synchronizing values from a Scroll
This commit is contained in:
parent
5b928327dc
commit
e3898bda50
4 changed files with 118 additions and 25 deletions
62
examples/virtual-scroll-list.rs
Normal file
62
examples/virtual-scroll-list.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
use cushy::styles::{Dimension, DimensionRange, Edges};
|
||||
use cushy::value::{Destination, Dynamic, Source};
|
||||
use cushy::widget::MakeWidget;
|
||||
use cushy::Run;
|
||||
use figures::units::{Lp, Px};
|
||||
use figures::{Point, Size};
|
||||
|
||||
fn list() -> impl MakeWidget {
|
||||
let height = Lp::inches(10);
|
||||
let content_size: Dynamic<Size<Px>> = Dynamic::default();
|
||||
let control_size = Dynamic::default();
|
||||
let current_scroll: Dynamic<Point<Px>> = Dynamic::default();
|
||||
let max_scroll = Dynamic::default();
|
||||
|
||||
let content = content_size
|
||||
.map_each(|s| format!("Content size: {:?};", s));
|
||||
let control = control_size
|
||||
.map_each(|s| format!("Control size: {:?};", s));
|
||||
let scroll = current_scroll
|
||||
.map_each(|s| format!("Current scroll: {:?};", s));
|
||||
let max = max_scroll
|
||||
.map_each(|s| format!("Max scroll: {:?};", s));
|
||||
|
||||
let content = content
|
||||
.and(control)
|
||||
.and(scroll)
|
||||
.and(max)
|
||||
.into_columns()
|
||||
.and("Hello world!")
|
||||
.into_rows()
|
||||
.pad_by(current_scroll.map_each(|scroll| Edges {
|
||||
top: Dimension::from(-scroll.y),
|
||||
..Default::default()
|
||||
}))
|
||||
.size(Size::new(DimensionRange::default(), DimensionRange::from(height)));
|
||||
|
||||
let scroll = content.scroll();
|
||||
|
||||
scroll.get_content_size()
|
||||
.for_each_cloned(move |s| content_size.set(s))
|
||||
.persist();
|
||||
scroll.get_control_size()
|
||||
.for_each_cloned(move |s| control_size.set(s))
|
||||
.persist();
|
||||
scroll.get_scroll()
|
||||
.for_each_cloned(move |s| current_scroll.set(s))
|
||||
.persist();
|
||||
scroll.get_max_scroll()
|
||||
.for_each_cloned(move |s| max_scroll.set(s))
|
||||
.persist();
|
||||
|
||||
scroll.expand()
|
||||
}
|
||||
|
||||
fn main() -> cushy::Result {
|
||||
list().run()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runs() {
|
||||
cushy::example!(list).untested_still_frame();
|
||||
}
|
||||
|
|
@ -63,7 +63,8 @@ change. Let's revisit the example from the [intro](../intro.md):
|
|||
Both the [`Input`][input] and the [`Label`][label] widgets have been given
|
||||
instances of `Dynamic<String>`s, but they are two different dynamics. The text
|
||||
input field was given the dynamic we want to be edited. We react to the changes
|
||||
through the `name.map_each(...)` callback.
|
||||
through the `name.map_each(...)` callback. You can react to multiple `Dynamic`s
|
||||
at once using `(&name, &surname).map_each(...)` callback.
|
||||
|
||||
## What is a `DynamicReader<T>`?
|
||||
|
||||
|
|
|
|||
|
|
@ -1212,7 +1212,7 @@ impl NamedComponent for Cow<'_, ComponentName> {
|
|||
}
|
||||
|
||||
/// A type describing characteristics about the edges of a rectangle.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Edges<T = FlexibleDimension> {
|
||||
/// The left edge
|
||||
pub left: T,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use crate::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn, Zer
|
|||
use crate::context::{AsEventContext, EventContext, LayoutContext};
|
||||
use crate::styles::components::{EasingIn, EasingOut, LineHeight};
|
||||
use crate::styles::Dimension;
|
||||
use crate::value::{Destination, Dynamic, IntoValue, Source, Value};
|
||||
use crate::value::{Destination, Dynamic, DynamicReader, IntoValue, Source, Value};
|
||||
use crate::widget::{EventHandling, MakeWidget, Widget, WidgetRef, HANDLED, IGNORED};
|
||||
use crate::window::DeviceId;
|
||||
use crate::ConstraintLimit;
|
||||
|
|
@ -22,9 +22,9 @@ use crate::ConstraintLimit;
|
|||
#[derive(Debug)]
|
||||
pub struct Scroll {
|
||||
contents: WidgetRef,
|
||||
content_size: Size<Px>,
|
||||
control_size: Size<Px>,
|
||||
scroll: Dynamic<Point<Px>>,
|
||||
content_size: Dynamic<Size<Px>>,
|
||||
control_size: Dynamic<Size<Px>>,
|
||||
pub scroll: Dynamic<Point<Px>>,
|
||||
enabled: Point<bool>,
|
||||
preserve_max_scroll: Value<bool>,
|
||||
max_scroll: Dynamic<Point<Px>>,
|
||||
|
|
@ -50,8 +50,8 @@ impl Scroll {
|
|||
Self {
|
||||
contents: WidgetRef::new(contents),
|
||||
enabled,
|
||||
content_size: Size::default(),
|
||||
control_size: Size::default(),
|
||||
content_size: Dynamic::new(Size::default()),
|
||||
control_size: Dynamic::new(Size::default()),
|
||||
scroll: Dynamic::new(Point::default()),
|
||||
max_scroll: Dynamic::new(Point::default()),
|
||||
scrollbar_opacity: Dynamic::default(),
|
||||
|
|
@ -98,6 +98,30 @@ impl Scroll {
|
|||
self
|
||||
}
|
||||
|
||||
/// Creates a reader for the scroll value.
|
||||
pub fn get_scroll(&self) -> DynamicReader<Point<Px>> {
|
||||
self.scroll.create_reader()
|
||||
}
|
||||
|
||||
/// Creates a reader for the maximum scroll value.
|
||||
/// This represents the maximum amount that the scroll can be moved by.
|
||||
/// This should usually mean that this value + the scroll visual size is the size of the content.
|
||||
pub fn get_max_scroll(&self) -> DynamicReader<Point<Px>> {
|
||||
self.max_scroll.create_reader()
|
||||
}
|
||||
|
||||
/// Creates a reader for the content size.
|
||||
/// This is the size of the content that is being scrolled.
|
||||
pub fn get_content_size(&self) -> DynamicReader<Size<Px>> {
|
||||
self.content_size.create_reader()
|
||||
}
|
||||
|
||||
/// Creates a reader for the control size.
|
||||
/// This is the size of the visible area of the scroll widget.
|
||||
pub fn get_control_size(&self) -> DynamicReader<Size<Px>> {
|
||||
self.control_size.create_reader()
|
||||
}
|
||||
|
||||
fn constrained_scroll(scroll: Point<Px>, max_scroll: Point<Px>) -> Point<Px> {
|
||||
scroll.max(max_scroll).min(Point::default())
|
||||
}
|
||||
|
|
@ -252,10 +276,10 @@ impl Widget for Scroll {
|
|||
new_content_size.height.into_unsigned()
|
||||
},
|
||||
);
|
||||
let control_size = layout_size.into_signed();
|
||||
let new_control_size = layout_size.into_signed();
|
||||
|
||||
self.horizontal_bar =
|
||||
scrollbar_region(scroll.x, new_content_size.width, control_size.width);
|
||||
scrollbar_region(scroll.x, new_content_size.width, new_control_size.width);
|
||||
let max_scroll_x = if self.enabled.x {
|
||||
-self.horizontal_bar.amount_hidden
|
||||
} else {
|
||||
|
|
@ -263,7 +287,7 @@ impl Widget for Scroll {
|
|||
};
|
||||
|
||||
self.vertical_bar =
|
||||
scrollbar_region(scroll.y, new_content_size.height, control_size.height);
|
||||
scrollbar_region(scroll.y, new_content_size.height, new_control_size.height);
|
||||
let max_scroll_y = if self.enabled.y {
|
||||
-self.vertical_bar.amount_hidden
|
||||
} else {
|
||||
|
|
@ -275,20 +299,24 @@ impl Widget for Scroll {
|
|||
scroll = scroll.max(new_max_scroll);
|
||||
}
|
||||
|
||||
// This is not tracked on purpose - it's only ever changed in layout
|
||||
let content_size = self.content_size.get();
|
||||
let control_size = self.control_size.get();
|
||||
|
||||
// Preserve the current scroll if the widget has resized
|
||||
if self.content_size.width != new_content_size.width
|
||||
|| self.control_size.width != control_size.width
|
||||
if content_size.width != new_content_size.width
|
||||
|| control_size.width != new_control_size.width
|
||||
{
|
||||
self.content_size.width = new_content_size.width;
|
||||
// content_size.width = new_content_size.width;
|
||||
if self.preserve_max_scroll.get() && scroll.x == current_max_scroll.x {
|
||||
scroll.x = max_scroll_x;
|
||||
}
|
||||
}
|
||||
|
||||
if self.content_size.height != new_content_size.height
|
||||
|| self.control_size.height != control_size.height
|
||||
if content_size.height != new_content_size.height
|
||||
|| control_size.height != new_control_size.height
|
||||
{
|
||||
self.content_size.height = new_content_size.height;
|
||||
// content_size.height = new_content_size.height;
|
||||
if self.preserve_max_scroll.get() && scroll.y == current_max_scroll.y {
|
||||
scroll.y = max_scroll_y;
|
||||
}
|
||||
|
|
@ -301,12 +329,12 @@ impl Widget for Scroll {
|
|||
*current_scroll = scroll;
|
||||
}
|
||||
context.invalidate_when_changed(&self.scroll);
|
||||
self.control_size = control_size;
|
||||
self.content_size = new_content_size;
|
||||
self.control_size.set(new_control_size);
|
||||
self.content_size.set(new_content_size);
|
||||
|
||||
let region = Rect::new(
|
||||
scroll,
|
||||
self.content_size
|
||||
new_content_size
|
||||
.min(Size::new(Px::MAX, Px::MAX) - scroll.max(Point::default())),
|
||||
);
|
||||
context.set_child_layout(&managed, region);
|
||||
|
|
@ -349,10 +377,12 @@ impl Widget for Scroll {
|
|||
_button: kludgine::app::winit::event::MouseButton,
|
||||
context: &mut EventContext<'_>,
|
||||
) -> EventHandling {
|
||||
let relative_x = (self.control_size.width - location.x).max(Px::ZERO);
|
||||
let control_size = self.control_size.get();
|
||||
|
||||
let relative_x = (control_size.width - location.x).max(Px::ZERO);
|
||||
let in_vertical_area = self.enabled.y && relative_x <= self.bar_width;
|
||||
|
||||
let relative_y = (self.control_size.height - location.y).max(Px::ZERO);
|
||||
let relative_y = (control_size.height - location.y).max(Px::ZERO);
|
||||
let in_horizontal_area = self.enabled.x && relative_y <= self.bar_width;
|
||||
|
||||
if matches!(
|
||||
|
|
@ -382,7 +412,7 @@ impl Widget for Scroll {
|
|||
&self.horizontal_bar,
|
||||
&self.vertical_bar,
|
||||
self.max_scroll.get(),
|
||||
self.control_size,
|
||||
control_size,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -405,7 +435,7 @@ impl Widget for Scroll {
|
|||
&self.horizontal_bar,
|
||||
&self.vertical_bar,
|
||||
self.max_scroll.get(),
|
||||
self.control_size,
|
||||
self.control_size.get(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -420,7 +450,7 @@ impl Widget for Scroll {
|
|||
|
||||
if self.drag.mouse_buttons_down == 0 {
|
||||
if location.map_or(false, |location| {
|
||||
Rect::from(self.control_size).contains(location)
|
||||
Rect::from(self.control_size.get()).contains(location)
|
||||
}) {
|
||||
self.scrollbar_opacity_animation.handle.clear();
|
||||
self.show_scrollbars(context);
|
||||
|
|
|
|||
Loading…
Reference in a new issue