Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scrollable widget #35

Merged
merged 23 commits into from
Nov 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
719c073
Draft `Scrollable` widget (no clipping yet!)
hecrj Oct 25, 2019
09bd2c4
Expose scrollable offset properly
hecrj Oct 26, 2019
63c10b6
Remove `Scrollable::justify_content` method
hecrj Oct 26, 2019
0a0aa3e
Implement clipping for images
hecrj Oct 27, 2019
e2668b8
Remove `adapter` from `iced_wgpu::Renderer`
hecrj Oct 27, 2019
82c2aa6
Align items properly inside a `Scrollable`
hecrj Oct 27, 2019
e218901
Improve `scroll` example
hecrj Oct 27, 2019
21eb2f6
Implement clipping for quads
hecrj Oct 27, 2019
35e94f5
Draft text scrolling (no clipping yet!)
hecrj Oct 27, 2019
2b23e09
Implement text clipping (caching still broken)
hecrj Oct 28, 2019
23ebfb7
Issue draw calls only when necessary
hecrj Oct 29, 2019
be488ac
Draw scrollbar on top of scrollable content
hecrj Oct 29, 2019
a3c55f7
Stop leaking impl details in scrollable `Renderer`
hecrj Oct 29, 2019
6602c15
Complete `Scrollable::hash_layout`
hecrj Oct 29, 2019
9dabbf7
Provide `Renderer` to `Widget::on_event`
hecrj Oct 29, 2019
29588f6
Implement scrollbar interactions! :tada:
hecrj Oct 29, 2019
bd5d871
Handle touchpad scroll events
hecrj Oct 29, 2019
ace4217
Fix `Transformation` docs
hecrj Oct 29, 2019
85916c9
Rename `Primitive::Scrollable` to `Clip`
hecrj Oct 29, 2019
298c42a
Replace `nalgebra` with `glam`
hecrj Oct 31, 2019
58d04ca
Add scrollable section to `tour`
hecrj Nov 2, 2019
85dab04
Scale scrollbar movement by content ratio
hecrj Nov 2, 2019
022dc01
Show Ferris at the end of the scrollable section
hecrj Nov 2, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod radio;
mod row;

pub mod button;
pub mod scrollable;
pub mod slider;
pub mod text;

Expand All @@ -26,6 +27,9 @@ pub use slider::Slider;
#[doc(no_inline)]
pub use text::Text;

#[doc(no_inline)]
pub use scrollable::Scrollable;

pub use checkbox::Checkbox;
pub use column::Column;
pub use image::Image;
Expand Down
151 changes: 151 additions & 0 deletions core/src/widget/scrollable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
use crate::{Align, Column, Length, Point, Rectangle};

#[derive(Debug)]
pub struct Scrollable<'a, Element> {
pub state: &'a mut State,
pub height: Length,
pub max_height: Length,
pub align_self: Option<Align>,
pub content: Column<Element>,
}

impl<'a, Element> Scrollable<'a, Element> {
pub fn new(state: &'a mut State) -> Self {
Scrollable {
state,
height: Length::Shrink,
max_height: Length::Shrink,
align_self: None,
content: Column::new(),
}
}

/// Sets the vertical spacing _between_ elements.
///
/// Custom margins per element do not exist in Iced. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, units: u16) -> Self {
self.content = self.content.spacing(units);
self
}

/// Sets the padding of the [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn padding(mut self, units: u16) -> Self {
self.content = self.content.padding(units);
self
}

/// Sets the width of the [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn width(mut self, width: Length) -> Self {
self.content = self.content.width(width);
self
}

/// Sets the height of the [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}

/// Sets the maximum width of the [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn max_width(mut self, max_width: Length) -> Self {
self.content = self.content.max_width(max_width);
self
}

/// Sets the maximum height of the [`Scrollable`] in pixels.
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn max_height(mut self, max_height: Length) -> Self {
self.max_height = max_height;
self
}

/// Sets the alignment of the [`Scrollable`] itself.
///
/// This is useful if you want to override the default alignment given by
/// the parent container.
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn align_self(mut self, align: Align) -> Self {
self.align_self = Some(align);
self
}

/// Sets the horizontal alignment of the contents of the [`Scrollable`] .
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn align_items(mut self, align_items: Align) -> Self {
self.content = self.content.align_items(align_items);
self
}

/// Adds an element to the [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn push<E>(mut self, child: E) -> Scrollable<'a, Element>
where
E: Into<Element>,
{
self.content = self.content.push(child);
self
}
}

#[derive(Debug, Clone, Copy, Default)]
pub struct State {
pub scrollbar_grabbed_at: Option<Point>,
offset: u32,
}

impl State {
pub fn new() -> Self {
State::default()
}

pub fn scroll(
&mut self,
delta_y: f32,
bounds: Rectangle,
content_bounds: Rectangle,
) {
if bounds.height >= content_bounds.height {
return;
}

self.offset = (self.offset as i32 - delta_y.round() as i32)
.max(0)
.min((content_bounds.height - bounds.height) as i32)
as u32;
}

pub fn scroll_to(
&mut self,
percentage: f32,
bounds: Rectangle,
content_bounds: Rectangle,
) {
self.offset = ((content_bounds.height - bounds.height) * percentage)
.max(0.0) as u32;
}

pub fn offset(&self, bounds: Rectangle, content_bounds: Rectangle) -> u32 {
let hidden_content =
(content_bounds.height - bounds.height).round() as u32;

self.offset.min(hidden_content).max(0)
}

pub fn is_scrollbar_grabbed(&self) -> bool {
self.scrollbar_grabbed_at.is_some()
}
}
75 changes: 75 additions & 0 deletions examples/scroll.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use iced::{
button, scrollable, Align, Application, Button, Column, Element, Image,
Justify, Length, Scrollable, Text,
};

pub fn main() {
env_logger::init();

Example::default().run()
}

#[derive(Default)]
struct Example {
item_count: u16,

scroll: scrollable::State,
add_button: button::State,
}

#[derive(Debug, Clone, Copy)]
pub enum Message {
AddItem,
}

impl Application for Example {
type Message = Message;

fn update(&mut self, message: Message) {
match message {
Message::AddItem => {
self.item_count += 1;
}
}
}

fn view(&mut self) -> Element<Message> {
let content = (0..self.item_count)
.fold(
Scrollable::new(&mut self.scroll)
.spacing(20)
.padding(20)
.align_items(Align::Center),
|scrollable, i| {
if i % 2 == 0 {
scrollable.push(lorem_ipsum().width(Length::Units(600)))
} else {
scrollable.push(
Image::new(format!(
"{}/examples/resources/ferris.png",
env!("CARGO_MANIFEST_DIR")
))
.width(Length::Units(400)),
)
}
},
)
.push(
Button::new(&mut self.add_button, Text::new("Add item"))
.on_press(Message::AddItem)
.padding(20)
.border_radius(5),
);

Column::new()
.height(Length::Fill)
.justify_content(Justify::Center)
.padding(20)
.push(content)
.into()
}
}

fn lorem_ipsum() -> Text {
Text::new("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi in dui vel massa blandit interdum. Quisque placerat, odio ut vulputate sagittis, augue est facilisis ex, eget euismod felis magna in sapien. Nullam luctus consequat massa, ac interdum mauris blandit pellentesque. Nullam in est urna. Aliquam tristique lectus ac luctus feugiat. Aenean libero diam, euismod facilisis consequat quis, pellentesque luctus erat. Praesent vel tincidunt elit.")
}
77 changes: 57 additions & 20 deletions examples/tour.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use iced::{
button, slider, text::HorizontalAlignment, Align, Application, Background,
Button, Checkbox, Color, Column, Element, Image, Justify, Length, Radio,
Row, Slider, Text,
button, scrollable, slider, text::HorizontalAlignment, Align, Application,
Background, Button, Checkbox, Color, Column, Element, Image, Justify,
Length, Radio, Row, Scrollable, Slider, Text,
};

pub fn main() {
Expand All @@ -14,6 +14,7 @@ pub fn main() {

pub struct Tour {
steps: Steps,
scroll: scrollable::State,
back_button: button::State,
next_button: button::State,
debug: bool,
Expand All @@ -23,6 +24,7 @@ impl Tour {
pub fn new() -> Tour {
Tour {
steps: Steps::new(),
scroll: scrollable::State::new(),
back_button: button::State::new(),
next_button: button::State::new(),
debug: false,
Expand Down Expand Up @@ -88,11 +90,13 @@ impl Application for Tour {
};

Column::new()
.width(Length::Fill)
.height(Length::Fill)
.align_items(Align::Center)
.justify_content(Justify::Center)
.push(element)
.push(
Scrollable::new(&mut self.scroll)
.align_items(Align::Center)
.push(element),
)
.into()
}
}
Expand Down Expand Up @@ -134,6 +138,7 @@ impl Steps {
width: 300,
slider: slider::State::new(),
},
Step::Scrollable,
Step::Debugger,
Step::End,
],
Expand Down Expand Up @@ -195,6 +200,7 @@ enum Step {
width: u16,
slider: slider::State,
},
Scrollable,
Debugger,
End,
}
Expand Down Expand Up @@ -265,6 +271,7 @@ impl<'a> Step {
Step::Text { .. } => true,
Step::Image { .. } => true,
Step::RowsAndColumns { .. } => true,
Step::Scrollable => true,
Step::Debugger => true,
Step::End => false,
}
Expand All @@ -289,6 +296,7 @@ impl<'a> Step {
} => {
Self::rows_and_columns(*layout, spacing_slider, *spacing).into()
}
Step::Scrollable => Self::scrollable().into(),
Step::Debugger => Self::debugger(debug).into(),
Step::End => Self::end().into(),
}
Expand Down Expand Up @@ -502,20 +510,7 @@ impl<'a> Step {
) -> Column<'a, StepMessage> {
Self::container("Image")
.push(Text::new("An image that tries to keep its aspect ratio."))
.push(
// This should go away once we unify resource loading on native
// platforms
if cfg!(target_arch = "wasm32") {
Image::new("resources/ferris.png")
} else {
Image::new(format!(
"{}/examples/resources/ferris.png",
env!("CARGO_MANIFEST_DIR")
))
}
.width(Length::Units(width))
.align_self(Align::Center),
)
.push(ferris(width))
.push(Slider::new(
slider,
100.0..=500.0,
Expand All @@ -528,6 +523,33 @@ impl<'a> Step {
)
}

fn scrollable() -> Column<'a, StepMessage> {
Self::container("Scrollable")
.push(Text::new(
"Iced supports scrollable content. Try it out! Find the \
button further below.",
))
.push(
Text::new(
"Tip: You can use the scrollbar to scroll down faster!",
)
.size(16),
)
.push(Column::new().height(Length::Units(4096)))
.push(
Text::new("You are halfway there!")
.size(30)
.horizontal_alignment(HorizontalAlignment::Center),
)
.push(Column::new().height(Length::Units(4096)))
.push(ferris(300))
.push(
Text::new("You made it!")
.size(50)
.horizontal_alignment(HorizontalAlignment::Center),
)
}

fn debugger(debug: bool) -> Column<'a, StepMessage> {
Self::container("Debugger")
.push(Text::new(
Expand Down Expand Up @@ -555,6 +577,21 @@ impl<'a> Step {
}
}

fn ferris(width: u16) -> Image {
// This should go away once we unify resource loading on native
// platforms
if cfg!(target_arch = "wasm32") {
Image::new("resources/ferris.png")
} else {
Image::new(format!(
"{}/examples/resources/ferris.png",
env!("CARGO_MANIFEST_DIR")
))
}
.width(Length::Units(width))
.align_self(Align::Center)
}

fn button<'a, Message>(
state: &'a mut button::State,
label: &str,
Expand Down
Loading