Draft Scrollable widget (no clipping yet!)
This commit is contained in:
parent
4769272122
commit
719c073fc6
13 changed files with 526 additions and 50 deletions
|
|
@ -14,6 +14,7 @@ mod radio;
|
||||||
mod row;
|
mod row;
|
||||||
|
|
||||||
pub mod button;
|
pub mod button;
|
||||||
|
pub mod scrollable;
|
||||||
pub mod slider;
|
pub mod slider;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
|
|
@ -26,6 +27,9 @@ pub use slider::Slider;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use text::Text;
|
pub use text::Text;
|
||||||
|
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use scrollable::Scrollable;
|
||||||
|
|
||||||
pub use checkbox::Checkbox;
|
pub use checkbox::Checkbox;
|
||||||
pub use column::Column;
|
pub use column::Column;
|
||||||
pub use image::Image;
|
pub use image::Image;
|
||||||
|
|
|
||||||
122
core/src/widget/scrollable.rs
Normal file
122
core/src/widget/scrollable.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
use crate::{Align, Column, Justify, Length};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Scrollable<'a, Element> {
|
||||||
|
pub state: &'a mut State,
|
||||||
|
pub height: Length,
|
||||||
|
pub align_self: Option<Align>,
|
||||||
|
pub align_items: Align,
|
||||||
|
pub content: Column<Element>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Element> Scrollable<'a, Element> {
|
||||||
|
pub fn new(state: &'a mut State) -> Self {
|
||||||
|
Scrollable {
|
||||||
|
state,
|
||||||
|
height: Length::Shrink,
|
||||||
|
align_self: None,
|
||||||
|
align_items: Align::Start,
|
||||||
|
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 [`Column`].
|
||||||
|
///
|
||||||
|
/// [`Column`]: struct.Column.html
|
||||||
|
pub fn padding(mut self, units: u16) -> Self {
|
||||||
|
self.content = self.content.padding(units);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the width of the [`Column`].
|
||||||
|
///
|
||||||
|
/// [`Column`]: struct.Column.html
|
||||||
|
pub fn width(mut self, width: Length) -> Self {
|
||||||
|
self.content = self.content.width(width);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the height of the [`Column`].
|
||||||
|
///
|
||||||
|
/// [`Column`]: struct.Column.html
|
||||||
|
pub fn height(mut self, height: Length) -> Self {
|
||||||
|
self.height = height;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the maximum width of the [`Column`].
|
||||||
|
///
|
||||||
|
/// [`Column`]: struct.Column.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 [`Column`] in pixels.
|
||||||
|
///
|
||||||
|
/// [`Column`]: struct.Column.html
|
||||||
|
pub fn max_height(mut self, max_height: Length) -> Self {
|
||||||
|
self.content = self.content.max_height(max_height);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the alignment of the [`Column`] itself.
|
||||||
|
///
|
||||||
|
/// This is useful if you want to override the default alignment given by
|
||||||
|
/// the parent container.
|
||||||
|
///
|
||||||
|
/// [`Column`]: struct.Column.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 [`Column`] .
|
||||||
|
///
|
||||||
|
/// [`Column`]: struct.Column.html
|
||||||
|
pub fn align_items(mut self, align_items: Align) -> Self {
|
||||||
|
self.align_items = align_items;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the vertical distribution strategy for the contents of the
|
||||||
|
/// [`Column`] .
|
||||||
|
///
|
||||||
|
/// [`Column`]: struct.Column.html
|
||||||
|
pub fn justify_content(mut self, justify: Justify) -> Self {
|
||||||
|
self.content = self.content.justify_content(justify);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds an element to the [`Column`].
|
||||||
|
///
|
||||||
|
/// [`Column`]: struct.Column.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 offset: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
State::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
69
examples/scroll.rs
Normal file
69
examples/scroll.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
use iced::{
|
||||||
|
button, scrollable, Align, Application, Button, Color, Element, Image,
|
||||||
|
Length, Scrollable, Text,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
Example::default().run()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Example {
|
||||||
|
paragraph_count: u16,
|
||||||
|
|
||||||
|
scroll: scrollable::State,
|
||||||
|
add_button: button::State,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Message {
|
||||||
|
AddParagraph,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Application for Example {
|
||||||
|
type Message = Message;
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message) {
|
||||||
|
match message {
|
||||||
|
Message::AddParagraph => {
|
||||||
|
self.paragraph_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&mut self) -> Element<Message> {
|
||||||
|
let content = Scrollable::new(&mut self.scroll)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.max_width(Length::Units(600))
|
||||||
|
.spacing(20)
|
||||||
|
.padding(20)
|
||||||
|
.align_self(Align::Center);
|
||||||
|
|
||||||
|
//let content = (0..self.paragraph_count)
|
||||||
|
// .fold(content, |column, _| column.push(lorem_ipsum()))
|
||||||
|
// .push(
|
||||||
|
// Button::new(&mut self.add_button, Text::new("Add paragraph"))
|
||||||
|
// .on_press(Message::AddParagraph)
|
||||||
|
// .padding(20)
|
||||||
|
// .border_radius(5)
|
||||||
|
// .align_self(Align::Center),
|
||||||
|
// );
|
||||||
|
|
||||||
|
(0..10)
|
||||||
|
.fold(content, |content, _| {
|
||||||
|
content.push(
|
||||||
|
Image::new(format!(
|
||||||
|
"{}/examples/resources/ferris.png",
|
||||||
|
env!("CARGO_MANIFEST_DIR")
|
||||||
|
))
|
||||||
|
.width(Length::Units(400))
|
||||||
|
.align_self(Align::Center),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.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.")
|
||||||
|
}
|
||||||
|
|
@ -347,7 +347,7 @@ impl Cache {
|
||||||
.0
|
.0
|
||||||
.compute_layout(geometry::Size::undefined())
|
.compute_layout(geometry::Size::undefined())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
cursor_position: Point::new(0.0, 0.0),
|
cursor_position: Point::new(-1.0, -1.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ pub mod column;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod radio;
|
pub mod radio;
|
||||||
pub mod row;
|
pub mod row;
|
||||||
|
pub mod scrollable;
|
||||||
pub mod slider;
|
pub mod slider;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
|
|
@ -42,6 +43,8 @@ pub use radio::Radio;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use row::Row;
|
pub use row::Row;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
|
pub use scrollable::Scrollable;
|
||||||
|
#[doc(no_inline)]
|
||||||
pub use slider::Slider;
|
pub use slider::Slider;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use text::Text;
|
pub use text::Text;
|
||||||
|
|
|
||||||
121
native/src/widget/scrollable.rs
Normal file
121
native/src/widget/scrollable.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
use crate::{
|
||||||
|
column, input::mouse, Element, Event, Hasher, Layout, Node, Point, Style,
|
||||||
|
Widget,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use iced_core::scrollable::State;
|
||||||
|
|
||||||
|
/// A scrollable [`Column`].
|
||||||
|
///
|
||||||
|
/// [`Column`]: ../column/struct.Column.html
|
||||||
|
pub type Scrollable<'a, Message, Renderer> =
|
||||||
|
iced_core::Scrollable<'a, Element<'a, Message, Renderer>>;
|
||||||
|
|
||||||
|
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||||
|
for Scrollable<'a, Message, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: self::Renderer + column::Renderer,
|
||||||
|
{
|
||||||
|
fn node(&self, renderer: &Renderer) -> Node {
|
||||||
|
let mut content = self.content.node(renderer);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut style = content.0.style();
|
||||||
|
style.flex_shrink = 0.0;
|
||||||
|
|
||||||
|
content.0.set_style(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut style = Style::default()
|
||||||
|
.width(self.content.width)
|
||||||
|
.max_width(self.content.max_width)
|
||||||
|
.height(self.height)
|
||||||
|
.align_self(self.align_self)
|
||||||
|
.align_items(self.align_items);
|
||||||
|
|
||||||
|
style.0.flex_direction = stretch::style::FlexDirection::Column;
|
||||||
|
|
||||||
|
Node::with_children(style, vec![content])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
event: Event,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
messages: &mut Vec<Message>,
|
||||||
|
) {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
|
|
||||||
|
let content = layout.children().next().unwrap();
|
||||||
|
let content_bounds = content.bounds();
|
||||||
|
|
||||||
|
if is_mouse_over {
|
||||||
|
match event {
|
||||||
|
Event::Mouse(mouse::Event::WheelScrolled {
|
||||||
|
delta_y, ..
|
||||||
|
}) => {
|
||||||
|
// TODO: Configurable speed (?)
|
||||||
|
self.state.offset = (self.state.offset as i32
|
||||||
|
- delta_y.round() as i32 * 15)
|
||||||
|
.max(0)
|
||||||
|
.min((content_bounds.height - bounds.height) as i32)
|
||||||
|
as u32;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cursor_position = if is_mouse_over {
|
||||||
|
Point::new(
|
||||||
|
cursor_position.x,
|
||||||
|
cursor_position.y + self.state.offset as f32,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Point::new(cursor_position.x, -1.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.content.on_event(
|
||||||
|
event,
|
||||||
|
layout.children().next().unwrap(),
|
||||||
|
cursor_position,
|
||||||
|
messages,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
) -> Renderer::Output {
|
||||||
|
self::Renderer::draw(renderer, &self, layout, cursor_position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_layout(&self, state: &mut Hasher) {
|
||||||
|
self.content.hash_layout(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Renderer: crate::Renderer + Sized {
|
||||||
|
fn draw<Message>(
|
||||||
|
&mut self,
|
||||||
|
scrollable: &Scrollable<'_, Message, Self>,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
|
||||||
|
for Element<'a, Message, Renderer>
|
||||||
|
where
|
||||||
|
Renderer: 'a + self::Renderer + column::Renderer,
|
||||||
|
Message: 'static,
|
||||||
|
{
|
||||||
|
fn from(
|
||||||
|
scrollable: Scrollable<'a, Message, Renderer>,
|
||||||
|
) -> Element<'a, Message, Renderer> {
|
||||||
|
Element::new(scrollable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
pub use iced_wgpu::{Primitive, Renderer};
|
pub use iced_wgpu::{Primitive, Renderer};
|
||||||
|
|
||||||
pub use iced_winit::{
|
pub use iced_winit::{
|
||||||
button, slider, text, winit, Align, Background, Checkbox, Color, Image,
|
button, scrollable, slider, text, winit, Align, Background, Checkbox,
|
||||||
Justify, Length, Radio, Slider, Text,
|
Color, Image, Justify, Length, Radio, Scrollable, Slider, Text,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type Element<'a, Message> = iced_winit::Element<'a, Message, Renderer>;
|
pub type Element<'a, Message> = iced_winit::Element<'a, Message, Renderer>;
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,5 @@ wgpu = { version = "0.3", git = "https://github.com/gfx-rs/wgpu-rs", rev = "cb25
|
||||||
wgpu_glyph = { version = "0.4", git = "https://github.com/hecrj/wgpu_glyph", rev = "48daa98f5f785963838b4345e86ac40eac095ba9" }
|
wgpu_glyph = { version = "0.4", git = "https://github.com/hecrj/wgpu_glyph", rev = "48daa98f5f785963838b4345e86ac40eac095ba9" }
|
||||||
raw-window-handle = "0.1"
|
raw-window-handle = "0.1"
|
||||||
image = "0.22"
|
image = "0.22"
|
||||||
|
nalgebra = "0.18"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
|
||||||
|
|
@ -23,4 +23,9 @@ pub enum Primitive {
|
||||||
path: String,
|
path: String,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
},
|
},
|
||||||
|
Scrollable {
|
||||||
|
bounds: Rectangle,
|
||||||
|
offset: u32,
|
||||||
|
content: Box<Primitive>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ mod column;
|
||||||
mod image;
|
mod image;
|
||||||
mod radio;
|
mod radio;
|
||||||
mod row;
|
mod row;
|
||||||
|
mod scrollable;
|
||||||
mod slider;
|
mod slider;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
|
|
@ -31,8 +32,6 @@ pub struct Renderer {
|
||||||
quad_pipeline: quad::Pipeline,
|
quad_pipeline: quad::Pipeline,
|
||||||
image_pipeline: crate::image::Pipeline,
|
image_pipeline: crate::image::Pipeline,
|
||||||
|
|
||||||
quads: Vec<Quad>,
|
|
||||||
images: Vec<Image>,
|
|
||||||
glyph_brush: Rc<RefCell<GlyphBrush<'static, ()>>>,
|
glyph_brush: Rc<RefCell<GlyphBrush<'static, ()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,6 +42,24 @@ pub struct Target {
|
||||||
swap_chain: SwapChain,
|
swap_chain: SwapChain,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Layer {
|
||||||
|
quads: Vec<Quad>,
|
||||||
|
images: Vec<Image>,
|
||||||
|
layers: Vec<Layer>,
|
||||||
|
y_offset: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layer {
|
||||||
|
pub fn new(y_offset: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
quads: Vec::new(),
|
||||||
|
images: Vec::new(),
|
||||||
|
layers: Vec::new(),
|
||||||
|
y_offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Renderer {
|
impl Renderer {
|
||||||
fn new<W: HasRawWindowHandle>(window: &W) -> Self {
|
fn new<W: HasRawWindowHandle>(window: &W) -> Self {
|
||||||
let adapter = Adapter::request(&RequestAdapterOptions {
|
let adapter = Adapter::request(&RequestAdapterOptions {
|
||||||
|
|
@ -79,8 +96,6 @@ impl Renderer {
|
||||||
quad_pipeline,
|
quad_pipeline,
|
||||||
image_pipeline,
|
image_pipeline,
|
||||||
|
|
||||||
quads: Vec::new(),
|
|
||||||
images: Vec::new(),
|
|
||||||
glyph_brush: Rc::new(RefCell::new(glyph_brush)),
|
glyph_brush: Rc::new(RefCell::new(glyph_brush)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -132,27 +147,10 @@ impl Renderer {
|
||||||
depth_stencil_attachment: None,
|
depth_stencil_attachment: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
self.draw_primitive(primitive);
|
let mut layer = Layer::new(0);
|
||||||
|
|
||||||
self.quad_pipeline.draw(
|
self.draw_primitive(primitive, &mut layer);
|
||||||
&mut self.device,
|
self.flush(target.transformation, &layer, &mut encoder, &frame.view);
|
||||||
&mut encoder,
|
|
||||||
&self.quads,
|
|
||||||
target.transformation,
|
|
||||||
&frame.view,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.quads.clear();
|
|
||||||
|
|
||||||
self.image_pipeline.draw(
|
|
||||||
&mut self.device,
|
|
||||||
&mut encoder,
|
|
||||||
&self.images,
|
|
||||||
target.transformation,
|
|
||||||
&frame.view,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.images.clear();
|
|
||||||
|
|
||||||
self.glyph_brush
|
self.glyph_brush
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
|
|
@ -170,13 +168,13 @@ impl Renderer {
|
||||||
*mouse_cursor
|
*mouse_cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_primitive(&mut self, primitive: &Primitive) {
|
fn draw_primitive(&mut self, primitive: &Primitive, layer: &mut Layer) {
|
||||||
match primitive {
|
match primitive {
|
||||||
Primitive::None => {}
|
Primitive::None => {}
|
||||||
Primitive::Group { primitives } => {
|
Primitive::Group { primitives } => {
|
||||||
// TODO: Inspect a bit and regroup (?)
|
// TODO: Inspect a bit and regroup (?)
|
||||||
for primitive in primitives {
|
for primitive in primitives {
|
||||||
self.draw_primitive(primitive)
|
self.draw_primitive(primitive, layer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Primitive::Text {
|
Primitive::Text {
|
||||||
|
|
@ -244,7 +242,7 @@ impl Renderer {
|
||||||
background,
|
background,
|
||||||
border_radius,
|
border_radius,
|
||||||
} => {
|
} => {
|
||||||
self.quads.push(Quad {
|
layer.quads.push(Quad {
|
||||||
position: [bounds.x, bounds.y],
|
position: [bounds.x, bounds.y],
|
||||||
scale: [bounds.width, bounds.height],
|
scale: [bounds.width, bounds.height],
|
||||||
color: match background {
|
color: match background {
|
||||||
|
|
@ -254,12 +252,55 @@ impl Renderer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Primitive::Image { path, bounds } => {
|
Primitive::Image { path, bounds } => {
|
||||||
self.images.push(Image {
|
layer.images.push(Image {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
position: [bounds.x, bounds.y],
|
position: [bounds.x, bounds.y],
|
||||||
scale: [bounds.width, bounds.height],
|
scale: [bounds.width, bounds.height],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Primitive::Scrollable {
|
||||||
|
bounds,
|
||||||
|
offset,
|
||||||
|
content,
|
||||||
|
} => {
|
||||||
|
let mut new_layer = Layer::new(layer.y_offset + offset);
|
||||||
|
|
||||||
|
// TODO: Primitive culling
|
||||||
|
self.draw_primitive(content, &mut new_layer);
|
||||||
|
|
||||||
|
layer.layers.push(new_layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(
|
||||||
|
&mut self,
|
||||||
|
transformation: Transformation,
|
||||||
|
layer: &Layer,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
target: &wgpu::TextureView,
|
||||||
|
) {
|
||||||
|
let translated = transformation
|
||||||
|
* Transformation::translate(0.0, -(layer.y_offset as f32));
|
||||||
|
|
||||||
|
self.quad_pipeline.draw(
|
||||||
|
&mut self.device,
|
||||||
|
encoder,
|
||||||
|
&layer.quads,
|
||||||
|
transformation,
|
||||||
|
target,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.image_pipeline.draw(
|
||||||
|
&mut self.device,
|
||||||
|
encoder,
|
||||||
|
&layer.images,
|
||||||
|
translated,
|
||||||
|
target,
|
||||||
|
);
|
||||||
|
|
||||||
|
for layer in layer.layers.iter() {
|
||||||
|
self.flush(transformation, layer, encoder, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
70
wgpu/src/renderer/scrollable.rs
Normal file
70
wgpu/src/renderer/scrollable.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
use crate::{Primitive, Renderer};
|
||||||
|
use iced_native::{
|
||||||
|
scrollable, Background, Color, Layout, Point, Rectangle, Scrollable, Widget,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl scrollable::Renderer for Renderer {
|
||||||
|
fn draw<Message>(
|
||||||
|
&mut self,
|
||||||
|
scrollable: &Scrollable<'_, Message, Self>,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
) -> Self::Output {
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let is_mouse_over = bounds.contains(cursor_position);
|
||||||
|
|
||||||
|
let content = layout.children().next().unwrap();
|
||||||
|
let content_bounds = content.bounds();
|
||||||
|
|
||||||
|
let cursor_position = if bounds.contains(cursor_position) {
|
||||||
|
Point::new(
|
||||||
|
cursor_position.x,
|
||||||
|
cursor_position.y + scrollable.state.offset as f32,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Point::new(cursor_position.x, -1.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (content, mouse_cursor) =
|
||||||
|
scrollable.content.draw(self, content, cursor_position);
|
||||||
|
|
||||||
|
let primitive = Primitive::Scrollable {
|
||||||
|
bounds,
|
||||||
|
offset: scrollable.state.offset,
|
||||||
|
content: Box::new(content),
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
Primitive::Group {
|
||||||
|
primitives: if is_mouse_over
|
||||||
|
&& content_bounds.height > bounds.height
|
||||||
|
{
|
||||||
|
let ratio = bounds.height / content_bounds.height;
|
||||||
|
let scrollbar_height = bounds.height * ratio;
|
||||||
|
let y_offset = scrollable.state.offset as f32 * ratio;
|
||||||
|
|
||||||
|
let scrollbar = Primitive::Quad {
|
||||||
|
bounds: Rectangle {
|
||||||
|
x: bounds.x + bounds.width - 12.0,
|
||||||
|
y: bounds.y + y_offset,
|
||||||
|
width: 10.0,
|
||||||
|
height: scrollbar_height,
|
||||||
|
},
|
||||||
|
background: Background::Color(Color {
|
||||||
|
r: 0.0,
|
||||||
|
g: 0.0,
|
||||||
|
b: 0.0,
|
||||||
|
a: 0.5,
|
||||||
|
}),
|
||||||
|
border_radius: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
vec![primitive, scrollbar]
|
||||||
|
} else {
|
||||||
|
vec![primitive]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mouse_cursor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,30 +1,59 @@
|
||||||
#[derive(Debug, Clone, Copy)]
|
use nalgebra::Matrix3;
|
||||||
pub struct Transformation([f32; 16]);
|
use std::ops::Mul;
|
||||||
|
|
||||||
|
/// A 2D transformation matrix.
|
||||||
|
///
|
||||||
|
/// It can be used to apply a transformation to a [`Target`].
|
||||||
|
///
|
||||||
|
/// [`Target`]: struct.Target.html
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct Transformation(Matrix3<f32>);
|
||||||
|
|
||||||
impl Transformation {
|
impl Transformation {
|
||||||
#[rustfmt::skip]
|
/// Get the identity transformation.
|
||||||
pub fn identity() -> Self {
|
pub fn identity() -> Transformation {
|
||||||
Transformation([
|
Transformation(Matrix3::identity())
|
||||||
1.0, 0.0, 0.0, 0.0,
|
|
||||||
0.0, 1.0, 0.0, 0.0,
|
|
||||||
0.0, 0.0, 1.0, 0.0,
|
|
||||||
0.0, 0.0, 0.0, 1.0,
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an orthographic projection.
|
||||||
|
///
|
||||||
|
/// You should rarely need this. On creation, a [`Target`] is automatically
|
||||||
|
/// set up with the correct orthographic projection.
|
||||||
|
///
|
||||||
|
/// [`Target`]: struct.Target.html
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub fn orthographic(width: u16, height: u16) -> Self {
|
pub fn orthographic(width: u16, height: u16) -> Transformation {
|
||||||
Transformation([
|
Transformation(nalgebra::Matrix3::new(
|
||||||
2.0 / width as f32, 0.0, 0.0, 0.0,
|
2.0 / f32::from(width), 0.0, -1.0,
|
||||||
0.0, 2.0 / height as f32, 0.0, 0.0,
|
0.0, 2.0 / f32::from(height), -1.0,
|
||||||
0.0, 0.0, 1.0, 0.0,
|
0.0, 0.0, 1.0
|
||||||
-1.0, -1.0, 0.0, 1.0,
|
))
|
||||||
])
|
}
|
||||||
|
|
||||||
|
/// Creates a translate transformation.
|
||||||
|
///
|
||||||
|
/// You can use this to pan your camera, for example.
|
||||||
|
pub fn translate(x: f32, y: f32) -> Transformation {
|
||||||
|
Transformation(Matrix3::new_translation(&nalgebra::Vector2::new(x, y)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul for Transformation {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn mul(self, rhs: Self) -> Self {
|
||||||
|
Transformation(self.0 * rhs.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Transformation> for [f32; 16] {
|
impl From<Transformation> for [f32; 16] {
|
||||||
fn from(transformation: Transformation) -> [f32; 16] {
|
#[rustfmt::skip]
|
||||||
transformation.0
|
fn from(t: Transformation) -> [f32; 16] {
|
||||||
|
[
|
||||||
|
t.0[0], t.0[1], 0.0, t.0[2],
|
||||||
|
t.0[3], t.0[4], 0.0, t.0[5],
|
||||||
|
0.0, 0.0, -1.0, 0.0,
|
||||||
|
t.0[6], t.0[7], 0.0, t.0[8]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,17 @@ pub trait Application {
|
||||||
state: conversion::button_state(state),
|
state: conversion::button_state(state),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
WindowEvent::MouseWheel { delta, .. } => match delta {
|
||||||
|
winit::event::MouseScrollDelta::LineDelta(
|
||||||
|
delta_x,
|
||||||
|
delta_y,
|
||||||
|
) => {
|
||||||
|
events.push(Event::Mouse(
|
||||||
|
mouse::Event::WheelScrolled { delta_x, delta_y },
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
WindowEvent::CloseRequested => {
|
WindowEvent::CloseRequested => {
|
||||||
*control_flow = ControlFlow::Exit;
|
*control_flow = ControlFlow::Exit;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue