mirror of
https://github.com/danbulant/cushy
synced 2026-07-09 13:10:57 +00:00
Label overflow + Virtual list horizontal scrolling
This commit is contained in:
parent
1bb5495f53
commit
96a265854b
10 changed files with 204 additions and 65 deletions
|
|
@ -60,6 +60,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- `ConstraintLimit::fit_measured` and `FitMeasuredSize::fit_measured` now accept
|
- `ConstraintLimit::fit_measured` and `FitMeasuredSize::fit_measured` now accept
|
||||||
either a `Px` or `UPx` measurement, and does not perform scaling adjustments.
|
either a `Px` or `UPx` measurement, and does not perform scaling adjustments.
|
||||||
To convert `Lp` use `into_upx()` first.
|
To convert `Lp` use `into_upx()` first.
|
||||||
|
- `IntoReader::to_label` and `IntoReader::into_label` have been moved to their
|
||||||
|
own trait: `Displayable`. This allows more flexible acceptance of types.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
@ -269,6 +271,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
@danbulant for helping with this change!
|
@danbulant for helping with this change!
|
||||||
- `ScrollBar` is a new widget that renders a scroll bar meant to scroll through
|
- `ScrollBar` is a new widget that renders a scroll bar meant to scroll through
|
||||||
a large container.
|
a large container.
|
||||||
|
- `Label::overflow` allows customizing the behavior for a label when it cannot
|
||||||
|
be drawn on a single line.
|
||||||
|
|
||||||
|
|
||||||
[139]: https://github.com/khonsulabs/cushy/issues/139
|
[139]: https://github.com/khonsulabs/cushy/issues/139
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use cushy::value::{Destination, Dynamic, IntoReader, Source};
|
use cushy::value::{Destination, Dynamic, Source};
|
||||||
use cushy::widget::MakeWidget;
|
use cushy::widget::MakeWidget;
|
||||||
|
use cushy::widgets::label::Displayable;
|
||||||
use cushy::Run;
|
use cushy::Run;
|
||||||
|
|
||||||
// begin rustme snippet: readme
|
// begin rustme snippet: readme
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use cushy::figures::units::Lp;
|
use cushy::figures::units::Lp;
|
||||||
use cushy::value::{Dynamic, IntoReader};
|
use cushy::value::Dynamic;
|
||||||
use cushy::widget::MakeWidget;
|
use cushy::widget::MakeWidget;
|
||||||
|
use cushy::widgets::label::Displayable;
|
||||||
use cushy::Run;
|
use cushy::Run;
|
||||||
|
|
||||||
fn main() -> cushy::Result {
|
fn main() -> cushy::Result {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use cushy::debug::DebugContext;
|
use cushy::debug::DebugContext;
|
||||||
use cushy::value::{Destination, Dynamic, IntoReader};
|
use cushy::value::{Destination, Dynamic};
|
||||||
use cushy::widget::MakeWidget;
|
use cushy::widget::MakeWidget;
|
||||||
|
use cushy::widgets::label::Displayable;
|
||||||
use cushy::widgets::slider::Slidable;
|
use cushy::widgets::slider::Slidable;
|
||||||
use cushy::{Application, Open, PendingApp};
|
use cushy::{Application, Open, PendingApp};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
use cushy::animation::{LinearInterpolate, PercentBetween};
|
use cushy::animation::{LinearInterpolate, PercentBetween};
|
||||||
use cushy::figures::units::Lp;
|
use cushy::figures::units::Lp;
|
||||||
use cushy::figures::Ranged;
|
use cushy::figures::Ranged;
|
||||||
use cushy::value::{Destination, Dynamic, ForEach, IntoReader, Source};
|
use cushy::value::{Destination, Dynamic, ForEach, Source};
|
||||||
use cushy::widget::MakeWidget;
|
use cushy::widget::MakeWidget;
|
||||||
use cushy::widgets::checkbox::Checkable;
|
use cushy::widgets::checkbox::Checkable;
|
||||||
use cushy::widgets::input::InputValue;
|
use cushy::widgets::input::InputValue;
|
||||||
|
use cushy::widgets::label::Displayable;
|
||||||
use cushy::widgets::slider::Slidable;
|
use cushy::widgets::slider::Slidable;
|
||||||
use cushy::Run;
|
use cushy::Run;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use cushy::value::Dynamic;
|
use cushy::value::Dynamic;
|
||||||
use cushy::widget::MakeWidget;
|
use cushy::widget::MakeWidget;
|
||||||
|
use cushy::widgets::label::{Displayable, LabelOverflow};
|
||||||
use cushy::widgets::slider::Slidable;
|
use cushy::widgets::slider::Slidable;
|
||||||
use cushy::widgets::VirtualList;
|
use cushy::widgets::VirtualList;
|
||||||
use cushy::Run;
|
use cushy::Run;
|
||||||
|
|
@ -14,6 +15,8 @@ fn list() -> impl MakeWidget {
|
||||||
.expect("System Time after 1970")
|
.expect("System Time after 1970")
|
||||||
.as_secs();
|
.as_secs();
|
||||||
format!("Item {index} - {timestamp}")
|
format!("Item {index} - {timestamp}")
|
||||||
|
.into_label()
|
||||||
|
.overflow(LabelOverflow::Clip)
|
||||||
});
|
});
|
||||||
let content_changed = list.content_watcher().clone();
|
let content_changed = list.content_watcher().clone();
|
||||||
|
|
||||||
|
|
|
||||||
20
src/value.rs
20
src/value.rs
|
|
@ -24,7 +24,7 @@ use crate::utils::WithClone;
|
||||||
use crate::widget::{
|
use crate::widget::{
|
||||||
MakeWidget, MakeWidgetWithTag, OnceCallback, WidgetId, WidgetInstance, WidgetList,
|
MakeWidget, MakeWidgetWithTag, OnceCallback, WidgetId, WidgetInstance, WidgetList,
|
||||||
};
|
};
|
||||||
use crate::widgets::{Label, Radio, Select, Space, Switcher};
|
use crate::widgets::{Radio, Select, Space, Switcher};
|
||||||
use crate::window::WindowHandle;
|
use crate::window::WindowHandle;
|
||||||
|
|
||||||
/// A source of one or more `T` values.
|
/// A source of one or more `T` values.
|
||||||
|
|
@ -2662,24 +2662,6 @@ impl<T> IntoReadOnly<T> for Owned<T> {
|
||||||
pub trait IntoReader<T> {
|
pub trait IntoReader<T> {
|
||||||
/// Returns this value as a reader.
|
/// Returns this value as a reader.
|
||||||
fn into_reader(self) -> DynamicReader<T>;
|
fn into_reader(self) -> DynamicReader<T>;
|
||||||
|
|
||||||
/// Returns `self` being `Display`ed in a [`Label`] widget.
|
|
||||||
fn into_label(self) -> Label<T>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
T: Debug + Display + Send + 'static,
|
|
||||||
{
|
|
||||||
Label::new(self.into_reader())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `self` being `Display`ed in a [`Label`] widget.
|
|
||||||
fn to_label(&self) -> Label<T>
|
|
||||||
where
|
|
||||||
Self: Clone,
|
|
||||||
T: Debug + Display + Send + 'static,
|
|
||||||
{
|
|
||||||
self.clone().into_label()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> IntoReader<T> for Dynamic<T> {
|
impl<T> IntoReader<T> for Dynamic<T> {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! A read-only text widget.
|
//! A read-only text widget.
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt::{Display, Write};
|
use std::fmt::{Debug, Display, Write};
|
||||||
|
|
||||||
use figures::units::{Px, UPx};
|
use figures::units::{Px, UPx};
|
||||||
use figures::{Point, Round, Size};
|
use figures::{Point, Round, Size};
|
||||||
|
|
@ -12,7 +12,9 @@ use super::input::CowString;
|
||||||
use crate::context::{GraphicsContext, LayoutContext, Trackable, WidgetContext};
|
use crate::context::{GraphicsContext, LayoutContext, Trackable, WidgetContext};
|
||||||
use crate::styles::components::TextColor;
|
use crate::styles::components::TextColor;
|
||||||
use crate::styles::FontFamilyList;
|
use crate::styles::FontFamilyList;
|
||||||
use crate::value::{Dynamic, Generation, IntoReadOnly, ReadOnly, Value};
|
use crate::value::{
|
||||||
|
Dynamic, DynamicReader, Generation, IntoDynamic, IntoReadOnly, IntoValue, ReadOnly, Value,
|
||||||
|
};
|
||||||
use crate::widget::{MakeWidgetWithTag, Widget, WidgetInstance, WidgetTag};
|
use crate::widget::{MakeWidgetWithTag, Widget, WidgetInstance, WidgetTag};
|
||||||
use crate::window::WindowLocal;
|
use crate::window::WindowLocal;
|
||||||
use crate::ConstraintLimit;
|
use crate::ConstraintLimit;
|
||||||
|
|
@ -22,29 +24,46 @@ use crate::ConstraintLimit;
|
||||||
pub struct Label<T> {
|
pub struct Label<T> {
|
||||||
/// The contents of the label.
|
/// The contents of the label.
|
||||||
pub display: ReadOnly<T>,
|
pub display: ReadOnly<T>,
|
||||||
|
/// The behavior to use when too much text is able to be displayed on a
|
||||||
|
/// single line.
|
||||||
|
pub overflow: Value<LabelOverflow>,
|
||||||
displayed: String,
|
displayed: String,
|
||||||
prepared_text: WindowLocal<LabelCacheKey>,
|
prepared_text: WindowLocal<LabelCacheKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Label<T>
|
impl<T> Label<T>
|
||||||
where
|
where
|
||||||
T: std::fmt::Debug + DynamicDisplay + Send + 'static,
|
T: Debug + DynamicDisplay + Send + 'static,
|
||||||
{
|
{
|
||||||
/// Returns a new label that displays `text`.
|
/// Returns a new label that displays `text`, wrapping if necessary to fit
|
||||||
|
/// the content in the provided space.
|
||||||
pub fn new(text: impl IntoReadOnly<T>) -> Self {
|
pub fn new(text: impl IntoReadOnly<T>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
display: text.into_read_only(),
|
display: text.into_read_only(),
|
||||||
|
overflow: Value::Constant(LabelOverflow::WordWrap),
|
||||||
displayed: String::new(),
|
displayed: String::new(),
|
||||||
prepared_text: WindowLocal::default(),
|
prepared_text: WindowLocal::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the behavior when more text than can fit on a single line is
|
||||||
|
/// displayed.
|
||||||
|
#[must_use]
|
||||||
|
pub fn overflow(mut self, overflow: impl IntoValue<LabelOverflow>) -> Self {
|
||||||
|
self.overflow = overflow.into_value();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn prepared_text(
|
fn prepared_text(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &mut GraphicsContext<'_, '_, '_, '_>,
|
context: &mut GraphicsContext<'_, '_, '_, '_>,
|
||||||
color: Color,
|
color: Color,
|
||||||
width: Px,
|
mut width: Px,
|
||||||
) -> &MeasuredText<Px> {
|
) -> &MeasuredText<Px> {
|
||||||
|
let overflow = self.overflow.get_tracking_invalidate(context);
|
||||||
|
if overflow == LabelOverflow::Clip {
|
||||||
|
width = Px::MAX;
|
||||||
|
}
|
||||||
let check_generation = self.display.generation();
|
let check_generation = self.display.generation();
|
||||||
context.apply_current_font_settings();
|
context.apply_current_font_settings();
|
||||||
let current_families = context.current_family_list();
|
let current_families = context.current_family_list();
|
||||||
|
|
@ -88,7 +107,7 @@ where
|
||||||
|
|
||||||
impl<T> Widget for Label<T>
|
impl<T> Widget for Label<T>
|
||||||
where
|
where
|
||||||
T: std::fmt::Debug + DynamicDisplay + Send + 'static,
|
T: Debug + DynamicDisplay + Send + 'static,
|
||||||
{
|
{
|
||||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) {
|
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) {
|
||||||
self.display.invalidate_when_changed(context);
|
self.display.invalidate_when_changed(context);
|
||||||
|
|
@ -158,6 +177,18 @@ impl MakeWidgetWithTag for &'_ String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The overflow behavior for a [`Label`].
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum LabelOverflow {
|
||||||
|
/// Any text that cannot be drawn on a single line will be clipped to the
|
||||||
|
/// bounds of the label.
|
||||||
|
Clip,
|
||||||
|
/// Wraps text at the boundaries between words and whitespace while
|
||||||
|
/// attaching punctuation to the non-wrapped word when possible.
|
||||||
|
WordWrap,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct LabelCacheKey {
|
struct LabelCacheKey {
|
||||||
text: MeasuredText<Px>,
|
text: MeasuredText<Px>,
|
||||||
|
|
@ -209,3 +240,65 @@ impl Display for DynamicDisplayer<'_, '_> {
|
||||||
self.0.fmt(self.1, f)
|
self.0.fmt(self.1, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A type that can be displayed as a [`Label`].
|
||||||
|
pub trait Displayable<T>
|
||||||
|
where
|
||||||
|
T: Debug + Display + Send + 'static,
|
||||||
|
{
|
||||||
|
/// Returns this value as a displayable reader.
|
||||||
|
fn into_displayable(self) -> DynamicReader<T>;
|
||||||
|
|
||||||
|
/// Returns `self` being `Display`ed in a [`Label`] widget.
|
||||||
|
fn into_label(self) -> Label<T>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
T: Debug + Display + Send + 'static,
|
||||||
|
{
|
||||||
|
Label::new(self.into_displayable())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `self` being `Display`ed in a [`Label`] widget.
|
||||||
|
fn to_label(&self) -> Label<T>
|
||||||
|
where
|
||||||
|
Self: Clone,
|
||||||
|
{
|
||||||
|
self.clone().into_label()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Displayable<T> for T
|
||||||
|
where
|
||||||
|
T: Debug + Display + Send + 'static,
|
||||||
|
{
|
||||||
|
fn into_displayable(self) -> DynamicReader<T> {
|
||||||
|
Dynamic::new(self).into_reader()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Displayable<T> for Dynamic<T>
|
||||||
|
where
|
||||||
|
T: Debug + Display + Send + 'static,
|
||||||
|
{
|
||||||
|
fn into_displayable(self) -> DynamicReader<T> {
|
||||||
|
self.into_reader()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Displayable<T> for DynamicReader<T>
|
||||||
|
where
|
||||||
|
T: Debug + Display + Send + 'static,
|
||||||
|
{
|
||||||
|
fn into_displayable(self) -> DynamicReader<T> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Displayable<T> for Value<T>
|
||||||
|
where
|
||||||
|
T: Debug + Display + Send + 'static,
|
||||||
|
{
|
||||||
|
fn into_displayable(self) -> DynamicReader<T> {
|
||||||
|
self.into_dynamic().into_reader()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -715,7 +715,7 @@ impl Widget for ScrollBar {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Rect::new(
|
Rect::new(
|
||||||
Point::new(UPx::ZERO, control_size.height - self.bar_width),
|
Point::new(self.info.offset, control_size.height - self.bar_width),
|
||||||
Size::new(self.info.size, self.bar_width),
|
Size::new(self.info.size, self.bar_width),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use std::ops::Range;
|
||||||
|
|
||||||
use cushy::context::LayoutContext;
|
use cushy::context::LayoutContext;
|
||||||
use cushy::ConstraintLimit;
|
use cushy::ConstraintLimit;
|
||||||
use figures::{IntoUnsigned, UnscaledUnit};
|
use figures::IntoUnsigned;
|
||||||
|
|
||||||
use super::scroll::OwnedWidget;
|
use super::scroll::OwnedWidget;
|
||||||
use crate::context::{AsEventContext, EventContext, Trackable};
|
use crate::context::{AsEventContext, EventContext, Trackable};
|
||||||
|
|
@ -12,7 +12,9 @@ use crate::figures::units::{Px, UPx};
|
||||||
use crate::figures::{IntoSigned, Point, Rect, Round, Size, Zero};
|
use crate::figures::{IntoSigned, Point, Rect, Round, Size, Zero};
|
||||||
use crate::kludgine::app::winit::event::{MouseScrollDelta, TouchPhase};
|
use crate::kludgine::app::winit::event::{MouseScrollDelta, TouchPhase};
|
||||||
use crate::kludgine::app::winit::window::CursorIcon;
|
use crate::kludgine::app::winit::window::CursorIcon;
|
||||||
use crate::value::{Destination, Dynamic, DynamicReader, IntoDynamic, IntoValue, Source, Watcher};
|
use crate::value::{
|
||||||
|
Destination, Dynamic, DynamicReader, IntoDynamic, IntoValue, MapEachCloned, Source, Watcher,
|
||||||
|
};
|
||||||
use crate::widget::{
|
use crate::widget::{
|
||||||
Callback, EventHandling, MakeWidget, MountedWidget, Widget, WidgetInstance, HANDLED, IGNORED,
|
Callback, EventHandling, MakeWidget, MountedWidget, Widget, WidgetInstance, HANDLED, IGNORED,
|
||||||
};
|
};
|
||||||
|
|
@ -49,6 +51,7 @@ struct VirtualListItem {
|
||||||
pub struct VirtualList {
|
pub struct VirtualList {
|
||||||
make_row: RowMaker,
|
make_row: RowMaker,
|
||||||
vertical_scroll: OwnedWidget<ScrollBar>,
|
vertical_scroll: OwnedWidget<ScrollBar>,
|
||||||
|
horizontal_scroll: OwnedWidget<ScrollBar>,
|
||||||
items: VecDeque<VirtualListItem>,
|
items: VecDeque<VirtualListItem>,
|
||||||
content_size: Dynamic<Size<UPx>>,
|
content_size: Dynamic<Size<UPx>>,
|
||||||
contents: Watcher,
|
contents: Watcher,
|
||||||
|
|
@ -88,6 +91,18 @@ impl VirtualList {
|
||||||
let item_count = item_count.into_value().into_dynamic().into_reader();
|
let item_count = item_count.into_value().into_dynamic().into_reader();
|
||||||
let content_size = Dynamic::new(Size::default());
|
let content_size = Dynamic::new(Size::default());
|
||||||
|
|
||||||
|
let x = scroll.map_each_cloned(|scroll| scroll.x);
|
||||||
|
x.for_each_cloned({
|
||||||
|
let scroll = scroll.clone();
|
||||||
|
move |x| {
|
||||||
|
if let Ok(mut scroll) = scroll.try_lock() {
|
||||||
|
if scroll.x != x {
|
||||||
|
scroll.x = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.persist();
|
||||||
let y = scroll.map_each_cloned(|scroll| scroll.y);
|
let y = scroll.map_each_cloned(|scroll| scroll.y);
|
||||||
y.for_each_cloned({
|
y.for_each_cloned({
|
||||||
let scroll = scroll.clone();
|
let scroll = scroll.clone();
|
||||||
|
|
@ -100,10 +115,12 @@ impl VirtualList {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.persist();
|
.persist();
|
||||||
let vertical = ScrollBar::new(content_size.map_each_cloned(|size| size.height), y, true);
|
let horizontal = ScrollBar::new(content_size.map_each_cloned(|size| size.width), x, false);
|
||||||
let max_scroll = vertical
|
let mut vertical =
|
||||||
.max_scroll()
|
ScrollBar::new(content_size.map_each_cloned(|size| size.height), y, true);
|
||||||
.map_each_cloned(|y| Point::new(UPx::ZERO, y))
|
vertical.synchronize_visibility_with(&horizontal);
|
||||||
|
let max_scroll = (&horizontal.max_scroll(), &vertical.max_scroll())
|
||||||
|
.map_each_cloned(|(x, y)| Point::new(x, y))
|
||||||
.into_reader();
|
.into_reader();
|
||||||
|
|
||||||
let contents = Watcher::default();
|
let contents = Watcher::default();
|
||||||
|
|
@ -114,6 +131,7 @@ impl VirtualList {
|
||||||
contents,
|
contents,
|
||||||
contents_generation,
|
contents_generation,
|
||||||
vertical_scroll: OwnedWidget::new(vertical),
|
vertical_scroll: OwnedWidget::new(vertical),
|
||||||
|
horizontal_scroll: OwnedWidget::new(horizontal),
|
||||||
items: VecDeque::new(),
|
items: VecDeque::new(),
|
||||||
control_size: Dynamic::new(Size::default()),
|
control_size: Dynamic::new(Size::default()),
|
||||||
content_size,
|
content_size,
|
||||||
|
|
@ -180,6 +198,52 @@ impl VirtualList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn layout_scrollbars(
|
||||||
|
&mut self,
|
||||||
|
available_space: Size<ConstraintLimit>,
|
||||||
|
new_control_size: Size<UPx>,
|
||||||
|
context: &mut LayoutContext<'_, '_, '_, '_>,
|
||||||
|
) {
|
||||||
|
let horizontal = self
|
||||||
|
.horizontal_scroll
|
||||||
|
.make_if_needed()
|
||||||
|
.mounted(&mut context.as_event_context());
|
||||||
|
let scrollbar_layout = context.for_other(&horizontal).layout(available_space);
|
||||||
|
context.set_child_layout(
|
||||||
|
&horizontal,
|
||||||
|
Rect::new(
|
||||||
|
Point::new(
|
||||||
|
Px::ZERO,
|
||||||
|
available_space
|
||||||
|
.height
|
||||||
|
.fit_measured(new_control_size.height)
|
||||||
|
.saturating_sub(scrollbar_layout.height)
|
||||||
|
.into_signed(),
|
||||||
|
),
|
||||||
|
scrollbar_layout.into_signed(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let vertical = self
|
||||||
|
.vertical_scroll
|
||||||
|
.make_if_needed()
|
||||||
|
.mounted(&mut context.as_event_context());
|
||||||
|
let scrollbar_layout = context.for_other(&vertical).layout(available_space);
|
||||||
|
context.set_child_layout(
|
||||||
|
&vertical,
|
||||||
|
Rect::new(
|
||||||
|
Point::new(
|
||||||
|
available_space
|
||||||
|
.width
|
||||||
|
.fit_measured(new_control_size.width)
|
||||||
|
.saturating_sub(scrollbar_layout.width)
|
||||||
|
.into_signed(),
|
||||||
|
Px::ZERO,
|
||||||
|
),
|
||||||
|
scrollbar_layout.into_signed(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn layout_rows(
|
fn layout_rows(
|
||||||
&mut self,
|
&mut self,
|
||||||
item_count: usize,
|
item_count: usize,
|
||||||
|
|
@ -205,29 +269,12 @@ impl VirtualList {
|
||||||
item_size.width = new_control_size.width;
|
item_size.width = new_control_size.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
let vertical = self
|
self.layout_scrollbars(available_space, new_control_size, context);
|
||||||
.vertical_scroll
|
|
||||||
.make_if_needed()
|
|
||||||
.mounted(&mut context.as_event_context());
|
|
||||||
let scrollbar_layout = context.for_other(&vertical).layout(available_space);
|
|
||||||
context.set_child_layout(
|
|
||||||
&vertical,
|
|
||||||
Rect::new(
|
|
||||||
Point::new(
|
|
||||||
available_space
|
|
||||||
.width
|
|
||||||
.fit_measured(new_control_size.width)
|
|
||||||
.saturating_sub(scrollbar_layout.width)
|
|
||||||
.into_signed(),
|
|
||||||
Px::ZERO,
|
|
||||||
),
|
|
||||||
scrollbar_layout.into_signed(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
let scroll = self.scroll.get_tracking_invalidate(context);
|
let scroll = self.scroll.get_tracking_invalidate(context);
|
||||||
|
|
||||||
|
let max_scroll_x = item_size.width.saturating_sub(new_control_size.width);
|
||||||
let max_scroll_y = content_height.saturating_sub(new_control_size.height);
|
let max_scroll_y = content_height.saturating_sub(new_control_size.height);
|
||||||
let scroll = scroll.min(Point::new(UPx::MAX, max_scroll_y));
|
let scroll = scroll.min(Point::new(max_scroll_x, max_scroll_y));
|
||||||
|
|
||||||
let start_item = (scroll.y.floor() / item_size.height).floor().get() as usize;
|
let start_item = (scroll.y.floor() / item_size.height).floor().get() as usize;
|
||||||
let end_item = ((scroll.y.ceil() + new_control_size.height) / item_size.height)
|
let end_item = ((scroll.y.ceil() + new_control_size.height) / item_size.height)
|
||||||
|
|
@ -271,20 +318,15 @@ impl VirtualList {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add % to Figures
|
let x = -scroll.x.into_signed();
|
||||||
let mut y =
|
let mut y = -(scroll.y % item_size.height).into_signed();
|
||||||
-UPx::from_unscaled(scroll.y.into_unscaled() % item_size.height.into_unscaled())
|
let constraint = item_size.map(ConstraintLimit::Fill);
|
||||||
.into_signed();
|
|
||||||
let constraint = item_size.map(ConstraintLimit::SizeToFit);
|
|
||||||
for item in &self.items {
|
for item in &self.items {
|
||||||
let child_size = context.for_other(&item.mounted).layout(constraint);
|
let child_size = context.for_other(&item.mounted).layout(constraint);
|
||||||
|
|
||||||
context.set_child_layout(
|
context.set_child_layout(
|
||||||
&item.mounted,
|
&item.mounted,
|
||||||
Rect::new(
|
Rect::new(Point::new(x, y), item_size.min(child_size).into_signed()),
|
||||||
Point::new(Px::ZERO, y),
|
|
||||||
item_size.min(child_size).into_signed(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
y += item_size.height.into_signed();
|
y += item_size.height.into_signed();
|
||||||
}
|
}
|
||||||
|
|
@ -354,6 +396,11 @@ impl Widget for VirtualList {
|
||||||
.expect_made_mut()
|
.expect_made_mut()
|
||||||
.mounted(&mut context.as_event_context());
|
.mounted(&mut context.as_event_context());
|
||||||
context.for_other(&vertical).redraw();
|
context.for_other(&vertical).redraw();
|
||||||
|
let horizontal = self
|
||||||
|
.horizontal_scroll
|
||||||
|
.expect_made_mut()
|
||||||
|
.mounted(&mut context.as_event_context());
|
||||||
|
context.for_other(&horizontal).redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
|
|
@ -384,6 +431,12 @@ impl Widget for VirtualList {
|
||||||
.expect("a ScrollBar")
|
.expect("a ScrollBar")
|
||||||
.mouse_wheel(delta, context)
|
.mouse_wheel(delta, context)
|
||||||
.is_break();
|
.is_break();
|
||||||
|
let mut horizontal = self.horizontal_scroll.expect_made().widget().lock();
|
||||||
|
handled |= horizontal
|
||||||
|
.downcast_mut::<ScrollBar>()
|
||||||
|
.expect("a ScrollBar")
|
||||||
|
.mouse_wheel(delta, context)
|
||||||
|
.is_break();
|
||||||
}
|
}
|
||||||
if handled {
|
if handled {
|
||||||
self.show_scrollbars(context);
|
self.show_scrollbars(context);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue