From 719c073fc67c87d6b2da1bc01b74751d3f5e59f0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Fri, 25 Oct 2019 03:47:34 +0200 Subject: Draft `Scrollable` widget (no clipping yet!) --- core/src/widget.rs | 4 ++ core/src/widget/scrollable.rs | 122 ++++++++++++++++++++++++++++++++++++++++ examples/scroll.rs | 69 +++++++++++++++++++++++ native/src/user_interface.rs | 2 +- native/src/widget.rs | 3 + native/src/widget/scrollable.rs | 121 +++++++++++++++++++++++++++++++++++++++ src/winit.rs | 4 +- wgpu/Cargo.toml | 1 + wgpu/src/primitive.rs | 5 ++ wgpu/src/renderer.rs | 97 +++++++++++++++++++++++--------- wgpu/src/renderer/scrollable.rs | 70 +++++++++++++++++++++++ wgpu/src/transformation.rs | 67 +++++++++++++++------- winit/src/application.rs | 11 ++++ 13 files changed, 526 insertions(+), 50 deletions(-) create mode 100644 core/src/widget/scrollable.rs create mode 100644 examples/scroll.rs create mode 100644 native/src/widget/scrollable.rs create mode 100644 wgpu/src/renderer/scrollable.rs diff --git a/core/src/widget.rs b/core/src/widget.rs index f9d4bf2a..3ee8e347 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -14,6 +14,7 @@ mod radio; mod row; pub mod button; +pub mod scrollable; pub mod slider; pub mod text; @@ -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; diff --git a/core/src/widget/scrollable.rs b/core/src/widget/scrollable.rs new file mode 100644 index 00000000..dd62b658 --- /dev/null +++ b/core/src/widget/scrollable.rs @@ -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() + } +} diff --git a/examples/scroll.rs b/examples/scroll.rs new file mode 100644 index 00000000..648b1d8f --- /dev/null +++ b/examples/scroll.rs @@ -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.") +} diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 5675076d..5df0dc6a 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -347,7 +347,7 @@ impl Cache { .0 .compute_layout(geometry::Size::undefined()) .unwrap(), - cursor_position: Point::new(0.0, 0.0), + cursor_position: Point::new(-1.0, -1.0), } } } diff --git a/native/src/widget.rs b/native/src/widget.rs index bcef2665..0d3f6d2c 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -26,6 +26,7 @@ pub mod column; pub mod image; pub mod radio; pub mod row; +pub mod scrollable; pub mod slider; pub mod text; @@ -42,6 +43,8 @@ pub use radio::Radio; #[doc(no_inline)] pub use row::Row; #[doc(no_inline)] +pub use scrollable::Scrollable; +#[doc(no_inline)] pub use slider::Slider; #[doc(no_inline)] pub use text::Text; diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs new file mode 100644 index 00000000..4644282b --- /dev/null +++ b/native/src/widget/scrollable.rs @@ -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) + } +} diff --git a/src/winit.rs b/src/winit.rs index 64e301f4..b2200551 100644 --- a/src/winit.rs +++ b/src/winit.rs @@ -1,8 +1,8 @@ pub use iced_wgpu::{Primitive, Renderer}; pub use iced_winit::{ - button, slider, text, winit, Align, Background, Checkbox, Color, Image, - Justify, Length, Radio, Slider, Text, + button, scrollable, slider, text, winit, Align, Background, Checkbox, + Color, Image, Justify, Length, Radio, Scrollable, Slider, Text, }; pub type Element<'a, Message> = iced_winit::Element<'a, Message, Renderer>; diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index cac5e113..ffb15ea2 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -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" } raw-window-handle = "0.1" image = "0.22" +nalgebra = "0.18" log = "0.4" diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 0b9e2c41..cdd87894 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -23,4 +23,9 @@ pub enum Primitive { path: String, bounds: Rectangle, }, + Scrollable { + bounds: Rectangle, + offset: u32, + content: Box<Primitive>, + }, } diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index ab6f744f..bb0e7b27 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -20,6 +20,7 @@ mod column; mod image; mod radio; mod row; +mod scrollable; mod slider; mod text; @@ -31,8 +32,6 @@ pub struct Renderer { quad_pipeline: quad::Pipeline, image_pipeline: crate::image::Pipeline, - quads: Vec<Quad>, - images: Vec<Image>, glyph_brush: Rc<RefCell<GlyphBrush<'static, ()>>>, } @@ -43,6 +42,24 @@ pub struct Target { 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 { fn new<W: HasRawWindowHandle>(window: &W) -> Self { let adapter = Adapter::request(&RequestAdapterOptions { @@ -79,8 +96,6 @@ impl Renderer { quad_pipeline, image_pipeline, - quads: Vec::new(), - images: Vec::new(), glyph_brush: Rc::new(RefCell::new(glyph_brush)), } } @@ -132,27 +147,10 @@ impl Renderer { depth_stencil_attachment: None, }); - self.draw_primitive(primitive); + let mut layer = Layer::new(0); - self.quad_pipeline.draw( - &mut self.device, - &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.draw_primitive(primitive, &mut layer); + self.flush(target.transformation, &layer, &mut encoder, &frame.view); self.glyph_brush .borrow_mut() @@ -170,13 +168,13 @@ impl Renderer { *mouse_cursor } - fn draw_primitive(&mut self, primitive: &Primitive) { + fn draw_primitive(&mut self, primitive: &Primitive, layer: &mut Layer) { match primitive { Primitive::None => {} Primitive::Group { primitives } => { // TODO: Inspect a bit and regroup (?) for primitive in primitives { - self.draw_primitive(primitive) + self.draw_primitive(primitive, layer) } } Primitive::Text { @@ -244,7 +242,7 @@ impl Renderer { background, border_radius, } => { - self.quads.push(Quad { + layer.quads.push(Quad { position: [bounds.x, bounds.y], scale: [bounds.width, bounds.height], color: match background { @@ -254,12 +252,55 @@ impl Renderer { }); } Primitive::Image { path, bounds } => { - self.images.push(Image { + layer.images.push(Image { path: path.clone(), position: [bounds.x, bounds.y], 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); } } } diff --git a/wgpu/src/renderer/scrollable.rs b/wgpu/src/renderer/scrollable.rs new file mode 100644 index 00000000..14ff9ff4 --- /dev/null +++ b/wgpu/src/renderer/scrollable.rs @@ -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, + ) + } +} diff --git a/wgpu/src/transformation.rs b/wgpu/src/transformation.rs index 1101e135..ed80b31a 100644 --- a/wgpu/src/transformation.rs +++ b/wgpu/src/transformation.rs @@ -1,30 +1,59 @@ -#[derive(Debug, Clone, Copy)] -pub struct Transformation([f32; 16]); +use nalgebra::Matrix3; +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 { - #[rustfmt::skip] - pub fn identity() -> Self { - Transformation([ - 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, - ]) + /// Get the identity transformation. + pub fn identity() -> Transformation { + Transformation(Matrix3::identity()) } + /// 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] - pub fn orthographic(width: u16, height: u16) -> Self { - Transformation([ - 2.0 / width as f32, 0.0, 0.0, 0.0, - 0.0, 2.0 / height as f32, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - -1.0, -1.0, 0.0, 1.0, - ]) + pub fn orthographic(width: u16, height: u16) -> Transformation { + Transformation(nalgebra::Matrix3::new( + 2.0 / f32::from(width), 0.0, -1.0, + 0.0, 2.0 / f32::from(height), -1.0, + 0.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] { - fn from(transformation: Transformation) -> [f32; 16] { - transformation.0 + #[rustfmt::skip] + 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] + ] } } diff --git a/winit/src/application.rs b/winit/src/application.rs index 418ee3c4..870f4868 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -136,6 +136,17 @@ pub trait Application { 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 => { *control_flow = ControlFlow::Exit; } -- cgit From 09bd2c46c06eba72da40852d82a52e7353cc9e9b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Sun, 27 Oct 2019 01:24:08 +0200 Subject: Expose scrollable offset properly --- core/src/widget/scrollable.rs | 28 ++++++++++++++++++++++++++-- examples/scroll.rs | 21 ++++++++++----------- native/src/widget/scrollable.rs | 10 +++------- wgpu/src/renderer/scrollable.rs | 11 +++++------ 4 files changed, 44 insertions(+), 26 deletions(-) diff --git a/core/src/widget/scrollable.rs b/core/src/widget/scrollable.rs index dd62b658..4e5c4ad0 100644 --- a/core/src/widget/scrollable.rs +++ b/core/src/widget/scrollable.rs @@ -1,4 +1,4 @@ -use crate::{Align, Column, Justify, Length}; +use crate::{Align, Column, Justify, Length, Rectangle}; #[derive(Debug)] pub struct Scrollable<'a, Element> { @@ -112,11 +112,35 @@ impl<'a, Element> Scrollable<'a, Element> { #[derive(Debug, Clone, Copy, Default)] pub struct State { - pub offset: u32, + 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; + } + + // TODO: Configurable speed (?) + self.offset = (self.offset as i32 - delta_y.round() as i32 * 15) + .max(0) + .min((content_bounds.height - bounds.height) as i32) + 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) + } } diff --git a/examples/scroll.rs b/examples/scroll.rs index 648b1d8f..2f250ff8 100644 --- a/examples/scroll.rs +++ b/examples/scroll.rs @@ -1,6 +1,6 @@ use iced::{ - button, scrollable, Align, Application, Button, Color, Element, Image, - Length, Scrollable, Text, + button, scrollable, Align, Application, Button, Color, Column, Element, + Image, Justify, Length, Scrollable, Text, }; pub fn main() { @@ -32,12 +32,7 @@ impl Application for Example { } 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 = Scrollable::new(&mut self.scroll).spacing(20).padding(20); //let content = (0..self.paragraph_count) // .fold(content, |column, _| column.push(lorem_ipsum())) @@ -49,8 +44,12 @@ impl Application for Example { // .align_self(Align::Center), // ); - (0..10) - .fold(content, |content, _| { + Column::new() + .height(Length::Fill) + .max_width(Length::Units(600)) + .align_self(Align::Center) + .justify_content(Justify::Center) + .push((0..3).fold(content, |content, _| { content.push( Image::new(format!( "{}/examples/resources/ferris.png", @@ -59,7 +58,7 @@ impl Application for Example { .width(Length::Units(400)) .align_self(Align::Center), ) - }) + })) .into() } } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 4644282b..f411915b 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -56,12 +56,7 @@ where 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; + self.state.scroll(delta_y, bounds, content_bounds); } _ => {} } @@ -70,7 +65,8 @@ where let cursor_position = if is_mouse_over { Point::new( cursor_position.x, - cursor_position.y + self.state.offset as f32, + cursor_position.y + + self.state.offset(bounds, content_bounds) as f32, ) } else { Point::new(cursor_position.x, -1.0) diff --git a/wgpu/src/renderer/scrollable.rs b/wgpu/src/renderer/scrollable.rs index 14ff9ff4..5b93a765 100644 --- a/wgpu/src/renderer/scrollable.rs +++ b/wgpu/src/renderer/scrollable.rs @@ -16,11 +16,10 @@ impl scrollable::Renderer for Renderer { let content = layout.children().next().unwrap(); let content_bounds = content.bounds(); + let offset = scrollable.state.offset(bounds, content_bounds); + let cursor_position = if bounds.contains(cursor_position) { - Point::new( - cursor_position.x, - cursor_position.y + scrollable.state.offset as f32, - ) + Point::new(cursor_position.x, cursor_position.y + offset as f32) } else { Point::new(cursor_position.x, -1.0) }; @@ -30,7 +29,7 @@ impl scrollable::Renderer for Renderer { let primitive = Primitive::Scrollable { bounds, - offset: scrollable.state.offset, + offset, content: Box::new(content), }; @@ -41,7 +40,7 @@ impl scrollable::Renderer for Renderer { { let ratio = bounds.height / content_bounds.height; let scrollbar_height = bounds.height * ratio; - let y_offset = scrollable.state.offset as f32 * ratio; + let y_offset = offset as f32 * ratio; let scrollbar = Primitive::Quad { bounds: Rectangle { -- cgit From 63c10b67ab213c5971313743fde566bd5c0f0c15 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Sun, 27 Oct 2019 01:37:40 +0200 Subject: Remove `Scrollable::justify_content` method --- core/src/widget/scrollable.rs | 47 ++++++++++++++++++----------------------- native/src/widget/scrollable.rs | 1 + 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/core/src/widget/scrollable.rs b/core/src/widget/scrollable.rs index 4e5c4ad0..ef3bc1db 100644 --- a/core/src/widget/scrollable.rs +++ b/core/src/widget/scrollable.rs @@ -1,9 +1,10 @@ -use crate::{Align, Column, Justify, Length, Rectangle}; +use crate::{Align, Column, Length, 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 align_items: Align, pub content: Column<Element>, @@ -14,6 +15,7 @@ impl<'a, Element> Scrollable<'a, Element> { Scrollable { state, height: Length::Shrink, + max_height: Length::Shrink, align_self: None, align_items: Align::Start, content: Column::new(), @@ -30,77 +32,68 @@ impl<'a, Element> Scrollable<'a, Element> { self } - /// Sets the padding of the [`Column`]. + /// Sets the padding of the [`Scrollable`]. /// - /// [`Column`]: struct.Column.html + /// [`Scrollable`]: struct.Scrollable.html pub fn padding(mut self, units: u16) -> Self { self.content = self.content.padding(units); self } - /// Sets the width of the [`Column`]. + /// Sets the width of the [`Scrollable`]. /// - /// [`Column`]: struct.Column.html + /// [`Scrollable`]: struct.Scrollable.html pub fn width(mut self, width: Length) -> Self { self.content = self.content.width(width); self } - /// Sets the height of the [`Column`]. + /// Sets the height of the [`Scrollable`]. /// - /// [`Column`]: struct.Column.html + /// [`Scrollable`]: struct.Scrollable.html pub fn height(mut self, height: Length) -> Self { self.height = height; self } - /// Sets the maximum width of the [`Column`]. + /// Sets the maximum width of the [`Scrollable`]. /// - /// [`Column`]: struct.Column.html + /// [`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 [`Column`] in pixels. + /// Sets the maximum height of the [`Scrollable`] in pixels. /// - /// [`Column`]: struct.Column.html + /// [`Scrollable`]: struct.Scrollable.html pub fn max_height(mut self, max_height: Length) -> Self { - self.content = self.content.max_height(max_height); + self.max_height = max_height; self } - /// Sets the alignment of the [`Column`] itself. + /// Sets the alignment of the [`Scrollable`] itself. /// /// This is useful if you want to override the default alignment given by /// the parent container. /// - /// [`Column`]: struct.Column.html + /// [`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 [`Column`] . + /// Sets the horizontal alignment of the contents of the [`Scrollable`] . /// - /// [`Column`]: struct.Column.html + /// [`Scrollable`]: struct.Scrollable.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`] . + /// Adds an element to the [`Scrollable`]. /// - /// [`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 + /// [`Scrollable`]: struct.Scrollable.html pub fn push<E>(mut self, child: E) -> Scrollable<'a, Element> where E: Into<Element>, diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index f411915b..e0983e6e 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -30,6 +30,7 @@ where .width(self.content.width) .max_width(self.content.max_width) .height(self.height) + .max_height(self.max_height) .align_self(self.align_self) .align_items(self.align_items); -- cgit From 0a0aa3edd9c5185551040c75a934f12d3bce7618 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Sun, 27 Oct 2019 02:29:23 +0100 Subject: Implement clipping for images --- examples/scroll.rs | 28 +++++++++++++++------------- wgpu/src/image.rs | 8 ++++++++ wgpu/src/renderer.rs | 31 +++++++++++++++++++++++++------ 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/examples/scroll.rs b/examples/scroll.rs index 2f250ff8..450dae11 100644 --- a/examples/scroll.rs +++ b/examples/scroll.rs @@ -32,7 +32,19 @@ impl Application for Example { } fn view(&mut self) -> Element<Message> { - let content = Scrollable::new(&mut self.scroll).spacing(20).padding(20); + let content = (0..3).fold( + Scrollable::new(&mut self.scroll).spacing(20).padding(20), + |content, _| { + content.push( + Image::new(format!( + "{}/examples/resources/ferris.png", + env!("CARGO_MANIFEST_DIR") + )) + .width(Length::Units(400)) + .align_self(Align::Center), + ) + }, + ); //let content = (0..self.paragraph_count) // .fold(content, |column, _| column.push(lorem_ipsum())) @@ -46,19 +58,9 @@ impl Application for Example { Column::new() .height(Length::Fill) - .max_width(Length::Units(600)) - .align_self(Align::Center) .justify_content(Justify::Center) - .push((0..3).fold(content, |content, _| { - content.push( - Image::new(format!( - "{}/examples/resources/ferris.png", - env!("CARGO_MANIFEST_DIR") - )) - .width(Length::Units(400)) - .align_self(Align::Center), - ) - })) + .padding(20) + .push(content) .into() } } diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index c883eaa8..c42e1cd4 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,4 +1,5 @@ use crate::Transformation; +use iced_native::Rectangle; use std::cell::RefCell; use std::collections::HashMap; @@ -218,6 +219,7 @@ impl Pipeline { encoder: &mut wgpu::CommandEncoder, instances: &[Image], transformation: Transformation, + bounds: Rectangle<u32>, target: &wgpu::TextureView, ) { let matrix: [f32; 16] = transformation.into(); @@ -291,6 +293,12 @@ impl Pipeline { 0, &[(&self.vertices, 0), (&self.instances, 0)], ); + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); render_pass.draw_indexed( 0..QUAD_INDICES.len() as u32, diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index bb0e7b27..d838c6ee 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,7 +1,7 @@ use crate::{quad, Image, Primitive, Quad, Transformation}; use iced_native::{ renderer::Debugger, renderer::Windowed, Background, Color, Layout, - MouseCursor, Point, Widget, + MouseCursor, Point, Rectangle, Widget, }; use raw_window_handle::HasRawWindowHandle; @@ -43,19 +43,21 @@ pub struct Target { } pub struct Layer { + bounds: Rectangle<u32>, + y_offset: u32, quads: Vec<Quad>, images: Vec<Image>, layers: Vec<Layer>, - y_offset: u32, } impl Layer { - pub fn new(y_offset: u32) -> Self { + pub fn new(bounds: Rectangle<u32>, y_offset: u32) -> Self { Self { + bounds, + y_offset, quads: Vec::new(), images: Vec::new(), layers: Vec::new(), - y_offset, } } } @@ -147,7 +149,15 @@ impl Renderer { depth_stencil_attachment: None, }); - let mut layer = Layer::new(0); + let mut layer = Layer::new( + Rectangle { + x: 0, + y: 0, + width: u32::from(target.width), + height: u32::from(target.height), + }, + 0, + ); self.draw_primitive(primitive, &mut layer); self.flush(target.transformation, &layer, &mut encoder, &frame.view); @@ -263,7 +273,15 @@ impl Renderer { offset, content, } => { - let mut new_layer = Layer::new(layer.y_offset + offset); + let mut new_layer = Layer::new( + Rectangle { + x: bounds.x as u32, + y: bounds.y as u32 - layer.y_offset, + width: bounds.width as u32, + height: bounds.height as u32, + }, + layer.y_offset + offset, + ); // TODO: Primitive culling self.draw_primitive(content, &mut new_layer); @@ -296,6 +314,7 @@ impl Renderer { encoder, &layer.images, translated, + layer.bounds, target, ); -- cgit From e2668b882a8115fd0afcd32373edb180492908b1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Sun, 27 Oct 2019 02:30:19 +0100 Subject: Remove `adapter` from `iced_wgpu::Renderer` --- wgpu/src/renderer.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index d838c6ee..5bd7be8d 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -26,7 +26,6 @@ mod text; pub struct Renderer { surface: Surface, - adapter: Adapter, device: Device, queue: Queue, quad_pipeline: quad::Pipeline, @@ -92,7 +91,6 @@ impl Renderer { Self { surface, - adapter, device, queue, quad_pipeline, -- cgit From 82c2aa6bfd1ed90b32b303a900e13b2c07bc69ba Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Sun, 27 Oct 2019 02:59:25 +0100 Subject: Align items properly inside a `Scrollable` --- core/src/widget/scrollable.rs | 4 +--- native/src/widget/scrollable.rs | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/core/src/widget/scrollable.rs b/core/src/widget/scrollable.rs index ef3bc1db..1bad8555 100644 --- a/core/src/widget/scrollable.rs +++ b/core/src/widget/scrollable.rs @@ -6,7 +6,6 @@ pub struct Scrollable<'a, Element> { pub height: Length, pub max_height: Length, pub align_self: Option<Align>, - pub align_items: Align, pub content: Column<Element>, } @@ -17,7 +16,6 @@ impl<'a, Element> Scrollable<'a, Element> { height: Length::Shrink, max_height: Length::Shrink, align_self: None, - align_items: Align::Start, content: Column::new(), } } @@ -87,7 +85,7 @@ impl<'a, Element> Scrollable<'a, Element> { /// /// [`Scrollable`]: struct.Scrollable.html pub fn align_items(mut self, align_items: Align) -> Self { - self.align_items = align_items; + self.content = self.content.align_items(align_items); self } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index e0983e6e..95e8c74d 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -31,8 +31,7 @@ where .max_width(self.content.max_width) .height(self.height) .max_height(self.max_height) - .align_self(self.align_self) - .align_items(self.align_items); + .align_self(self.align_self); style.0.flex_direction = stretch::style::FlexDirection::Column; -- cgit From e21890168f3db64fb6bb9aa5e1de974f5fad1c68 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Sun, 27 Oct 2019 03:04:07 +0100 Subject: Improve `scroll` example --- examples/scroll.rs | 67 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/examples/scroll.rs b/examples/scroll.rs index 450dae11..b1e427bf 100644 --- a/examples/scroll.rs +++ b/examples/scroll.rs @@ -1,6 +1,6 @@ use iced::{ - button, scrollable, Align, Application, Button, Color, Column, Element, - Image, Justify, Length, Scrollable, Text, + button, scrollable, Align, Application, Button, Column, Element, Image, + Justify, Length, Scrollable, Text, }; pub fn main() { @@ -9,7 +9,7 @@ pub fn main() { #[derive(Default)] struct Example { - paragraph_count: u16, + item_count: u16, scroll: scrollable::State, add_button: button::State, @@ -17,7 +17,7 @@ struct Example { #[derive(Debug, Clone, Copy)] pub enum Message { - AddParagraph, + AddItem, } impl Application for Example { @@ -25,36 +25,47 @@ impl Application for Example { fn update(&mut self, message: Message) { match message { - Message::AddParagraph => { - self.paragraph_count += 1; + Message::AddItem => { + self.item_count += 1; } } } fn view(&mut self) -> Element<Message> { - let content = (0..3).fold( - Scrollable::new(&mut self.scroll).spacing(20).padding(20), - |content, _| { - content.push( - Image::new(format!( - "{}/examples/resources/ferris.png", - env!("CARGO_MANIFEST_DIR") - )) - .width(Length::Units(400)) - .align_self(Align::Center), - ) - }, - ); + //let content = (0..3).fold( + // Scrollable::new(&mut self.scroll).spacing(20).padding(20), + // |content, _| { + // content.push( + // ) + // }, + //); - //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), - // ); + let content = (0..self.item_count) + .fold( + Scrollable::new(&mut self.scroll) + .spacing(20) + .padding(20) + .align_items(Align::Center), + |column, i| { + if i % 2 == 0 { + column.push(lorem_ipsum().width(Length::Units(600))) + } else { + column.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) -- cgit From 21eb2f692c687a675c54ae5e951556e28e7435eb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Sun, 27 Oct 2019 03:10:49 +0100 Subject: Implement clipping for quads --- wgpu/src/quad.rs | 8 ++++++++ wgpu/src/renderer.rs | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index adb294f0..6365e117 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,4 +1,5 @@ use crate::Transformation; +use iced_native::Rectangle; use std::mem; @@ -165,6 +166,7 @@ impl Pipeline { encoder: &mut wgpu::CommandEncoder, instances: &[Quad], transformation: Transformation, + bounds: Rectangle<u32>, target: &wgpu::TextureView, ) { let matrix: [f32; 16] = transformation.into(); @@ -227,6 +229,12 @@ impl Pipeline { 0, &[(&self.vertices, 0), (&self.instances, 0)], ); + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); render_pass.draw_indexed( 0..QUAD_INDICES.len() as u32, diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 5bd7be8d..ba140a66 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -251,7 +251,7 @@ impl Renderer { border_radius, } => { layer.quads.push(Quad { - position: [bounds.x, bounds.y], + position: [bounds.x, bounds.y - layer.y_offset as f32], scale: [bounds.width, bounds.height], color: match background { Background::Color(color) => color.into_linear(), @@ -304,6 +304,7 @@ impl Renderer { encoder, &layer.quads, transformation, + layer.bounds, target, ); -- cgit From 35e94f5a324f5c28de855b725039733efb21b26a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Sun, 27 Oct 2019 03:11:54 +0100 Subject: Draft text scrolling (no clipping yet!) --- wgpu/src/renderer.rs | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index ba140a66..cfdd7a45 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -41,21 +41,23 @@ pub struct Target { swap_chain: SwapChain, } -pub struct Layer { +pub struct Layer<'a> { bounds: Rectangle<u32>, y_offset: u32, quads: Vec<Quad>, images: Vec<Image>, - layers: Vec<Layer>, + text: Vec<wgpu_glyph::Section<'a>>, + layers: Vec<Layer<'a>>, } -impl Layer { +impl<'a> Layer<'a> { pub fn new(bounds: Rectangle<u32>, y_offset: u32) -> Self { Self { bounds, y_offset, quads: Vec::new(), images: Vec::new(), + text: Vec::new(), layers: Vec::new(), } } @@ -176,7 +178,11 @@ impl Renderer { *mouse_cursor } - fn draw_primitive(&mut self, primitive: &Primitive, layer: &mut Layer) { + fn draw_primitive<'a>( + &mut self, + primitive: &'a Primitive, + layer: &mut Layer<'a>, + ) { match primitive { Primitive::None => {} Primitive::Group { primitives } => { @@ -213,7 +219,7 @@ impl Renderer { } }; - self.glyph_brush.borrow_mut().queue(Section { + layer.text.push(Section { text: &content, screen_position: (x, y), bounds: (bounds.width, bounds.height), @@ -317,6 +323,23 @@ impl Renderer { target, ); + { + let mut glyph_brush = self.glyph_brush.borrow_mut(); + + for text in layer.text.iter() { + glyph_brush.queue(text); + } + + glyph_brush + .draw_queued_with_transform( + &mut self.device, + encoder, + target, + translated.into(), + ) + .expect("Draw text"); + } + for layer in layer.layers.iter() { self.flush(transformation, layer, encoder, target); } -- cgit From 2b23e0986c532dbacd89ccd73bb603db558cbdaf Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Mon, 28 Oct 2019 04:28:21 +0100 Subject: Implement text clipping (caching still broken) --- examples/scroll.rs | 16 +++++----------- native/Cargo.toml | 2 +- wgpu/Cargo.toml | 6 +++--- wgpu/src/renderer.rs | 21 ++++++++------------- winit/Cargo.toml | 2 +- 5 files changed, 18 insertions(+), 29 deletions(-) diff --git a/examples/scroll.rs b/examples/scroll.rs index b1e427bf..608923fe 100644 --- a/examples/scroll.rs +++ b/examples/scroll.rs @@ -4,6 +4,8 @@ use iced::{ }; pub fn main() { + env_logger::init(); + Example::default().run() } @@ -32,25 +34,17 @@ impl Application for Example { } fn view(&mut self) -> Element<Message> { - //let content = (0..3).fold( - // Scrollable::new(&mut self.scroll).spacing(20).padding(20), - // |content, _| { - // content.push( - // ) - // }, - //); - let content = (0..self.item_count) .fold( Scrollable::new(&mut self.scroll) .spacing(20) .padding(20) .align_items(Align::Center), - |column, i| { + |scrollable, i| { if i % 2 == 0 { - column.push(lorem_ipsum().width(Length::Units(600))) + scrollable.push(lorem_ipsum().width(Length::Units(600))) } else { - column.push( + scrollable.push( Image::new(format!( "{}/examples/resources/ferris.png", env!("CARGO_MANIFEST_DIR") diff --git a/native/Cargo.toml b/native/Cargo.toml index 8cabe94c..bb6139d6 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -11,4 +11,4 @@ repository = "https://github.com/hecrj/iced" iced_core = { version = "0.1.0-alpha", path = "../core" } stretch = "0.2" twox-hash = "1.5" -raw-window-handle = "0.1" +raw-window-handle = "0.3" diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index ffb15ea2..30f9224f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -9,9 +9,9 @@ repository = "https://github.com/hecrj/iced" [dependencies] iced_native = { version = "0.1.0-alpha", path = "../native" } -wgpu = { version = "0.3", git = "https://github.com/gfx-rs/wgpu-rs", rev = "cb25914b95b58fee0dc139b400867e7a731d98f4" } -wgpu_glyph = { version = "0.4", git = "https://github.com/hecrj/wgpu_glyph", rev = "48daa98f5f785963838b4345e86ac40eac095ba9" } -raw-window-handle = "0.1" +wgpu = { version = "0.3", git = "https://github.com/gfx-rs/wgpu-rs", rev = "ed2c67f762970d0099c1e6c6e078fb645afbf964" } +wgpu_glyph = { version = "0.4", git = "https://github.com/hecrj/wgpu_glyph", rev = "954ac865ca1b7f6b97bf403f8c6174a7120e667c" } +raw-window-handle = "0.3" image = "0.22" nalgebra = "0.18" log = "0.4" diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index cfdd7a45..0f91428e 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -75,7 +75,7 @@ impl Renderer { extensions: Extensions { anisotropic_filtering: false, }, - limits: Limits { max_bind_groups: 1 }, + limits: Limits { max_bind_groups: 2 }, }); let surface = Surface::create(window); @@ -162,17 +162,6 @@ impl Renderer { self.draw_primitive(primitive, &mut layer); self.flush(target.transformation, &layer, &mut encoder, &frame.view); - self.glyph_brush - .borrow_mut() - .draw_queued( - &mut self.device, - &mut encoder, - &frame.view, - u32::from(target.width), - u32::from(target.height), - ) - .expect("Draw text"); - self.queue.submit(&[encoder.finish()]); *mouse_cursor @@ -331,11 +320,17 @@ impl Renderer { } glyph_brush - .draw_queued_with_transform( + .draw_queued_with_transform_and_scissoring( &mut self.device, encoder, target, translated.into(), + wgpu_glyph::Region { + x: layer.bounds.x, + y: layer.bounds.y, + width: layer.bounds.width, + height: layer.bounds.height, + }, ) .expect("Draw text"); } diff --git a/winit/Cargo.toml b/winit/Cargo.toml index fa5d6adf..c8227ac4 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -9,5 +9,5 @@ repository = "https://github.com/hecrj/iced" [dependencies] iced_native = { version = "0.1.0-alpha", path = "../native" } -winit = { version = "0.20.0-alpha3", git = "https://github.com/hecrj/winit", branch = "redraw-requested-2.0" } +winit = { version = "0.20.0-alpha3", git = "https://github.com/rust-windowing/winit", rev = "709808eb4e69044705fcb214bcc30556db761405"} log = "0.4" -- cgit From 23ebfb707a52d03a7beceaa5e197b4491619ae1d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Tue, 29 Oct 2019 01:21:28 +0100 Subject: Issue draw calls only when necessary --- wgpu/src/renderer.rs | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 0f91428e..bb7cb858 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -294,25 +294,29 @@ impl Renderer { let translated = transformation * Transformation::translate(0.0, -(layer.y_offset as f32)); - self.quad_pipeline.draw( - &mut self.device, - encoder, - &layer.quads, - transformation, - layer.bounds, - target, - ); + if layer.quads.len() > 0 { + self.quad_pipeline.draw( + &mut self.device, + encoder, + &layer.quads, + transformation, + layer.bounds, + target, + ); + } - self.image_pipeline.draw( - &mut self.device, - encoder, - &layer.images, - translated, - layer.bounds, - target, - ); + if layer.images.len() > 0 { + self.image_pipeline.draw( + &mut self.device, + encoder, + &layer.images, + translated, + layer.bounds, + target, + ); + } - { + if layer.text.len() > 0 { let mut glyph_brush = self.glyph_brush.borrow_mut(); for text in layer.text.iter() { -- cgit From be488ac73837c9a741d900617840ee5c4ed74d61 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Tue, 29 Oct 2019 02:00:17 +0100 Subject: Draw scrollbar on top of scrollable content --- wgpu/src/renderer.rs | 29 +++++++++++++---------- wgpu/src/renderer/scrollable.rs | 51 +++++++++++++++++++---------------------- 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index bb7cb858..f0be0860 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -47,7 +47,6 @@ pub struct Layer<'a> { quads: Vec<Quad>, images: Vec<Image>, text: Vec<wgpu_glyph::Section<'a>>, - layers: Vec<Layer<'a>>, } impl<'a> Layer<'a> { @@ -58,7 +57,6 @@ impl<'a> Layer<'a> { quads: Vec::new(), images: Vec::new(), text: Vec::new(), - layers: Vec::new(), } } } @@ -149,7 +147,8 @@ impl Renderer { depth_stencil_attachment: None, }); - let mut layer = Layer::new( + let mut layers = Vec::new(); + let mut current = Layer::new( Rectangle { x: 0, y: 0, @@ -159,8 +158,17 @@ impl Renderer { 0, ); - self.draw_primitive(primitive, &mut layer); - self.flush(target.transformation, &layer, &mut encoder, &frame.view); + self.draw_primitive(primitive, &mut current, &mut layers); + layers.push(current); + + for layer in layers { + self.flush( + target.transformation, + &layer, + &mut encoder, + &frame.view, + ); + } self.queue.submit(&[encoder.finish()]); @@ -171,13 +179,14 @@ impl Renderer { &mut self, primitive: &'a Primitive, layer: &mut Layer<'a>, + layers: &mut Vec<Layer<'a>>, ) { match primitive { Primitive::None => {} Primitive::Group { primitives } => { // TODO: Inspect a bit and regroup (?) for primitive in primitives { - self.draw_primitive(primitive, layer) + self.draw_primitive(primitive, layer, layers) } } Primitive::Text { @@ -277,9 +286,9 @@ impl Renderer { ); // TODO: Primitive culling - self.draw_primitive(content, &mut new_layer); + self.draw_primitive(content, &mut new_layer, layers); - layer.layers.push(new_layer); + layers.push(new_layer); } } } @@ -338,10 +347,6 @@ impl Renderer { ) .expect("Draw text"); } - - for layer in layer.layers.iter() { - self.flush(transformation, layer, encoder, target); - } } } diff --git a/wgpu/src/renderer/scrollable.rs b/wgpu/src/renderer/scrollable.rs index 5b93a765..43dddeed 100644 --- a/wgpu/src/renderer/scrollable.rs +++ b/wgpu/src/renderer/scrollable.rs @@ -34,34 +34,31 @@ impl scrollable::Renderer for Renderer { }; ( - 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 = offset as f32 * ratio; + 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 = 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] - }, + 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.7, + }), + border_radius: 5, + }; + Primitive::Group { + primitives: vec![primitive, scrollbar], + } + } else { + primitive }, mouse_cursor, ) -- cgit From a3c55f75174f9bc87f80d7fe6236a71138d2fd77 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Tue, 29 Oct 2019 02:13:22 +0100 Subject: Stop leaking impl details in scrollable `Renderer` --- native/src/widget/scrollable.rs | 18 ++++++++++++++---- wgpu/src/renderer/scrollable.rs | 6 ++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 95e8c74d..f9d75863 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -1,6 +1,6 @@ use crate::{ - column, input::mouse, Element, Event, Hasher, Layout, Node, Point, Style, - Widget, + column, input::mouse, Element, Event, Hasher, Layout, Node, Point, + Rectangle, Style, Widget, }; pub use iced_core::scrollable::State; @@ -86,7 +86,16 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - self::Renderer::draw(renderer, &self, layout, cursor_position) + let bounds = layout.bounds(); + let content_layout = layout.children().next().unwrap(); + + self::Renderer::draw( + renderer, + &self, + bounds, + content_layout, + cursor_position, + ) } fn hash_layout(&self, state: &mut Hasher) { @@ -98,7 +107,8 @@ pub trait Renderer: crate::Renderer + Sized { fn draw<Message>( &mut self, scrollable: &Scrollable<'_, Message, Self>, - layout: Layout<'_>, + bounds: Rectangle, + content_layout: Layout<'_>, cursor_position: Point, ) -> Self::Output; } diff --git a/wgpu/src/renderer/scrollable.rs b/wgpu/src/renderer/scrollable.rs index 43dddeed..1327e577 100644 --- a/wgpu/src/renderer/scrollable.rs +++ b/wgpu/src/renderer/scrollable.rs @@ -7,13 +7,11 @@ impl scrollable::Renderer for Renderer { fn draw<Message>( &mut self, scrollable: &Scrollable<'_, Message, Self>, - layout: Layout<'_>, + bounds: Rectangle, + content: 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 offset = scrollable.state.offset(bounds, content_bounds); -- cgit From 6602c1517cbffbc9ff0b6052ce7288cd51eb1e67 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Tue, 29 Oct 2019 03:29:29 +0100 Subject: Complete `Scrollable::hash_layout` --- native/src/widget/scrollable.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index f9d75863..9acba3c2 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -5,6 +5,8 @@ use crate::{ pub use iced_core::scrollable::State; +use std::hash::Hash; + /// A scrollable [`Column`]. /// /// [`Column`]: ../column/struct.Column.html @@ -69,15 +71,15 @@ where + self.state.offset(bounds, content_bounds) as f32, ) } else { + // TODO: Make `cursor_position` an `Option<Point>` so we can encode + // cursor unavailability. + // This will probably happen naturally once we add multi-window + // support. Point::new(cursor_position.x, -1.0) }; - self.content.on_event( - event, - layout.children().next().unwrap(), - cursor_position, - messages, - ) + self.content + .on_event(event, content, cursor_position, messages) } fn draw( @@ -99,6 +101,12 @@ where } fn hash_layout(&self, state: &mut Hasher) { + std::any::TypeId::of::<Scrollable<'static, (), ()>>().hash(state); + + self.height.hash(state); + self.max_height.hash(state); + self.align_self.hash(state); + self.content.hash_layout(state) } } -- cgit From 9dabbf78857c3a60583227d3aa2fa6e030f085d0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Tue, 29 Oct 2019 03:34:21 +0100 Subject: Provide `Renderer` to `Widget::on_event` This allows us to implement configurable event processing that adapts to different rendering strategies. --- native/src/element.rs | 13 ++++++++++--- native/src/user_interface.rs | 6 ++++-- native/src/widget.rs | 1 + native/src/widget/button.rs | 1 + native/src/widget/checkbox.rs | 1 + native/src/widget/column.rs | 11 ++++++++--- native/src/widget/radio.rs | 1 + native/src/widget/row.rs | 11 ++++++++--- native/src/widget/scrollable.rs | 10 ++++++++-- native/src/widget/slider.rs | 1 + winit/src/application.rs | 3 ++- 11 files changed, 45 insertions(+), 14 deletions(-) diff --git a/native/src/element.rs b/native/src/element.rs index bbedd942..c638c713 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -299,6 +299,7 @@ where layout: Layout<'_>, cursor_position: Point, messages: &mut Vec<B>, + renderer: &Renderer, ) { let mut original_messages = Vec::new(); @@ -307,6 +308,7 @@ where layout, cursor_position, &mut original_messages, + renderer, ); original_messages @@ -369,10 +371,15 @@ where layout: Layout<'_>, cursor_position: Point, messages: &mut Vec<Message>, + renderer: &Renderer, ) { - self.element - .widget - .on_event(event, layout, cursor_position, messages) + self.element.widget.on_event( + event, + layout, + cursor_position, + messages, + renderer, + ) } fn draw( diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 5df0dc6a..0760dd7e 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -186,7 +186,7 @@ where /// ); /// /// // Update the user interface - /// let messages = user_interface.update(events.drain(..)); + /// let messages = user_interface.update(&renderer, events.drain(..)); /// /// cache = user_interface.into_cache(); /// @@ -198,6 +198,7 @@ where /// ``` pub fn update( &mut self, + renderer: &Renderer, events: impl Iterator<Item = Event>, ) -> Vec<Message> { let mut messages = Vec::new(); @@ -212,6 +213,7 @@ where Layout::new(&self.layout), self.cursor_position, &mut messages, + renderer, ); } @@ -281,7 +283,7 @@ where /// &mut renderer, /// ); /// - /// let messages = user_interface.update(events.drain(..)); + /// let messages = user_interface.update(&renderer, events.drain(..)); /// /// // Draw the user interface /// let mouse_cursor = user_interface.draw(&mut renderer); diff --git a/native/src/widget.rs b/native/src/widget.rs index 0d3f6d2c..c04f3377 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -117,6 +117,7 @@ where _layout: Layout<'_>, _cursor_position: Point, _messages: &mut Vec<Message>, + _renderer: &Renderer, ) { } } diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 4ab59f7f..cf5dba93 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -31,6 +31,7 @@ where layout: Layout<'_>, cursor_position: Point, messages: &mut Vec<Message>, + _renderer: &Renderer, ) { match event { Event::Mouse(mouse::Event::Input { diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 5393417e..b8053238 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -20,6 +20,7 @@ where layout: Layout<'_>, cursor_position: Point, messages: &mut Vec<Message>, + _renderer: &Renderer, ) { match event { Event::Mouse(mouse::Event::Input { diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 7995cf5d..086d05ef 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -55,12 +55,17 @@ where layout: Layout<'_>, cursor_position: Point, messages: &mut Vec<Message>, + renderer: &Renderer, ) { self.children.iter_mut().zip(layout.children()).for_each( |(child, layout)| { - child - .widget - .on_event(event, layout, cursor_position, messages) + child.widget.on_event( + event, + layout, + cursor_position, + messages, + renderer, + ) }, ); } diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 27b8f8a8..93ec4a36 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -21,6 +21,7 @@ where layout: Layout<'_>, cursor_position: Point, messages: &mut Vec<Message>, + _renderer: &Renderer, ) { match event { Event::Mouse(mouse::Event::Input { diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 5ec27159..7dbfb92a 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -55,12 +55,17 @@ where layout: Layout<'_>, cursor_position: Point, messages: &mut Vec<Message>, + renderer: &Renderer, ) { self.children.iter_mut().zip(layout.children()).for_each( |(child, layout)| { - child - .widget - .on_event(event, layout, cursor_position, messages) + child.widget.on_event( + event, + layout, + cursor_position, + messages, + renderer, + ) }, ); } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 9acba3c2..e52f3c3f 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -46,6 +46,7 @@ where layout: Layout<'_>, cursor_position: Point, messages: &mut Vec<Message>, + renderer: &Renderer, ) { let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); @@ -78,8 +79,13 @@ where Point::new(cursor_position.x, -1.0) }; - self.content - .on_event(event, content, cursor_position, messages) + self.content.on_event( + event, + content, + cursor_position, + messages, + renderer, + ) } fn draw( diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index d643d902..be2b9b22 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -25,6 +25,7 @@ where layout: Layout<'_>, cursor_position: Point, messages: &mut Vec<Message>, + _renderer: &Renderer, ) { let mut change = || { let bounds = layout.bounds(); diff --git a/winit/src/application.rs b/winit/src/application.rs index 870f4868..855968aa 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -68,7 +68,8 @@ pub trait Application { &renderer, ); - let messages = user_interface.update(events.drain(..)); + let messages = + user_interface.update(&renderer, events.drain(..)); if messages.is_empty() { primitive = user_interface.draw(&mut renderer); -- cgit From 29588f604af66fb4911f791c0c402fccd30ba64b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Tue, 29 Oct 2019 05:09:54 +0100 Subject: Implement scrollbar interactions! :tada: --- core/src/widget/scrollable.rs | 20 ++++++++-- native/src/widget/scrollable.rs | 65 +++++++++++++++++++++++++++--- wgpu/src/renderer/scrollable.rs | 87 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 155 insertions(+), 17 deletions(-) diff --git a/core/src/widget/scrollable.rs b/core/src/widget/scrollable.rs index 1bad8555..31a5abed 100644 --- a/core/src/widget/scrollable.rs +++ b/core/src/widget/scrollable.rs @@ -1,4 +1,4 @@ -use crate::{Align, Column, Length, Rectangle}; +use crate::{Align, Column, Length, Point, Rectangle}; #[derive(Debug)] pub struct Scrollable<'a, Element> { @@ -103,6 +103,7 @@ impl<'a, Element> Scrollable<'a, Element> { #[derive(Debug, Clone, Copy, Default)] pub struct State { + pub scrollbar_grabbed_at: Option<Point>, offset: u32, } @@ -121,17 +122,30 @@ impl State { return; } - // TODO: Configurable speed (?) - self.offset = (self.offset as i32 - delta_y.round() as i32 * 15) + 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() + } } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index e52f3c3f..76d12124 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -1,6 +1,7 @@ use crate::{ - column, input::mouse, Element, Event, Hasher, Layout, Node, Point, - Rectangle, Style, Widget, + column, + input::{mouse, ButtonState}, + Element, Event, Hasher, Layout, Node, Point, Rectangle, Style, Widget, }; pub use iced_core::scrollable::State; @@ -54,18 +55,65 @@ where let content = layout.children().next().unwrap(); let content_bounds = content.bounds(); + let is_mouse_over_scrollbar = renderer.is_mouse_over_scrollbar( + bounds, + content_bounds, + cursor_position, + ); + + // TODO: Event capture. Nested scrollables should capture scroll events. if is_mouse_over { match event { Event::Mouse(mouse::Event::WheelScrolled { delta_y, .. }) => { - self.state.scroll(delta_y, bounds, content_bounds); + // TODO: Configurable speed (?) + self.state.scroll(delta_y * 15.0, bounds, content_bounds); + } + _ => {} + } + } + + if self.state.is_scrollbar_grabbed() || is_mouse_over_scrollbar { + match event { + Event::Mouse(mouse::Event::Input { + button: mouse::Button::Left, + state, + }) => match state { + ButtonState::Pressed => { + self.state.scroll_to( + cursor_position.y / (bounds.y + bounds.height), + bounds, + content_bounds, + ); + + self.state.scrollbar_grabbed_at = Some(cursor_position); + } + ButtonState::Released => { + self.state.scrollbar_grabbed_at = None; + } + }, + Event::Mouse(mouse::Event::CursorMoved { .. }) => { + if let Some(scrollbar_grabbed_at) = + self.state.scrollbar_grabbed_at + { + self.state.scroll( + scrollbar_grabbed_at.y - cursor_position.y, + bounds, + content_bounds, + ); + + self.state.scrollbar_grabbed_at = Some(cursor_position); + } } _ => {} } } - let cursor_position = if is_mouse_over { + let cursor_position = if is_mouse_over + && !(is_mouse_over_scrollbar + || self.state.scrollbar_grabbed_at.is_some()) + { Point::new( cursor_position.x, cursor_position.y @@ -73,7 +121,7 @@ where ) } else { // TODO: Make `cursor_position` an `Option<Point>` so we can encode - // cursor unavailability. + // cursor availability. // This will probably happen naturally once we add multi-window // support. Point::new(cursor_position.x, -1.0) @@ -118,6 +166,13 @@ where } pub trait Renderer: crate::Renderer + Sized { + fn is_mouse_over_scrollbar( + &self, + bounds: Rectangle, + content_bounds: Rectangle, + cursor_position: Point, + ) -> bool; + fn draw<Message>( &mut self, scrollable: &Scrollable<'_, Message, Self>, diff --git a/wgpu/src/renderer/scrollable.rs b/wgpu/src/renderer/scrollable.rs index 1327e577..7f8a7db6 100644 --- a/wgpu/src/renderer/scrollable.rs +++ b/wgpu/src/renderer/scrollable.rs @@ -1,9 +1,33 @@ use crate::{Primitive, Renderer}; use iced_native::{ - scrollable, Background, Color, Layout, Point, Rectangle, Scrollable, Widget, + scrollable, Background, Color, Layout, MouseCursor, Point, Rectangle, + Scrollable, Widget, }; +const SCROLLBAR_WIDTH: u16 = 10; +const SCROLLBAR_MARGIN: u16 = 10; + +fn scrollbar_bounds(bounds: Rectangle) -> Rectangle { + Rectangle { + x: bounds.x + bounds.width + - f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), + y: bounds.y, + width: f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), + height: bounds.height, + } +} + impl scrollable::Renderer for Renderer { + fn is_mouse_over_scrollbar( + &self, + bounds: Rectangle, + content_bounds: Rectangle, + cursor_position: Point, + ) -> bool { + content_bounds.height > bounds.height + && scrollbar_bounds(bounds).contains(cursor_position) + } + fn draw<Message>( &mut self, scrollable: &Scrollable<'_, Message, Self>, @@ -15,8 +39,15 @@ impl scrollable::Renderer for Renderer { let content_bounds = content.bounds(); let offset = scrollable.state.offset(bounds, content_bounds); + let is_content_overflowing = content_bounds.height > bounds.height; + let scrollbar_bounds = scrollbar_bounds(bounds); + let is_mouse_over_scrollbar = self.is_mouse_over_scrollbar( + bounds, + content_bounds, + cursor_position, + ); - let cursor_position = if bounds.contains(cursor_position) { + let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { Point::new(cursor_position.x, cursor_position.y + offset as f32) } else { Point::new(cursor_position.x, -1.0) @@ -32,16 +63,19 @@ impl scrollable::Renderer for Renderer { }; ( - if is_mouse_over && content_bounds.height > bounds.height { + if is_content_overflowing + && (is_mouse_over || scrollable.state.is_scrollbar_grabbed()) + { let ratio = bounds.height / content_bounds.height; let scrollbar_height = bounds.height * ratio; let y_offset = 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, + x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), + y: scrollbar_bounds.y + y_offset, + width: scrollbar_bounds.width + - f32::from(2 * SCROLLBAR_MARGIN), height: scrollbar_height, }, background: Background::Color(Color { @@ -52,13 +86,48 @@ impl scrollable::Renderer for Renderer { }), border_radius: 5, }; - Primitive::Group { - primitives: vec![primitive, scrollbar], + + if is_mouse_over_scrollbar + || scrollable.state.is_scrollbar_grabbed() + { + let scrollbar_background = Primitive::Quad { + bounds: Rectangle { + x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), + width: scrollbar_bounds.width + - f32::from(2 * SCROLLBAR_MARGIN), + ..scrollbar_bounds + }, + background: Background::Color(Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.3, + }), + border_radius: 5, + }; + + Primitive::Group { + primitives: vec![ + primitive, + scrollbar_background, + scrollbar, + ], + } + } else { + Primitive::Group { + primitives: vec![primitive, scrollbar], + } } } else { primitive }, - mouse_cursor, + if is_mouse_over_scrollbar + || scrollable.state.is_scrollbar_grabbed() + { + MouseCursor::Idle + } else { + mouse_cursor + }, ) } } -- cgit From bd5d871eb6630bc8f987d72c771032f878fb91b6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Tue, 29 Oct 2019 19:00:46 +0100 Subject: Handle touchpad scroll events --- native/src/input/mouse.rs | 2 +- native/src/input/mouse/event.rs | 17 ++++++++++++++--- native/src/widget/scrollable.rs | 15 ++++++++++----- winit/src/application.rs | 23 +++++++++++++++++++++-- 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/native/src/input/mouse.rs b/native/src/input/mouse.rs index d37f5b96..69dc6b4c 100644 --- a/native/src/input/mouse.rs +++ b/native/src/input/mouse.rs @@ -3,4 +3,4 @@ mod button; mod event; pub use button::Button; -pub use event::Event; +pub use event::{Event, ScrollDelta}; diff --git a/native/src/input/mouse/event.rs b/native/src/input/mouse/event.rs index 7b68208f..478f9b4d 100644 --- a/native/src/input/mouse/event.rs +++ b/native/src/input/mouse/event.rs @@ -34,11 +34,22 @@ pub enum Event { }, /// The mouse wheel was scrolled. - WheelScrolled { + WheelScrolled { delta: ScrollDelta }, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ScrollDelta { + Lines { /// The number of horizontal lines scrolled - delta_x: f32, + x: f32, /// The number of vertical lines scrolled - delta_y: f32, + y: f32, + }, + Pixels { + /// The number of horizontal pixels scrolled + x: f32, + /// The number of vertical pixels scrolled + y: f32, }, } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 76d12124..8a82be4f 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -64,11 +64,16 @@ where // TODO: Event capture. Nested scrollables should capture scroll events. if is_mouse_over { match event { - Event::Mouse(mouse::Event::WheelScrolled { - delta_y, .. - }) => { - // TODO: Configurable speed (?) - self.state.scroll(delta_y * 15.0, bounds, content_bounds); + Event::Mouse(mouse::Event::WheelScrolled { delta }) => { + match delta { + mouse::ScrollDelta::Lines { y, .. } => { + // TODO: Configurable speed (?) + self.state.scroll(y * 15.0, bounds, content_bounds); + } + mouse::ScrollDelta::Pixels { y, .. } => { + self.state.scroll(y, bounds, content_bounds); + } + } } _ => {} } diff --git a/winit/src/application.rs b/winit/src/application.rs index 855968aa..c8748199 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -123,6 +123,7 @@ pub trait Application { .. } => match window_event { WindowEvent::CursorMoved { position, .. } => { + // TODO: Remove when renderer supports HiDPI let physical_position = position.to_physical(window.hidpi_factor()); @@ -143,10 +144,28 @@ pub trait Application { delta_y, ) => { events.push(Event::Mouse( - mouse::Event::WheelScrolled { delta_x, delta_y }, + mouse::Event::WheelScrolled { + delta: mouse::ScrollDelta::Lines { + x: delta_x, + y: delta_y, + }, + }, + )); + } + winit::event::MouseScrollDelta::PixelDelta(position) => { + // TODO: Remove when renderer supports HiDPI + let physical_position = + position.to_physical(window.hidpi_factor()); + + events.push(Event::Mouse( + mouse::Event::WheelScrolled { + delta: mouse::ScrollDelta::Pixels { + x: physical_position.x as f32, + y: physical_position.y as f32, + }, + }, )); } - _ => {} }, WindowEvent::CloseRequested => { *control_flow = ControlFlow::Exit; -- cgit From ace4217b22131865623faac99cfdb5692a84d1ae Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Tue, 29 Oct 2019 19:45:47 +0100 Subject: Fix `Transformation` docs --- wgpu/src/transformation.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/wgpu/src/transformation.rs b/wgpu/src/transformation.rs index ed80b31a..53583e7e 100644 --- a/wgpu/src/transformation.rs +++ b/wgpu/src/transformation.rs @@ -2,10 +2,6 @@ use nalgebra::Matrix3; 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>); @@ -16,11 +12,6 @@ impl Transformation { } /// 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] pub fn orthographic(width: u16, height: u16) -> Transformation { Transformation(nalgebra::Matrix3::new( @@ -31,8 +22,6 @@ impl Transformation { } /// 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))) } -- cgit From 85916c9e8710ee90cbf37d384acbb6d208ff1da3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Tue, 29 Oct 2019 19:50:34 +0100 Subject: Rename `Primitive::Scrollable` to `Clip` --- wgpu/src/primitive.rs | 2 +- wgpu/src/renderer.rs | 2 +- wgpu/src/renderer/scrollable.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index cdd87894..354b0851 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -23,7 +23,7 @@ pub enum Primitive { path: String, bounds: Rectangle, }, - Scrollable { + Clip { bounds: Rectangle, offset: u32, content: Box<Primitive>, diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index f0be0860..a70693af 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -270,7 +270,7 @@ impl Renderer { scale: [bounds.width, bounds.height], }); } - Primitive::Scrollable { + Primitive::Clip { bounds, offset, content, diff --git a/wgpu/src/renderer/scrollable.rs b/wgpu/src/renderer/scrollable.rs index 7f8a7db6..7bce3a68 100644 --- a/wgpu/src/renderer/scrollable.rs +++ b/wgpu/src/renderer/scrollable.rs @@ -56,7 +56,7 @@ impl scrollable::Renderer for Renderer { let (content, mouse_cursor) = scrollable.content.draw(self, content, cursor_position); - let primitive = Primitive::Scrollable { + let primitive = Primitive::Clip { bounds, offset, content: Box::new(content), -- cgit From 298c42ac5f208745cd3b23b3cc8f10f7c8769797 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Thu, 31 Oct 2019 01:41:04 +0100 Subject: Replace `nalgebra` with `glam` `glam` compiles much faster and leverages SIMD nicely. --- wgpu/Cargo.toml | 2 +- wgpu/src/image.rs | 4 +--- wgpu/src/quad.rs | 8 ++------ wgpu/src/transformation.rs | 31 ++++++++++++++++--------------- 4 files changed, 20 insertions(+), 25 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 30f9224f..04fae248 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -13,5 +13,5 @@ wgpu = { version = "0.3", git = "https://github.com/gfx-rs/wgpu-rs", rev = "ed2c wgpu_glyph = { version = "0.4", git = "https://github.com/hecrj/wgpu_glyph", rev = "954ac865ca1b7f6b97bf403f8c6174a7120e667c" } raw-window-handle = "0.3" image = "0.22" -nalgebra = "0.18" +glam = "0.8" log = "0.4" diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index c42e1cd4..75cfa166 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -222,11 +222,9 @@ impl Pipeline { bounds: Rectangle<u32>, target: &wgpu::TextureView, ) { - let matrix: [f32; 16] = transformation.into(); - let transform_buffer = device .create_buffer_mapped(16, wgpu::BufferUsage::COPY_SRC) - .fill_from_slice(&matrix[..]); + .fill_from_slice(transformation.as_ref()); encoder.copy_buffer_to_buffer( &transform_buffer, diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 6365e117..bfbd7e2d 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -23,14 +23,12 @@ impl Pipeline { }], }); - let matrix: [f32; 16] = Transformation::identity().into(); - let transform = device .create_buffer_mapped( 16, wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, ) - .fill_from_slice(&matrix[..]); + .fill_from_slice(Transformation::identity().as_ref()); let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &constant_layout, @@ -169,11 +167,9 @@ impl Pipeline { bounds: Rectangle<u32>, target: &wgpu::TextureView, ) { - let matrix: [f32; 16] = transformation.into(); - let transform_buffer = device .create_buffer_mapped(16, wgpu::BufferUsage::COPY_SRC) - .fill_from_slice(&matrix[..]); + .fill_from_slice(transformation.as_ref()); encoder.copy_buffer_to_buffer( &transform_buffer, diff --git a/wgpu/src/transformation.rs b/wgpu/src/transformation.rs index 53583e7e..b0d14cc8 100644 --- a/wgpu/src/transformation.rs +++ b/wgpu/src/transformation.rs @@ -1,29 +1,30 @@ -use nalgebra::Matrix3; +use glam::{Mat4, Vec3, Vec4}; use std::ops::Mul; /// A 2D transformation matrix. #[derive(Debug, Clone, Copy, PartialEq)] -pub struct Transformation(Matrix3<f32>); +pub struct Transformation(Mat4); impl Transformation { /// Get the identity transformation. pub fn identity() -> Transformation { - Transformation(Matrix3::identity()) + Transformation(Mat4::identity()) } /// Creates an orthographic projection. #[rustfmt::skip] pub fn orthographic(width: u16, height: u16) -> Transformation { - Transformation(nalgebra::Matrix3::new( - 2.0 / f32::from(width), 0.0, -1.0, - 0.0, 2.0 / f32::from(height), -1.0, - 0.0, 0.0, 1.0 + Transformation(Mat4::from_cols( + Vec4::new(2.0 / f32::from(width), 0.0, 0.0, 0.0), + Vec4::new(0.0, 2.0 / f32::from(height), 0.0, 0.0), + Vec4::new(0.0, 0.0, -1.0, 0.0), + Vec4::new(-1.0, -1.0, 0.0, 1.0) )) } /// Creates a translate transformation. pub fn translate(x: f32, y: f32) -> Transformation { - Transformation(Matrix3::new_translation(&nalgebra::Vector2::new(x, y))) + Transformation(Mat4::from_translation(Vec3::new(x, y, 0.0))) } } @@ -35,14 +36,14 @@ impl Mul for Transformation { } } +impl AsRef<[f32; 16]> for Transformation { + fn as_ref(&self) -> &[f32; 16] { + self.0.as_ref() + } +} + impl From<Transformation> for [f32; 16] { - #[rustfmt::skip] 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] - ] + t.as_ref().clone() } } -- cgit From 58d04cadef020412375f2adc5fd96bfa10ec4692 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Sat, 2 Nov 2019 01:42:51 +0100 Subject: Add scrollable section to `tour` --- examples/tour.rs | 46 +++++++++++++++++++++++++++++++++++------ wgpu/src/renderer/scrollable.rs | 2 +- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/examples/tour.rs b/examples/tour.rs index 59a8c525..7d8e4e3e 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -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() { @@ -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, @@ -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, @@ -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() } } @@ -134,6 +138,7 @@ impl Steps { width: 300, slider: slider::State::new(), }, + Step::Scrollable, Step::Debugger, Step::End, ], @@ -195,6 +200,7 @@ enum Step { width: u16, slider: slider::State, }, + Scrollable, Debugger, End, } @@ -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, } @@ -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(), } @@ -528,6 +536,32 @@ 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( + Text::new("You made it!") + .size(50) + .horizontal_alignment(HorizontalAlignment::Center), + ) + } + fn debugger(debug: bool) -> Column<'a, StepMessage> { Self::container("Debugger") .push(Text::new( diff --git a/wgpu/src/renderer/scrollable.rs b/wgpu/src/renderer/scrollable.rs index 7bce3a68..e812a7e1 100644 --- a/wgpu/src/renderer/scrollable.rs +++ b/wgpu/src/renderer/scrollable.rs @@ -5,7 +5,7 @@ use iced_native::{ }; const SCROLLBAR_WIDTH: u16 = 10; -const SCROLLBAR_MARGIN: u16 = 10; +const SCROLLBAR_MARGIN: u16 = 2; fn scrollbar_bounds(bounds: Rectangle) -> Rectangle { Rectangle { -- cgit From 85dab04965940f15cf0e9879e296f67235a3775c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Sat, 2 Nov 2019 01:46:45 +0100 Subject: Scale scrollbar movement by content ratio --- native/src/widget/scrollable.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 8a82be4f..de4c749c 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -102,8 +102,11 @@ where if let Some(scrollbar_grabbed_at) = self.state.scrollbar_grabbed_at { + let ratio = content_bounds.height / bounds.height; + let delta = scrollbar_grabbed_at.y - cursor_position.y; + self.state.scroll( - scrollbar_grabbed_at.y - cursor_position.y, + delta * ratio, bounds, content_bounds, ); -- cgit From 022dc0139b7437f167a8d3ae483bf8e83f1dab04 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez <hector0193@gmail.com> Date: Sat, 2 Nov 2019 02:44:57 +0100 Subject: Show Ferris at the end of the scrollable section --- examples/tour.rs | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/examples/tour.rs b/examples/tour.rs index 7d8e4e3e..f63b4cfe 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -510,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, @@ -555,6 +542,7 @@ impl<'a> Step { .horizontal_alignment(HorizontalAlignment::Center), ) .push(Column::new().height(Length::Units(4096))) + .push(ferris(300)) .push( Text::new("You made it!") .size(50) @@ -589,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, -- cgit