diff options
Diffstat (limited to 'graphics')
44 files changed, 1019 insertions, 419 deletions
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index dec24c59..ea9471c6 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -1,36 +1,51 @@ [package] name = "iced_graphics" -version = "0.1.0" +version = "0.2.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] edition = "2018" +description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for Iced" +license = "MIT" +repository = "https://github.com/hecrj/iced" +documentation = "https://docs.rs/iced_graphics" +keywords = ["gui", "ui", "graphics", "interface", "widgets"] +categories = ["gui"] [features] canvas = ["lyon"] +qr_code = ["qrcode", "canvas"] font-source = ["font-kit"] font-fallback = [] font-icons = [] opengl = [] [dependencies] -bytemuck = "1.2" -glam = "0.9" +glam = "0.10" raw-window-handle = "0.3" thiserror = "1.0" +[dependencies.bytemuck] +version = "1.4" +features = ["derive"] + [dependencies.iced_native] -version = "0.2" +version = "0.4" path = "../native" [dependencies.iced_style] -version = "0.1" +version = "0.3" path = "../style" [dependencies.lyon] -version = "0.15" +version = "0.16" +optional = true + +[dependencies.qrcode] +version = "0.12" optional = true +default-features = false [dependencies.font-kit] -version = "0.6" +version = "0.10" optional = true [package.metadata.docs.rs] diff --git a/graphics/src/antialiasing.rs b/graphics/src/antialiasing.rs index 34d94711..7631c97c 100644 --- a/graphics/src/antialiasing.rs +++ b/graphics/src/antialiasing.rs @@ -13,8 +13,6 @@ pub enum Antialiasing { impl Antialiasing { /// Returns the amount of samples of the [`Antialiasing`]. - /// - /// [`Antialiasing`]: enum.Antialiasing.html pub fn sample_count(self) -> u32 { match self { Antialiasing::MSAAx2 => 2, diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index dd7dbbc2..ed1b9e08 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -5,7 +5,7 @@ use iced_native::{Font, Size}; /// The graphics backend of a [`Renderer`]. /// -/// [`Renderer`]: ../struct.Renderer.html +/// [`Renderer`]: crate::Renderer pub trait Backend { /// Trims the measurements cache. /// @@ -22,12 +22,12 @@ pub trait Text { /// The `char` representing a ✔ icon in the [`ICON_FONT`]. /// - /// [`ICON_FONT`]: #associatedconst.ICON_FONT + /// [`ICON_FONT`]: Self::ICON_FONT const CHECKMARK_ICON: char; - /// The `char` representing a ▼ icon in the built-in [`ICONS`] font. + /// The `char` representing a ▼ icon in the built-in [`ICON_FONT`]. /// - /// [`ICON_FONT`]: #associatedconst.ICON_FONT + /// [`ICON_FONT`]: Self::ICON_FONT const ARROW_DOWN_ICON: char; /// Returns the default size of text. diff --git a/graphics/src/font.rs b/graphics/src/font.rs index 5c62681c..d55d0faf 100644 --- a/graphics/src/font.rs +++ b/graphics/src/font.rs @@ -26,14 +26,10 @@ pub const ICONS: iced_native::Font = iced_native::Font::External { }; /// The `char` representing a ✔ icon in the built-in [`ICONS`] font. -/// -/// [`ICONS`]: const.ICONS.html #[cfg(feature = "font-icons")] #[cfg_attr(docsrs, doc(cfg(feature = "font-icons")))] pub const CHECKMARK_ICON: char = '\u{F00C}'; /// The `char` representing a ▼ icon in the built-in [`ICONS`] font. -/// -/// [`ICONS`]: const.ICONS.html #[cfg(feature = "font-icons")] pub const ARROW_DOWN_ICON: char = '\u{E800}'; diff --git a/graphics/src/font/source.rs b/graphics/src/font/source.rs index 917291ff..a2d3f51d 100644 --- a/graphics/src/font/source.rs +++ b/graphics/src/font/source.rs @@ -8,8 +8,6 @@ pub struct Source { impl Source { /// Creates a new [`Source`]. - /// - /// [`Source`]: struct.Source.html pub fn new() -> Self { Source { raw: font_kit::source::SystemSource::new(), @@ -17,8 +15,6 @@ impl Source { } /// Finds and loads a font matching the set of provided family priorities. - /// - /// [`Source`]: struct.Source.html pub fn load(&self, families: &[Family]) -> Result<Vec<u8>, LoadError> { let font = self.raw.select_best_match( families, diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index 6aca738e..7dce1d4c 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -11,35 +11,23 @@ use crate::{ #[derive(Debug, Clone)] pub struct Layer<'a> { /// The clipping bounds of the [`Layer`]. - /// - /// [`Layer`]: struct.Layer.html pub bounds: Rectangle, /// The quads of the [`Layer`]. - /// - /// [`Layer`]: struct.Layer.html pub quads: Vec<Quad>, /// The triangle meshes of the [`Layer`]. - /// - /// [`Layer`]: struct.Layer.html pub meshes: Vec<Mesh<'a>>, /// The text of the [`Layer`]. - /// - /// [`Layer`]: struct.Layer.html pub text: Vec<Text<'a>>, /// The images of the [`Layer`]. - /// - /// [`Layer`]: struct.Layer.html pub images: Vec<Image>, } impl<'a> Layer<'a> { /// Creates a new [`Layer`] with the given clipping bounds. - /// - /// [`Layer`]: struct.Layer.html pub fn new(bounds: Rectangle) -> Self { Self { bounds, @@ -53,8 +41,6 @@ impl<'a> Layer<'a> { /// Creates a new [`Layer`] for the provided overlay text. /// /// This can be useful for displaying debug information. - /// - /// [`Layer`]: struct.Layer.html pub fn overlay(lines: &'a [impl AsRef<str>], viewport: &Viewport) -> Self { let mut overlay = Layer::new(Rectangle::with_size(viewport.logical_size())); @@ -87,8 +73,6 @@ impl<'a> Layer<'a> { /// Distributes the given [`Primitive`] and generates a list of layers based /// on its contents. - /// - /// [`Primitive`]: ../enum.Primitive.html pub fn generate( primitive: &'a Primitive, viewport: &Viewport, @@ -98,7 +82,12 @@ impl<'a> Layer<'a> { let mut layers = vec![first_layer]; - Self::process_primitive(&mut layers, Vector::new(0.0, 0.0), primitive); + Self::process_primitive( + &mut layers, + Vector::new(0.0, 0.0), + primitive, + 0, + ); layers } @@ -107,13 +96,19 @@ impl<'a> Layer<'a> { layers: &mut Vec<Self>, translation: Vector, primitive: &'a Primitive, + current_layer: usize, ) { match primitive { Primitive::None => {} Primitive::Group { primitives } => { // TODO: Inspect a bit and regroup (?) for primitive in primitives { - Self::process_primitive(layers, translation, primitive) + Self::process_primitive( + layers, + translation, + primitive, + current_layer, + ) } } Primitive::Text { @@ -125,7 +120,7 @@ impl<'a> Layer<'a> { horizontal_alignment, vertical_alignment, } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; layer.text.push(Text { content, @@ -144,7 +139,7 @@ impl<'a> Layer<'a> { border_width, border_color, } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; // TODO: Move some of these computations to the GPU (?) layer.quads.push(Quad { @@ -156,13 +151,13 @@ impl<'a> Layer<'a> { color: match background { Background::Color(color) => color.into_linear(), }, - border_radius: *border_radius as f32, - border_width: *border_width as f32, + border_radius: *border_radius, + border_width: *border_width, border_color: border_color.into_linear(), }); } Primitive::Mesh2D { buffers, size } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; let bounds = Rectangle::new( Point::new(translation.x, translation.y), @@ -183,7 +178,7 @@ impl<'a> Layer<'a> { offset, content, } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; let translated_bounds = *bounds + translation; // Only draw visible content @@ -191,16 +186,15 @@ impl<'a> Layer<'a> { layer.bounds.intersection(&translated_bounds) { let clip_layer = Layer::new(clip_bounds); - let new_layer = Layer::new(layer.bounds); - layers.push(clip_layer); + Self::process_primitive( layers, translation - Vector::new(offset.x as f32, offset.y as f32), content, + layers.len() - 1, ); - layers.push(new_layer); } } Primitive::Translate { @@ -211,13 +205,19 @@ impl<'a> Layer<'a> { layers, translation + *new_translation, &content, + current_layer, ); } Primitive::Cached { cache } => { - Self::process_primitive(layers, translation, &cache); + Self::process_primitive( + layers, + translation, + &cache, + current_layer, + ); } Primitive::Image { handle, bounds } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; layer.images.push(Image::Raster { handle: handle.clone(), @@ -225,7 +225,7 @@ impl<'a> Layer<'a> { }); } Primitive::Svg { handle, bounds } => { - let layer = layers.last_mut().unwrap(); + let layer = &mut layers[current_layer]; layer.images.push(Image::Vector { handle: handle.clone(), @@ -243,33 +243,21 @@ impl<'a> Layer<'a> { #[repr(C)] pub struct Quad { /// The position of the [`Quad`]. - /// - /// [`Quad`]: struct.Quad.html pub position: [f32; 2], /// The size of the [`Quad`]. - /// - /// [`Quad`]: struct.Quad.html pub size: [f32; 2], /// The color of the [`Quad`], in __linear RGB__. - /// - /// [`Quad`]: struct.Quad.html pub color: [f32; 4], /// The border color of the [`Quad`], in __linear RGB__. - /// - /// [`Quad`]: struct.Quad.html pub border_color: [f32; 4], /// The border radius of the [`Quad`]. - /// - /// [`Quad`]: struct.Quad.html pub border_radius: f32, /// The border width of the [`Quad`]. - /// - /// [`Quad`]: struct.Quad.html pub border_width: f32, } @@ -277,18 +265,12 @@ pub struct Quad { #[derive(Debug, Clone, Copy)] pub struct Mesh<'a> { /// The origin of the vertices of the [`Mesh`]. - /// - /// [`Mesh`]: struct.Mesh.html pub origin: Point, /// The vertex and index buffers of the [`Mesh`]. - /// - /// [`Mesh`]: struct.Mesh.html pub buffers: &'a triangle::Mesh2D, /// The clipping bounds of the [`Mesh`]. - /// - /// [`Mesh`]: struct.Mesh.html pub clip_bounds: Rectangle<f32>, } @@ -296,38 +278,24 @@ pub struct Mesh<'a> { #[derive(Debug, Clone, Copy)] pub struct Text<'a> { /// The content of the [`Text`]. - /// - /// [`Text`]: struct.Text.html pub content: &'a str, /// The layout bounds of the [`Text`]. - /// - /// [`Text`]: struct.Text.html pub bounds: Rectangle, /// The color of the [`Text`], in __linear RGB_. - /// - /// [`Text`]: struct.Text.html pub color: [f32; 4], /// The size of the [`Text`]. - /// - /// [`Text`]: struct.Text.html pub size: f32, /// The font of the [`Text`]. - /// - /// [`Text`]: struct.Text.html pub font: Font, /// The horizontal alignment of the [`Text`]. - /// - /// [`Text`]: struct.Text.html pub horizontal_alignment: HorizontalAlignment, /// The vertical alignment of the [`Text`]. - /// - /// [`Text`]: struct.Text.html pub vertical_alignment: VerticalAlignment, } diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index a3bd5364..14388653 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -1,6 +1,8 @@ //! A bunch of backend-agnostic types that can be leveraged to build a renderer //! for [`iced`]. //! +//!  +//! //! [`iced`]: https://github.com/hecrj/iced #![deny(missing_docs)] #![deny(missing_debug_implementations)] diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs index a952f065..9e91a0ef 100644 --- a/graphics/src/overlay/menu.rs +++ b/graphics/src/overlay/menu.rs @@ -2,8 +2,8 @@ use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::{ - mouse, overlay, Color, Font, HorizontalAlignment, Point, Rectangle, - VerticalAlignment, + mouse, overlay, Color, Font, HorizontalAlignment, Padding, Point, + Rectangle, VerticalAlignment, }; pub use iced_style::menu::Style; @@ -29,7 +29,7 @@ where background: style.background, border_color: style.border_color, border_width: style.border_width, - border_radius: 0, + border_radius: 0.0, }, primitives, ], @@ -42,9 +42,10 @@ where &mut self, bounds: Rectangle, cursor_position: Point, + viewport: &Rectangle, options: &[T], hovered_option: Option<usize>, - padding: u16, + padding: Padding, text_size: u16, font: Font, style: &Style, @@ -52,18 +53,26 @@ where use std::f32; let is_mouse_over = bounds.contains(cursor_position); + let option_height = (text_size + padding.vertical()) as usize; let mut primitives = Vec::new(); - for (i, option) in options.iter().enumerate() { + let offset = viewport.y - bounds.y; + let start = (offset / option_height as f32) as usize; + let end = + ((offset + viewport.height) / option_height as f32).ceil() as usize; + + let visible_options = &options[start..end.min(options.len())]; + + for (i, option) in visible_options.iter().enumerate() { + let i = start + i; let is_selected = hovered_option == Some(i); let bounds = Rectangle { x: bounds.x, - y: bounds.y - + ((text_size as usize + padding as usize * 2) * i) as f32, + y: bounds.y + (option_height * i) as f32, width: bounds.width, - height: f32::from(text_size + padding * 2), + height: f32::from(text_size + padding.vertical()), }; if is_selected { @@ -71,15 +80,15 @@ where bounds, background: style.selected_background, border_color: Color::TRANSPARENT, - border_width: 0, - border_radius: 0, + border_width: 0.0, + border_radius: 0.0, }); } primitives.push(Primitive::Text { content: option.to_string(), bounds: Rectangle { - x: bounds.x + f32::from(padding), + x: bounds.x + padding.left as f32, y: bounds.center_y(), width: f32::INFINITY, ..bounds diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 95dbf7dd..30263bd4 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -40,9 +40,9 @@ pub enum Primitive { /// The background of the quad background: Background, /// The border radius of the quad - border_radius: u16, + border_radius: f32, /// The border width of the quad - border_width: u16, + border_width: f32, /// The border color of the quad border_color: Color, }, diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 5d51e6d4..fa63991b 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -13,25 +13,16 @@ pub struct Renderer<B: Backend> { impl<B: Backend> Renderer<B> { /// Creates a new [`Renderer`] from the given [`Backend`]. - /// - /// [`Renderer`]: struct.Renderer.html - /// [`Backend`]: backend/trait.Backend.html pub fn new(backend: B) -> Self { Self { backend } } /// Returns a reference to the [`Backend`] of the [`Renderer`]. - /// - /// [`Renderer`]: struct.Renderer.html - /// [`Backend`]: backend/trait.Backend.html pub fn backend(&self) -> &B { &self.backend } /// Returns a mutable reference to the [`Backend`] of the [`Renderer`]. - /// - /// [`Renderer`]: struct.Renderer.html - /// [`Backend`]: backend/trait.Backend.html pub fn backend_mut(&mut self) -> &mut B { &mut self.backend } @@ -96,10 +87,11 @@ where widget: &dyn Widget<Message, Self>, layout: Layout<'_>, cursor_position: Point, + viewport: &Rectangle, color: Color, ) -> Self::Output { let (primitive, cursor) = - widget.draw(self, defaults, layout, cursor_position); + widget.draw(self, defaults, layout, cursor_position, viewport); let mut primitives = Vec::new(); @@ -118,8 +110,8 @@ fn explain_layout( primitives.push(Primitive::Quad { bounds: layout.bounds(), background: Background::Color(Color::TRANSPARENT), - border_radius: 0, - border_width: 1, + border_radius: 0.0, + border_width: 1.0, border_color: [0.6, 0.6, 0.6, 0.5].into(), }); diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs index ce879ffc..05028f51 100644 --- a/graphics/src/triangle.rs +++ b/graphics/src/triangle.rs @@ -1,8 +1,7 @@ //! Draw geometry using meshes of triangles. +use bytemuck::{Pod, Zeroable}; /// A set of [`Vertex2D`] and indices representing a list of triangles. -/// -/// [`Vertex2D`]: struct.Vertex2D.html #[derive(Clone, Debug)] pub struct Mesh2D { /// The vertices of the mesh @@ -16,7 +15,7 @@ pub struct Mesh2D { } /// A two-dimensional vertex with some color in __linear__ RGBA. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] #[repr(C)] pub struct Vertex2D { /// The vertex position @@ -24,9 +23,3 @@ pub struct Vertex2D { /// The vertex color in __linear__ RGBA. pub color: [f32; 4], } - -#[allow(unsafe_code)] -unsafe impl bytemuck::Zeroable for Vertex2D {} - -#[allow(unsafe_code)] -unsafe impl bytemuck::Pod for Vertex2D {} diff --git a/graphics/src/viewport.rs b/graphics/src/viewport.rs index 66122e6d..2c0b541a 100644 --- a/graphics/src/viewport.rs +++ b/graphics/src/viewport.rs @@ -1,7 +1,7 @@ use crate::{Size, Transformation}; /// A viewing region for displaying computer graphics. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Viewport { physical_size: Size<u32>, logical_size: Size<f32>, @@ -12,8 +12,6 @@ pub struct Viewport { impl Viewport { /// Creates a new [`Viewport`] with the given physical dimensions and scale /// factor. - /// - /// [`Viewport`]: struct.Viewport.html pub fn with_physical_size(size: Size<u32>, scale_factor: f64) -> Viewport { Viewport { physical_size: size, @@ -27,43 +25,31 @@ impl Viewport { } /// Returns the physical size of the [`Viewport`]. - /// - /// [`Viewport`]: struct.Viewport.html pub fn physical_size(&self) -> Size<u32> { self.physical_size } /// Returns the physical width of the [`Viewport`]. - /// - /// [`Viewport`]: struct.Viewport.html pub fn physical_width(&self) -> u32 { - self.physical_size.height + self.physical_size.width } /// Returns the physical height of the [`Viewport`]. - /// - /// [`Viewport`]: struct.Viewport.html pub fn physical_height(&self) -> u32 { self.physical_size.height } /// Returns the logical size of the [`Viewport`]. - /// - /// [`Viewport`]: struct.Viewport.html pub fn logical_size(&self) -> Size<f32> { self.logical_size } /// Returns the scale factor of the [`Viewport`]. - /// - /// [`Viewport`]: struct.Viewport.html pub fn scale_factor(&self) -> f64 { self.scale_factor } /// Returns the projection transformation of the [`Viewport`]. - /// - /// [`Viewport`]: struct.Viewport.html pub fn projection(&self) -> Transformation { self.projection } diff --git a/graphics/src/widget.rs b/graphics/src/widget.rs index f87b558a..e34d267f 100644 --- a/graphics/src/widget.rs +++ b/graphics/src/widget.rs @@ -20,6 +20,8 @@ pub mod scrollable; pub mod slider; pub mod svg; pub mod text_input; +pub mod toggler; +pub mod tooltip; mod column; mod row; @@ -48,6 +50,10 @@ pub use scrollable::Scrollable; pub use slider::Slider; #[doc(no_inline)] pub use text_input::TextInput; +#[doc(no_inline)] +pub use toggler::Toggler; +#[doc(no_inline)] +pub use tooltip::Tooltip; pub use column::Column; pub use image::Image; @@ -63,3 +69,11 @@ pub mod canvas; #[cfg(feature = "canvas")] #[doc(no_inline)] pub use canvas::Canvas; + +#[cfg(feature = "qr_code")] +#[cfg_attr(docsrs, doc(cfg(feature = "qr_code")))] +pub mod qr_code; + +#[cfg(feature = "qr_code")] +#[doc(no_inline)] +pub use qr_code::QRCode; diff --git a/graphics/src/widget/button.rs b/graphics/src/widget/button.rs index ecabc868..60400ed8 100644 --- a/graphics/src/widget/button.rs +++ b/graphics/src/widget/button.rs @@ -1,14 +1,11 @@ //! Allow your users to perform actions by pressing a button. //! //! A [`Button`] has some local [`State`]. -//! -//! [`Button`]: type.Button.html -//! [`State`]: struct.State.html use crate::defaults::{self, Defaults}; use crate::{Backend, Primitive, Renderer}; use iced_native::mouse; use iced_native::{ - Background, Color, Element, Layout, Point, Rectangle, Vector, + Background, Color, Element, Layout, Padding, Point, Rectangle, Vector, }; pub use iced_native::button::State; @@ -24,7 +21,7 @@ impl<B> iced_native::button::Renderer for Renderer<B> where B: Backend, { - const DEFAULT_PADDING: u16 = 5; + const DEFAULT_PADDING: Padding = Padding::new(5); type Style = Box<dyn StyleSheet>; @@ -62,10 +59,11 @@ where }, content_layout, cursor_position, + &bounds, ); ( - if styling.background.is_some() || styling.border_width > 0 { + if styling.background.is_some() || styling.border_width > 0.0 { let background = Primitive::Quad { bounds, background: styling @@ -92,7 +90,7 @@ where [0.0, 0.0, 0.0, 0.5].into(), ), border_radius: styling.border_radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, }; diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index bc0802e5..7897c8ec 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -3,22 +3,21 @@ //! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a //! [`Frame`]. It can be used for animation, data visualization, game graphics, //! and more! -//! -//! [`Canvas`]: struct.Canvas.html -//! [`Frame`]: struct.Frame.html use crate::{Backend, Defaults, Primitive, Renderer}; +use iced_native::layout; +use iced_native::mouse; use iced_native::{ - layout, mouse, Clipboard, Element, Hasher, Layout, Length, Point, Size, - Vector, Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector, + Widget, }; use std::hash::Hash; use std::marker::PhantomData; +pub mod event; pub mod path; mod cache; mod cursor; -mod event; mod fill; mod frame; mod geometry; @@ -39,8 +38,6 @@ pub use text::Text; /// A widget capable of drawing 2D graphics. /// -/// [`Canvas`]: struct.Canvas.html -/// /// # Examples /// The repository has a couple of [examples] showcasing how to use a /// [`Canvas`]: @@ -106,8 +103,6 @@ impl<Message, P: Program<Message>> Canvas<Message, P> { const DEFAULT_SIZE: u16 = 100; /// Creates a new [`Canvas`]. - /// - /// [`Canvas`]: struct.Canvas.html pub fn new(program: P) -> Self { Canvas { width: Length::Units(Self::DEFAULT_SIZE), @@ -118,16 +113,12 @@ impl<Message, P: Program<Message>> Canvas<Message, P> { } /// Sets the width of the [`Canvas`]. - /// - /// [`Canvas`]: struct.Canvas.html pub fn width(mut self, width: Length) -> Self { self.width = width; self } /// Sets the height of the [`Canvas`]. - /// - /// [`Canvas`]: struct.Canvas.html pub fn height(mut self, height: Length) -> Self { self.height = height; self @@ -163,10 +154,10 @@ where event: iced_native::Event, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec<Message>, _renderer: &Renderer<B>, - _clipboard: Option<&dyn Clipboard>, - ) { + _clipboard: &mut dyn Clipboard, + messages: &mut Vec<Message>, + ) -> event::Status { let bounds = layout.bounds(); let canvas_event = match event { @@ -182,12 +173,17 @@ where let cursor = Cursor::from_window_position(cursor_position); if let Some(canvas_event) = canvas_event { - if let Some(message) = - self.program.update(canvas_event, bounds, cursor) - { + let (event_status, message) = + self.program.update(canvas_event, bounds, cursor); + + if let Some(message) = message { messages.push(message); } + + return event_status; } + + event::Status::Ignored } fn draw( @@ -196,6 +192,7 @@ where _defaults: &Defaults, layout: Layout<'_>, cursor_position: Point, + _viewport: &Rectangle, ) -> (Primitive, mouse::Interaction) { let bounds = layout.bounds(); let translation = Vector::new(bounds.x, bounds.y); diff --git a/graphics/src/widget/canvas/cache.rs b/graphics/src/widget/canvas/cache.rs index 4b28d164..a469417d 100644 --- a/graphics/src/widget/canvas/cache.rs +++ b/graphics/src/widget/canvas/cache.rs @@ -23,10 +23,6 @@ impl Default for State { /// /// A [`Cache`] will not redraw its geometry unless the dimensions of its layer /// change or it is explicitly cleared. -/// -/// [`Layer`]: ../trait.Layer.html -/// [`Cache`]: struct.Cache.html -/// [`Geometry`]: struct.Geometry.html #[derive(Debug, Default)] pub struct Cache { state: RefCell<State>, @@ -34,8 +30,6 @@ pub struct Cache { impl Cache { /// Creates a new empty [`Cache`]. - /// - /// [`Cache`]: struct.Cache.html pub fn new() -> Self { Cache { state: Default::default(), @@ -43,8 +37,6 @@ impl Cache { } /// Clears the [`Cache`], forcing a redraw the next time it is used. - /// - /// [`Cache`]: struct.Cache.html pub fn clear(&mut self) { *self.state.borrow_mut() = State::Empty; } @@ -59,8 +51,6 @@ impl Cache { /// Otherwise, the previously stored [`Geometry`] will be returned. The /// [`Cache`] is not cleared in this case. In other words, it will keep /// returning the stored [`Geometry`] if needed. - /// - /// [`Cache`]: struct.Cache.html pub fn draw(&self, bounds: Size, draw_fn: impl Fn(&mut Frame)) -> Geometry { use std::ops::Deref; diff --git a/graphics/src/widget/canvas/cursor.rs b/graphics/src/widget/canvas/cursor.rs index 456760ea..9588d129 100644 --- a/graphics/src/widget/canvas/cursor.rs +++ b/graphics/src/widget/canvas/cursor.rs @@ -22,8 +22,6 @@ impl Cursor { } /// Returns the absolute position of the [`Cursor`], if available. - /// - /// [`Cursor`]: enum.Cursor.html pub fn position(&self) -> Option<Point> { match self { Cursor::Available(position) => Some(*position), @@ -36,8 +34,6 @@ impl Cursor { /// /// If the [`Cursor`] is not over the provided bounds, this method will /// return `None`. - /// - /// [`Cursor`]: enum.Cursor.html pub fn position_in(&self, bounds: &Rectangle) -> Option<Point> { if self.is_over(bounds) { self.position_from(bounds.position()) @@ -48,8 +44,6 @@ impl Cursor { /// Returns the relative position of the [`Cursor`] from the given origin, /// if available. - /// - /// [`Cursor`]: enum.Cursor.html pub fn position_from(&self, origin: Point) -> Option<Point> { match self { Cursor::Available(position) => { @@ -61,8 +55,6 @@ impl Cursor { /// Returns whether the [`Cursor`] is currently over the provided bounds /// or not. - /// - /// [`Cursor`]: enum.Cursor.html pub fn is_over(&self, bounds: &Rectangle) -> bool { match self { Cursor::Available(position) => bounds.contains(*position), diff --git a/graphics/src/widget/canvas/event.rs b/graphics/src/widget/canvas/event.rs index 0e66f0ff..5bf6f7a6 100644 --- a/graphics/src/widget/canvas/event.rs +++ b/graphics/src/widget/canvas/event.rs @@ -1,9 +1,12 @@ +//! Handle events of a canvas. use iced_native::keyboard; use iced_native::mouse; +pub use iced_native::event::Status; + /// A [`Canvas`] event. /// -/// [`Canvas`]: struct.Event.html +/// [`Canvas`]: crate::widget::Canvas #[derive(Debug, Clone, Copy, PartialEq)] pub enum Event { /// A mouse event. diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs index b5c6a2b1..5af9d11f 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -7,7 +7,7 @@ use crate::{ /// The frame of a [`Canvas`]. /// -/// [`Canvas`]: struct.Canvas.html +/// [`Canvas`]: crate::widget::Canvas #[derive(Debug)] pub struct Frame { size: Size, @@ -33,8 +33,6 @@ impl Frame { /// /// The default coordinate system of a [`Frame`] has its origin at the /// top-left corner of its bounds. - /// - /// [`Frame`]: struct.Frame.html pub fn new(size: Size) -> Frame { Frame { size, @@ -51,32 +49,24 @@ impl Frame { } /// Returns the width of the [`Frame`]. - /// - /// [`Frame`]: struct.Frame.html #[inline] pub fn width(&self) -> f32 { self.size.width } - /// Returns the width of the [`Frame`]. - /// - /// [`Frame`]: struct.Frame.html + /// Returns the height of the [`Frame`]. #[inline] pub fn height(&self) -> f32 { self.size.height } /// Returns the dimensions of the [`Frame`]. - /// - /// [`Frame`]: struct.Frame.html #[inline] pub fn size(&self) -> Size { self.size } /// Returns the coordinate of the center of the [`Frame`]. - /// - /// [`Frame`]: struct.Frame.html #[inline] pub fn center(&self) -> Point { Point::new(self.size.width / 2.0, self.size.height / 2.0) @@ -84,9 +74,6 @@ impl Frame { /// Draws the given [`Path`] on the [`Frame`] by filling it with the /// provided style. - /// - /// [`Path`]: path/struct.Path.html - /// [`Frame`]: struct.Frame.html pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { use lyon::tessellation::{ BuffersBuilder, FillOptions, FillTessellator, @@ -115,8 +102,6 @@ impl Frame { /// Draws an axis-aligned rectangle given its top-left corner coordinate and /// its `Size` on the [`Frame`] by filling it with the provided style. - /// - /// [`Frame`]: struct.Frame.html pub fn fill_rectangle( &mut self, top_left: Point, @@ -152,9 +137,6 @@ impl Frame { /// Draws the stroke of the given [`Path`] on the [`Frame`] with the /// provided style. - /// - /// [`Path`]: path/struct.Path.html - /// [`Frame`]: struct.Frame.html pub fn stroke(&mut self, path: &Path, stroke: impl Into<Stroke>) { use lyon::tessellation::{ BuffersBuilder, StrokeOptions, StrokeTessellator, @@ -200,9 +182,7 @@ impl Frame { /// Support for vectorial text is planned, and should address all these /// limitations. /// - /// [`Text`]: struct.Text.html - /// [`Frame`]: struct.Frame.html - /// [`Canvas`]: struct.Canvas.html + /// [`Canvas`]: crate::widget::Canvas pub fn fill_text(&mut self, text: impl Into<Text>) { use std::f32; @@ -240,8 +220,6 @@ impl Frame { /// /// This method is useful to compose transforms and perform drawing /// operations in different coordinate systems. - /// - /// [`Frame`]: struct.Frame.html #[inline] pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { self.transforms.previous.push(self.transforms.current); @@ -252,8 +230,6 @@ impl Frame { } /// Applies a translation to the current transform of the [`Frame`]. - /// - /// [`Frame`]: struct.Frame.html #[inline] pub fn translate(&mut self, translation: Vector) { self.transforms.current.raw = self @@ -268,21 +244,17 @@ impl Frame { } /// Applies a rotation to the current transform of the [`Frame`]. - /// - /// [`Frame`]: struct.Frame.html #[inline] pub fn rotate(&mut self, angle: f32) { self.transforms.current.raw = self .transforms .current .raw - .pre_rotate(lyon::math::Angle::radians(-angle)); + .pre_rotate(lyon::math::Angle::radians(angle)); self.transforms.current.is_identity = false; } /// Applies a scaling to the current transform of the [`Frame`]. - /// - /// [`Frame`]: struct.Frame.html #[inline] pub fn scale(&mut self, scale: f32) { self.transforms.current.raw = @@ -291,9 +263,6 @@ impl Frame { } /// Produces the [`Geometry`] representing everything drawn on the [`Frame`]. - /// - /// [`Frame`]: struct.Frame.html - /// [`Geometry`]: struct.Geometry.html pub fn into_geometry(mut self) -> Geometry { if !self.buffers.indices.is_empty() { self.primitives.push(Primitive::Mesh2D { diff --git a/graphics/src/widget/canvas/geometry.rs b/graphics/src/widget/canvas/geometry.rs index 4cadee39..8915cda1 100644 --- a/graphics/src/widget/canvas/geometry.rs +++ b/graphics/src/widget/canvas/geometry.rs @@ -5,9 +5,8 @@ use crate::Primitive; /// [`Geometry`] can be easily generated with a [`Frame`] or stored in a /// [`Cache`]. /// -/// [`Geometry`]: struct.Geometry.html -/// [`Frame`]: struct.Frame.html -/// [`Cache`]: struct.Cache.html +/// [`Frame`]: crate::widget::canvas::Frame +/// [`Cache`]: crate::widget::canvas::Cache #[derive(Debug, Clone)] pub struct Geometry(Primitive); @@ -19,9 +18,6 @@ impl Geometry { /// Turns the [`Geometry`] into a [`Primitive`]. /// /// This can be useful if you are building a custom widget. - /// - /// [`Geometry`]: struct.Geometry.html - /// [`Primitive`]: ../enum.Primitive.html pub fn into_primitive(self) -> Primitive { self.0 } diff --git a/graphics/src/widget/canvas/path.rs b/graphics/src/widget/canvas/path.rs index c26bf187..6de19321 100644 --- a/graphics/src/widget/canvas/path.rs +++ b/graphics/src/widget/canvas/path.rs @@ -12,8 +12,6 @@ use iced_native::{Point, Size}; /// An immutable set of points that may or may not be connected. /// /// A single [`Path`] can represent different kinds of 2D shapes! -/// -/// [`Path`]: struct.Path.html #[derive(Debug, Clone)] pub struct Path { raw: lyon::path::Path, @@ -23,9 +21,6 @@ impl Path { /// Creates a new [`Path`] with the provided closure. /// /// Use the [`Builder`] to configure your [`Path`]. - /// - /// [`Path`]: struct.Path.html - /// [`Builder`]: struct.Builder.html pub fn new(f: impl FnOnce(&mut Builder)) -> Self { let mut builder = Builder::new(); @@ -37,8 +32,6 @@ impl Path { /// Creates a new [`Path`] representing a line segment given its starting /// and end points. - /// - /// [`Path`]: struct.Path.html pub fn line(from: Point, to: Point) -> Self { Self::new(|p| { p.move_to(from); @@ -48,16 +41,12 @@ impl Path { /// Creates a new [`Path`] representing a rectangle given its top-left /// corner coordinate and its `Size`. - /// - /// [`Path`]: struct.Path.html pub fn rectangle(top_left: Point, size: Size) -> Self { Self::new(|p| p.rectangle(top_left, size)) } /// Creates a new [`Path`] representing a circle given its center /// coordinate and its radius. - /// - /// [`Path`]: struct.Path.html pub fn circle(center: Point, radius: f32) -> Self { Self::new(|p| p.circle(center, radius)) } diff --git a/graphics/src/widget/canvas/path/arc.rs b/graphics/src/widget/canvas/path/arc.rs index 343191f1..b8e72daf 100644 --- a/graphics/src/widget/canvas/path/arc.rs +++ b/graphics/src/widget/canvas/path/arc.rs @@ -15,8 +15,6 @@ pub struct Arc { } /// An elliptical [`Arc`]. -/// -/// [`Arc`]: struct.Arc.html #[derive(Debug, Clone, Copy)] pub struct Elliptical { /// The center of the arc. diff --git a/graphics/src/widget/canvas/path/builder.rs b/graphics/src/widget/canvas/path/builder.rs index e0e52845..5ce0e02c 100644 --- a/graphics/src/widget/canvas/path/builder.rs +++ b/graphics/src/widget/canvas/path/builder.rs @@ -6,8 +6,6 @@ use lyon::path::builder::{Build, FlatPathBuilder, PathBuilder, SvgBuilder}; /// A [`Path`] builder. /// /// Once a [`Path`] is built, it can no longer be mutated. -/// -/// [`Path`]: struct.Path.html #[allow(missing_debug_implementations)] pub struct Builder { raw: lyon::path::builder::SvgPathBuilder<lyon::path::Builder>, @@ -15,8 +13,6 @@ pub struct Builder { impl Builder { /// Creates a new [`Builder`]. - /// - /// [`Builder`]: struct.Builder.html pub fn new() -> Builder { Builder { raw: lyon::path::Path::builder().with_svg(), @@ -31,8 +27,6 @@ impl Builder { /// Connects the last point in the [`Path`] to the given `Point` with a /// straight line. - /// - /// [`Path`]: struct.Path.html #[inline] pub fn line_to(&mut self, point: Point) { let _ = self.raw.line_to(lyon::math::Point::new(point.x, point.y)); @@ -40,9 +34,6 @@ impl Builder { /// Adds an [`Arc`] to the [`Path`] from `start_angle` to `end_angle` in /// a clockwise direction. - /// - /// [`Arc`]: struct.Arc.html - /// [`Path`]: struct.Path.html #[inline] pub fn arc(&mut self, arc: Arc) { self.ellipse(arc.into()); @@ -53,8 +44,6 @@ impl Builder { /// /// The arc is connected to the previous point by a straight line, if /// necessary. - /// - /// [`Path`]: struct.Path.html pub fn arc_to(&mut self, a: Point, b: Point, radius: f32) { use lyon::{math, path}; @@ -72,10 +61,7 @@ impl Builder { ); } - /// Adds an [`Ellipse`] to the [`Path`] using a clockwise direction. - /// - /// [`Ellipse`]: struct.Arc.html - /// [`Path`]: struct.Path.html + /// Adds an ellipse to the [`Path`] using a clockwise direction. pub fn ellipse(&mut self, arc: arc::Elliptical) { use lyon::{geom, math}; @@ -96,8 +82,6 @@ impl Builder { /// Adds a cubic Bézier curve to the [`Path`] given its two control points /// and its end point. - /// - /// [`Path`]: struct.Path.html #[inline] pub fn bezier_curve_to( &mut self, @@ -116,8 +100,6 @@ impl Builder { /// Adds a quadratic Bézier curve to the [`Path`] given its control point /// and its end point. - /// - /// [`Path`]: struct.Path.html #[inline] pub fn quadratic_curve_to(&mut self, control: Point, to: Point) { use lyon::math; @@ -130,8 +112,6 @@ impl Builder { /// Adds a rectangle to the [`Path`] given its top-left corner coordinate /// and its `Size`. - /// - /// [`Path`]: struct.Path.html #[inline] pub fn rectangle(&mut self, top_left: Point, size: Size) { self.move_to(top_left); @@ -146,8 +126,6 @@ impl Builder { /// Adds a circle to the [`Path`] given its center coordinate and its /// radius. - /// - /// [`Path`]: struct.Path.html #[inline] pub fn circle(&mut self, center: Point, radius: f32) { self.arc(Arc { @@ -160,17 +138,12 @@ impl Builder { /// Closes the current sub-path in the [`Path`] with a straight line to /// the starting point. - /// - /// [`Path`]: struct.Path.html #[inline] pub fn close(&mut self) { self.raw.close() } /// Builds the [`Path`] of this [`Builder`]. - /// - /// [`Path`]: struct.Path.html - /// [`Builder`]: struct.Builder.html #[inline] pub fn build(self) -> Path { Path { diff --git a/graphics/src/widget/canvas/program.rs b/graphics/src/widget/canvas/program.rs index 725d9d72..85a2f67b 100644 --- a/graphics/src/widget/canvas/program.rs +++ b/graphics/src/widget/canvas/program.rs @@ -1,4 +1,5 @@ -use crate::canvas::{Cursor, Event, Geometry}; +use crate::canvas::event::{self, Event}; +use crate::canvas::{Cursor, Geometry}; use iced_native::{mouse, Rectangle}; /// The state and logic of a [`Canvas`]. @@ -6,8 +7,7 @@ use iced_native::{mouse, Rectangle}; /// A [`Program`] can mutate internal state and produce messages for an /// application. /// -/// [`Canvas`]: struct.Canvas.html -/// [`Program`]: trait.Program.html +/// [`Canvas`]: crate::widget::Canvas pub trait Program<Message> { /// Updates the state of the [`Program`]. /// @@ -19,16 +19,14 @@ pub trait Program<Message> { /// /// By default, this method does and returns nothing. /// - /// [`Program`]: trait.Program.html - /// [`Canvas`]: struct.Canvas.html - /// [`Event`]: enum.Event.html + /// [`Canvas`]: crate::widget::Canvas fn update( &mut self, _event: Event, _bounds: Rectangle, _cursor: Cursor, - ) -> Option<Message> { - None + ) -> (event::Status, Option<Message>) { + (event::Status::Ignored, None) } /// Draws the state of the [`Program`], producing a bunch of [`Geometry`]. @@ -36,10 +34,8 @@ pub trait Program<Message> { /// [`Geometry`] can be easily generated with a [`Frame`] or stored in a /// [`Cache`]. /// - /// [`Program`]: trait.Program.html - /// [`Geometry`]: struct.Geometry.html - /// [`Frame`]: struct.Frame.html - /// [`Cache`]: struct.Cache.html + /// [`Frame`]: crate::widget::canvas::Frame + /// [`Cache`]: crate::widget::canvas::Cache fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry>; /// Returns the current mouse interaction of the [`Program`]. @@ -47,8 +43,7 @@ pub trait Program<Message> { /// The interaction returned will be in effect even if the cursor position /// is out of bounds of the program's [`Canvas`]. /// - /// [`Program`]: trait.Program.html - /// [`Canvas`]: struct.Canvas.html + /// [`Canvas`]: crate::widget::Canvas fn mouse_interaction( &self, _bounds: Rectangle, @@ -67,7 +62,7 @@ where event: Event, bounds: Rectangle, cursor: Cursor, - ) -> Option<Message> { + ) -> (event::Status, Option<Message>) { T::update(self, event, bounds, cursor) } diff --git a/graphics/src/widget/canvas/stroke.rs b/graphics/src/widget/canvas/stroke.rs index 5b6fc56a..9f0449d0 100644 --- a/graphics/src/widget/canvas/stroke.rs +++ b/graphics/src/widget/canvas/stroke.rs @@ -16,31 +16,21 @@ pub struct Stroke { impl Stroke { /// Sets the color of the [`Stroke`]. - /// - /// [`Stroke`]: struct.Stroke.html pub fn with_color(self, color: Color) -> Stroke { Stroke { color, ..self } } /// Sets the width of the [`Stroke`]. - /// - /// [`Stroke`]: struct.Stroke.html pub fn with_width(self, width: f32) -> Stroke { Stroke { width, ..self } } /// Sets the [`LineCap`] of the [`Stroke`]. - /// - /// [`LineCap`]: enum.LineCap.html - /// [`Stroke`]: struct.Stroke.html pub fn with_line_cap(self, line_cap: LineCap) -> Stroke { Stroke { line_cap, ..self } } /// Sets the [`LineJoin`] of the [`Stroke`]. - /// - /// [`LineJoin`]: enum.LineJoin.html - /// [`Stroke`]: struct.Stroke.html pub fn with_line_join(self, line_join: LineJoin) -> Stroke { Stroke { line_join, ..self } } diff --git a/graphics/src/widget/column.rs b/graphics/src/widget/column.rs index 6c7235c7..0cf56842 100644 --- a/graphics/src/widget/column.rs +++ b/graphics/src/widget/column.rs @@ -1,7 +1,7 @@ use crate::{Backend, Primitive, Renderer}; use iced_native::column; use iced_native::mouse; -use iced_native::{Element, Layout, Point}; +use iced_native::{Element, Layout, Point, Rectangle}; /// A container that distributes its contents vertically. pub type Column<'a, Message, Backend> = @@ -17,6 +17,7 @@ where content: &[Element<'_, Message, Self>], layout: Layout<'_>, cursor_position: Point, + viewport: &Rectangle, ) -> Self::Output { let mut mouse_interaction = mouse::Interaction::default(); @@ -26,8 +27,13 @@ where .iter() .zip(layout.children()) .map(|(child, layout)| { - let (primitive, new_mouse_interaction) = - child.draw(self, defaults, layout, cursor_position); + let (primitive, new_mouse_interaction) = child.draw( + self, + defaults, + layout, + cursor_position, + viewport, + ); if new_mouse_interaction > mouse_interaction { mouse_interaction = new_mouse_interaction; diff --git a/graphics/src/widget/container.rs b/graphics/src/widget/container.rs index 5b3a01d2..aae3e1d8 100644 --- a/graphics/src/widget/container.rs +++ b/graphics/src/widget/container.rs @@ -24,6 +24,7 @@ where defaults: &Defaults, bounds: Rectangle, cursor_position: Point, + viewport: &Rectangle, style_sheet: &Self::Style, content: &Element<'_, Message, Self>, content_layout: Layout<'_>, @@ -36,8 +37,13 @@ where }, }; - let (content, mouse_interaction) = - content.draw(self, &defaults, content_layout, cursor_position); + let (content, mouse_interaction) = content.draw( + self, + &defaults, + content_layout, + cursor_position, + viewport, + ); if let Some(background) = background(bounds, &style) { ( @@ -56,7 +62,7 @@ pub(crate) fn background( bounds: Rectangle, style: &container::Style, ) -> Option<Primitive> { - if style.background.is_some() || style.border_width > 0 { + if style.background.is_some() || style.border_width > 0.0 { Some(Primitive::Quad { bounds, background: style diff --git a/graphics/src/widget/image.rs b/graphics/src/widget/image.rs index 30f446e8..bdf03de3 100644 --- a/graphics/src/widget/image.rs +++ b/graphics/src/widget/image.rs @@ -1,11 +1,14 @@ //! Display images in your user interface. +pub mod viewer; + use crate::backend::{self, Backend}; + use crate::{Primitive, Renderer}; use iced_native::image; use iced_native::mouse; use iced_native::Layout; -pub use iced_native::image::{Handle, Image}; +pub use iced_native::image::{Handle, Image, Viewer}; impl<B> image::Renderer for Renderer<B> where diff --git a/graphics/src/widget/image/viewer.rs b/graphics/src/widget/image/viewer.rs new file mode 100644 index 00000000..28dffc4f --- /dev/null +++ b/graphics/src/widget/image/viewer.rs @@ -0,0 +1,55 @@ +//! Zoom and pan on an image. +use crate::backend::{self, Backend}; +use crate::{Primitive, Renderer}; + +use iced_native::image; +use iced_native::image::viewer; +use iced_native::mouse; +use iced_native::{Rectangle, Size, Vector}; + +impl<B> viewer::Renderer for Renderer<B> +where + B: Backend + backend::Image, +{ + fn draw( + &mut self, + state: &viewer::State, + bounds: Rectangle, + image_size: Size, + translation: Vector, + handle: image::Handle, + is_mouse_over: bool, + ) -> Self::Output { + ( + { + Primitive::Clip { + bounds, + content: Box::new(Primitive::Translate { + translation, + content: Box::new(Primitive::Image { + handle, + bounds: Rectangle { + x: bounds.x, + y: bounds.y, + ..Rectangle::with_size(image_size) + }, + }), + }), + offset: Vector::new(0, 0), + } + }, + { + if state.is_cursor_grabbed() { + mouse::Interaction::Grabbing + } else if is_mouse_over + && (image_size.width > bounds.width + || image_size.height > bounds.height) + { + mouse::Interaction::Grab + } else { + mouse::Interaction::Idle + } + }, + ) + } +} diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs index aa8a3f7c..92cdbb77 100644 --- a/graphics/src/widget/pane_grid.rs +++ b/graphics/src/widget/pane_grid.rs @@ -6,24 +6,21 @@ //! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, //! drag and drop, and hotkey support. //! -//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.1/examples/pane_grid -//! [`PaneGrid`]: type.PaneGrid.html -use crate::backend::{self, Backend}; +//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid use crate::defaults; -use crate::{Primitive, Renderer}; +use crate::{Backend, Color, Primitive, Renderer}; +use iced_native::container; use iced_native::mouse; use iced_native::pane_grid; -use iced_native::text; -use iced_native::{ - Element, HorizontalAlignment, Layout, Point, Rectangle, Vector, - VerticalAlignment, -}; +use iced_native::{Element, Layout, Point, Rectangle, Vector}; pub use iced_native::pane_grid::{ - Axis, Configuration, Content, Direction, DragEvent, Focus, KeyPressEvent, - Pane, ResizeEvent, Split, State, TitleBar, + Axis, Configuration, Content, Direction, DragEvent, Node, Pane, + ResizeEvent, Split, State, TitleBar, }; +pub use iced_style::pane_grid::{Line, StyleSheet}; + /// A collection of panes distributed using either vertical or horizontal splits /// to completely fill the space available. /// @@ -35,16 +32,20 @@ pub type PaneGrid<'a, Message, Backend> = impl<B> pane_grid::Renderer for Renderer<B> where - B: Backend + backend::Text, + B: Backend, { + type Style = Box<dyn StyleSheet>; + fn draw<Message>( &mut self, defaults: &Self::Defaults, content: &[(Pane, Content<'_, Message, Self>)], dragging: Option<(Pane, Point)>, - resizing: Option<Axis>, + resizing: Option<(Axis, Rectangle, bool)>, layout: Layout<'_>, + style_sheet: &<Self as pane_grid::Renderer>::Style, cursor_position: Point, + viewport: &Rectangle, ) -> Self::Output { let pane_cursor_position = if dragging.is_some() { // TODO: Remove once cursor availability is encoded in the type @@ -62,8 +63,13 @@ where .zip(layout.children()) .enumerate() .map(|(i, ((id, pane), layout))| { - let (primitive, new_mouse_interaction) = - pane.draw(self, defaults, layout, pane_cursor_position); + let (primitive, new_mouse_interaction) = pane.draw( + self, + defaults, + layout, + pane_cursor_position, + viewport, + ); if new_mouse_interaction > mouse_interaction { mouse_interaction = new_mouse_interaction; @@ -79,7 +85,8 @@ where }) .collect(); - let primitives = if let Some((index, layout, origin)) = dragged_pane { + let mut primitives = if let Some((index, layout, origin)) = dragged_pane + { let pane = panes.remove(index); let bounds = layout.bounds(); @@ -109,15 +116,62 @@ where panes }; + let (primitives, mouse_interaction) = + if let Some((axis, split_region, is_picked)) = resizing { + let highlight = if is_picked { + style_sheet.picked_split() + } else { + style_sheet.hovered_split() + }; + + if let Some(highlight) = highlight { + primitives.push(Primitive::Quad { + bounds: match axis { + Axis::Horizontal => Rectangle { + x: split_region.x, + y: (split_region.y + + (split_region.height - highlight.width) + / 2.0) + .round(), + width: split_region.width, + height: highlight.width, + }, + Axis::Vertical => Rectangle { + x: (split_region.x + + (split_region.width - highlight.width) + / 2.0) + .round(), + y: split_region.y, + width: highlight.width, + height: split_region.height, + }, + }, + background: highlight.color.into(), + border_radius: 0.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }); + } + + ( + primitives, + match axis { + Axis::Horizontal => { + mouse::Interaction::ResizingVertically + } + Axis::Vertical => { + mouse::Interaction::ResizingHorizontally + } + }, + ) + } else { + (primitives, mouse_interaction) + }; + ( Primitive::Group { primitives }, if dragging.is_some() { mouse::Interaction::Grabbing - } else if let Some(axis) = resizing { - match axis { - Axis::Horizontal => mouse::Interaction::ResizingVertically, - Axis::Vertical => mouse::Interaction::ResizingHorizontally, - } } else { mouse_interaction }, @@ -128,16 +182,17 @@ where &mut self, defaults: &Self::Defaults, bounds: Rectangle, - style_sheet: &Self::Style, + style_sheet: &<Self as container::Renderer>::Style, title_bar: Option<(&TitleBar<'_, Message, Self>, Layout<'_>)>, body: (&Element<'_, Message, Self>, Layout<'_>), cursor_position: Point, + viewport: &Rectangle, ) -> Self::Output { let style = style_sheet.style(); let (body, body_layout) = body; let (body_primitive, body_interaction) = - body.draw(self, defaults, body_layout, cursor_position); + body.draw(self, defaults, body_layout, cursor_position, viewport); let background = crate::widget::container::background(bounds, &style); @@ -151,6 +206,7 @@ where defaults, title_bar_layout, cursor_position, + viewport, show_controls, ); @@ -162,10 +218,10 @@ where body_primitive, ], }, - if is_over_pick_area { - mouse::Interaction::Grab - } else if title_bar_interaction > body_interaction { + if title_bar_interaction > body_interaction { title_bar_interaction + } else if is_over_pick_area { + mouse::Interaction::Grab } else { body_interaction }, @@ -188,15 +244,14 @@ where &mut self, defaults: &Self::Defaults, bounds: Rectangle, - style_sheet: &Self::Style, - title: &str, - title_size: u16, - title_font: Self::Font, - title_bounds: Rectangle, + style_sheet: &<Self as container::Renderer>::Style, + content: (&Element<'_, Message, Self>, Layout<'_>), controls: Option<(&Element<'_, Message, Self>, Layout<'_>)>, cursor_position: Point, + viewport: &Rectangle, ) -> Self::Output { let style = style_sheet.style(); + let (title_content, title_layout) = content; let defaults = Self::Defaults { text: defaults::Text { @@ -206,16 +261,12 @@ where let background = crate::widget::container::background(bounds, &style); - let (title_primitive, _) = text::Renderer::draw( + let (title_primitive, title_interaction) = title_content.draw( self, &defaults, - title_bounds, - title, - title_size, - title_font, - None, - HorizontalAlignment::Left, - VerticalAlignment::Top, + title_layout, + cursor_position, + viewport, ); if let Some((controls, controls_layout)) = controls { @@ -224,6 +275,7 @@ where &defaults, controls_layout, cursor_position, + viewport, ); ( @@ -234,7 +286,7 @@ where controls_primitive, ], }, - controls_interaction, + controls_interaction.max(title_interaction), ) } else { ( @@ -245,7 +297,7 @@ where } else { title_primitive }, - mouse::Interaction::default(), + title_interaction, ) } } diff --git a/graphics/src/widget/pick_list.rs b/graphics/src/widget/pick_list.rs index f42a8707..88a590b5 100644 --- a/graphics/src/widget/pick_list.rs +++ b/graphics/src/widget/pick_list.rs @@ -2,7 +2,8 @@ use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::{ - mouse, Font, HorizontalAlignment, Point, Rectangle, VerticalAlignment, + mouse, Font, HorizontalAlignment, Padding, Point, Rectangle, + VerticalAlignment, }; use iced_style::menu; @@ -19,7 +20,7 @@ where { type Style = Box<dyn StyleSheet>; - const DEFAULT_PADDING: u16 = 5; + const DEFAULT_PADDING: Padding = Padding::new(5); fn menu_style(style: &Box<dyn StyleSheet>) -> menu::Style { style.menu() @@ -30,12 +31,14 @@ where bounds: Rectangle, cursor_position: Point, selected: Option<String>, - padding: u16, + placeholder: Option<&str>, + padding: Padding, text_size: u16, font: Font, style: &Box<dyn StyleSheet>, ) -> Self::Output { let is_mouse_over = bounds.contains(cursor_position); + let is_selected = selected.is_some(); let style = if is_mouse_over { style.hovered() @@ -56,7 +59,7 @@ where font: B::ICON_FONT, size: bounds.height * style.icon_size, bounds: Rectangle { - x: bounds.x + bounds.width - f32::from(padding) * 2.0, + x: bounds.x + bounds.width - f32::from(padding.horizontal()), y: bounds.center_y(), ..bounds }, @@ -67,14 +70,18 @@ where ( Primitive::Group { - primitives: if let Some(label) = selected { + primitives: if let Some(label) = + selected.or_else(|| placeholder.map(str::to_string)) + { let label = Primitive::Text { content: label, size: f32::from(text_size), font, - color: style.text_color, + color: is_selected + .then(|| style.text_color) + .unwrap_or(style.placeholder_color), bounds: Rectangle { - x: bounds.x + f32::from(padding), + x: bounds.x + f32::from(padding.left), y: bounds.center_y(), ..bounds }, diff --git a/graphics/src/widget/progress_bar.rs b/graphics/src/widget/progress_bar.rs index 48acb3c1..32ee42c6 100644 --- a/graphics/src/widget/progress_bar.rs +++ b/graphics/src/widget/progress_bar.rs @@ -2,8 +2,6 @@ //! //! A [`ProgressBar`] has a range of possible values and a current value, //! as well as a length, height and style. -//! -//! [`ProgressBar`]: type.ProgressBar.html use crate::{Backend, Primitive, Renderer}; use iced_native::mouse; use iced_native::progress_bar; @@ -33,17 +31,20 @@ where style_sheet: &Self::Style, ) -> Self::Output { let style = style_sheet.style(); - let (range_start, range_end) = range.into_inner(); - let active_progress_width = bounds.width - * ((value - range_start) / (range_end - range_start).max(1.0)); + + let active_progress_width = if range_start >= range_end { + 0.0 + } else { + bounds.width * (value - range_start) / (range_end - range_start) + }; let background = Primitive::Group { primitives: vec![Primitive::Quad { bounds: Rectangle { ..bounds }, background: style.background, border_radius: style.border_radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, }], }; @@ -57,7 +58,7 @@ where }, background: style.bar, border_radius: style.border_radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, }; diff --git a/graphics/src/widget/qr_code.rs b/graphics/src/widget/qr_code.rs new file mode 100644 index 00000000..b3a01dd7 --- /dev/null +++ b/graphics/src/widget/qr_code.rs @@ -0,0 +1,305 @@ +//! Encode and display information in a QR code. +use crate::canvas; +use crate::{Backend, Defaults, Primitive, Renderer, Vector}; + +use iced_native::{ + layout, mouse, Color, Element, Hasher, Layout, Length, Point, Rectangle, + Size, Widget, +}; +use thiserror::Error; + +const DEFAULT_CELL_SIZE: u16 = 4; +const QUIET_ZONE: usize = 2; + +/// A type of matrix barcode consisting of squares arranged in a grid which +/// can be read by an imaging device, such as a camera. +#[derive(Debug)] +pub struct QRCode<'a> { + state: &'a State, + dark: Color, + light: Color, + cell_size: u16, +} + +impl<'a> QRCode<'a> { + /// Creates a new [`QRCode`] with the provided [`State`]. + pub fn new(state: &'a State) -> Self { + Self { + cell_size: DEFAULT_CELL_SIZE, + dark: Color::BLACK, + light: Color::WHITE, + state, + } + } + + /// Sets both the dark and light [`Color`]s of the [`QRCode`]. + pub fn color(mut self, dark: Color, light: Color) -> Self { + self.dark = dark; + self.light = light; + self + } + + /// Sets the size of the squares of the grid cell of the [`QRCode`]. + pub fn cell_size(mut self, cell_size: u16) -> Self { + self.cell_size = cell_size; + self + } +} + +impl<'a, Message, B> Widget<Message, Renderer<B>> for QRCode<'a> +where + B: Backend, +{ + fn width(&self) -> Length { + Length::Shrink + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + _renderer: &Renderer<B>, + _limits: &layout::Limits, + ) -> layout::Node { + let side_length = (self.state.width + 2 * QUIET_ZONE) as f32 + * f32::from(self.cell_size); + + layout::Node::new(Size::new( + f32::from(side_length), + f32::from(side_length), + )) + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash; + + self.state.contents.hash(state); + } + + fn draw( + &self, + _renderer: &mut Renderer<B>, + _defaults: &Defaults, + layout: Layout<'_>, + _cursor_position: Point, + _viewport: &Rectangle, + ) -> (Primitive, mouse::Interaction) { + let bounds = layout.bounds(); + let side_length = self.state.width + 2 * QUIET_ZONE; + + // Reuse cache if possible + let geometry = self.state.cache.draw(bounds.size(), |frame| { + // Scale units to cell size + frame.scale(f32::from(self.cell_size)); + + // Draw background + frame.fill_rectangle( + Point::ORIGIN, + Size::new(side_length as f32, side_length as f32), + self.light, + ); + + // Avoid drawing on the quiet zone + frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32)); + + // Draw contents + self.state + .contents + .iter() + .enumerate() + .filter(|(_, value)| **value == qrcode::Color::Dark) + .for_each(|(index, _)| { + let row = index / self.state.width; + let column = index % self.state.width; + + frame.fill_rectangle( + Point::new(column as f32, row as f32), + Size::UNIT, + self.dark, + ); + }); + }); + + ( + Primitive::Translate { + translation: Vector::new(bounds.x, bounds.y), + content: Box::new(geometry.into_primitive()), + }, + mouse::Interaction::default(), + ) + } +} + +impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for QRCode<'a> +where + B: Backend, +{ + fn into(self) -> Element<'a, Message, Renderer<B>> { + Element::new(self) + } +} + +/// The state of a [`QRCode`]. +/// +/// It stores the data that will be displayed. +#[derive(Debug)] +pub struct State { + contents: Vec<qrcode::Color>, + width: usize, + cache: canvas::Cache, +} + +impl State { + /// Creates a new [`State`] with the provided data. + /// + /// This method uses an [`ErrorCorrection::Medium`] and chooses the smallest + /// size to display the data. + pub fn new(data: impl AsRef<[u8]>) -> Result<Self, Error> { + let encoded = qrcode::QrCode::new(data)?; + + Ok(Self::build(encoded)) + } + + /// Creates a new [`State`] with the provided [`ErrorCorrection`]. + pub fn with_error_correction( + data: impl AsRef<[u8]>, + error_correction: ErrorCorrection, + ) -> Result<Self, Error> { + let encoded = qrcode::QrCode::with_error_correction_level( + data, + error_correction.into(), + )?; + + Ok(Self::build(encoded)) + } + + /// Creates a new [`State`] with the provided [`Version`] and + /// [`ErrorCorrection`]. + pub fn with_version( + data: impl AsRef<[u8]>, + version: Version, + error_correction: ErrorCorrection, + ) -> Result<Self, Error> { + let encoded = qrcode::QrCode::with_version( + data, + version.into(), + error_correction.into(), + )?; + + Ok(Self::build(encoded)) + } + + fn build(encoded: qrcode::QrCode) -> Self { + let width = encoded.width(); + let contents = encoded.into_colors(); + + Self { + contents, + width, + cache: canvas::Cache::new(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// The size of a [`QRCode`]. +/// +/// The higher the version the larger the grid of cells, and therefore the more +/// information the [`QRCode`] can carry. +pub enum Version { + /// A normal QR code version. It should be between 1 and 40. + Normal(u8), + + /// A micro QR code version. It should be between 1 and 4. + Micro(u8), +} + +impl From<Version> for qrcode::Version { + fn from(version: Version) -> Self { + match version { + Version::Normal(v) => qrcode::Version::Normal(i16::from(v)), + Version::Micro(v) => qrcode::Version::Micro(i16::from(v)), + } + } +} + +/// The error correction level. +/// +/// It controls the amount of data that can be damaged while still being able +/// to recover the original information. +/// +/// A higher error correction level allows for more corrupted data. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ErrorCorrection { + /// Low error correction. 7% of the data can be restored. + Low, + /// Medium error correction. 15% of the data can be restored. + Medium, + /// Quartile error correction. 25% of the data can be restored. + Quartile, + /// High error correction. 30% of the data can be restored. + High, +} + +impl From<ErrorCorrection> for qrcode::EcLevel { + fn from(ec_level: ErrorCorrection) -> Self { + match ec_level { + ErrorCorrection::Low => qrcode::EcLevel::L, + ErrorCorrection::Medium => qrcode::EcLevel::M, + ErrorCorrection::Quartile => qrcode::EcLevel::Q, + ErrorCorrection::High => qrcode::EcLevel::H, + } + } +} + +/// An error that occurred when building a [`State`] for a [`QRCode`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)] +pub enum Error { + /// The data is too long to encode in a QR code for the chosen [`Version`]. + #[error( + "The data is too long to encode in a QR code for the chosen version" + )] + DataTooLong, + + /// The chosen [`Version`] and [`ErrorCorrection`] combination is invalid. + #[error( + "The chosen version and error correction level combination is invalid." + )] + InvalidVersion, + + /// One or more characters in the provided data are not supported by the + /// chosen [`Version`]. + #[error( + "One or more characters in the provided data are not supported by the \ + chosen version" + )] + UnsupportedCharacterSet, + + /// The chosen ECI designator is invalid. A valid designator should be + /// between 0 and 999999. + #[error( + "The chosen ECI designator is invalid. A valid designator should be \ + between 0 and 999999." + )] + InvalidEciDesignator, + + /// A character that does not belong to the character set was found. + #[error("A character that does not belong to the character set was found")] + InvalidCharacter, +} + +impl From<qrcode::types::QrError> for Error { + fn from(error: qrcode::types::QrError) -> Self { + use qrcode::types::QrError; + + match error { + QrError::DataTooLong => Error::DataTooLong, + QrError::InvalidVersion => Error::InvalidVersion, + QrError::UnsupportedCharacterSet => Error::UnsupportedCharacterSet, + QrError::InvalidEciDesignator => Error::InvalidEciDesignator, + QrError::InvalidCharacter => Error::InvalidCharacter, + } + } +} diff --git a/graphics/src/widget/radio.rs b/graphics/src/widget/radio.rs index da41ac47..fd3d8145 100644 --- a/graphics/src/widget/radio.rs +++ b/graphics/src/widget/radio.rs @@ -42,7 +42,7 @@ where let radio = Primitive::Quad { bounds, background: style.background, - border_radius: (size / 2.0) as u16, + border_radius: size / 2.0, border_width: style.border_width, border_color: style.border_color, }; @@ -58,8 +58,8 @@ where height: bounds.height - dot_size, }, background: Background::Color(style.dot_color), - border_radius: (dot_size / 2.0) as u16, - border_width: 0, + border_radius: dot_size / 2.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }; diff --git a/graphics/src/widget/row.rs b/graphics/src/widget/row.rs index 4c1dbadc..397d80bf 100644 --- a/graphics/src/widget/row.rs +++ b/graphics/src/widget/row.rs @@ -1,7 +1,7 @@ use crate::{Backend, Primitive, Renderer}; use iced_native::mouse; use iced_native::row; -use iced_native::{Element, Layout, Point}; +use iced_native::{Element, Layout, Point, Rectangle}; /// A container that distributes its contents horizontally. pub type Row<'a, Message, Backend> = @@ -17,6 +17,7 @@ where content: &[Element<'_, Message, Self>], layout: Layout<'_>, cursor_position: Point, + viewport: &Rectangle, ) -> Self::Output { let mut mouse_interaction = mouse::Interaction::default(); @@ -26,8 +27,13 @@ where .iter() .zip(layout.children()) .map(|(child, layout)| { - let (primitive, new_mouse_interaction) = - child.draw(self, defaults, layout, cursor_position); + let (primitive, new_mouse_interaction) = child.draw( + self, + defaults, + layout, + cursor_position, + viewport, + ); if new_mouse_interaction > mouse_interaction { mouse_interaction = new_mouse_interaction; diff --git a/graphics/src/widget/rule.rs b/graphics/src/widget/rule.rs index a7a5d0e7..835ebed8 100644 --- a/graphics/src/widget/rule.rs +++ b/graphics/src/widget/rule.rs @@ -43,7 +43,7 @@ where }, background: Background::Color(style.color), border_radius: style.radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, } } else { @@ -63,7 +63,7 @@ where }, background: Background::Color(style.color), border_radius: style.radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, } }; diff --git a/graphics/src/widget/scrollable.rs b/graphics/src/widget/scrollable.rs index b149db0a..2220e4b8 100644 --- a/graphics/src/widget/scrollable.rs +++ b/graphics/src/widget/scrollable.rs @@ -15,9 +15,6 @@ pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; pub type Scrollable<'a, Message, Backend> = iced_native::Scrollable<'a, Message, Renderer<Backend>>; -const SCROLLBAR_WIDTH: u16 = 10; -const SCROLLBAR_MARGIN: u16 = 2; - impl<B> scrollable::Renderer for Renderer<B> where B: Backend, @@ -29,29 +26,45 @@ where bounds: Rectangle, content_bounds: Rectangle, offset: u32, + scrollbar_width: u16, + scrollbar_margin: u16, + scroller_width: u16, ) -> Option<scrollable::Scrollbar> { if content_bounds.height > bounds.height { + let outer_width = + scrollbar_width.max(scroller_width) + 2 * scrollbar_margin; + + let outer_bounds = Rectangle { + x: bounds.x + bounds.width - outer_width as f32, + y: bounds.y, + width: outer_width as f32, + height: bounds.height, + }; + let scrollbar_bounds = Rectangle { x: bounds.x + bounds.width - - f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), + - f32::from(outer_width / 2 + scrollbar_width / 2), y: bounds.y, - width: f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), + width: scrollbar_width as f32, height: bounds.height, }; let ratio = bounds.height / content_bounds.height; - let scrollbar_height = bounds.height * ratio; + let scroller_height = bounds.height * ratio; let y_offset = offset as f32 * ratio; let scroller_bounds = Rectangle { - x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), + x: bounds.x + bounds.width + - f32::from(outer_width / 2 + scroller_width / 2), y: scrollbar_bounds.y + y_offset, - width: scrollbar_bounds.width - f32::from(2 * SCROLLBAR_MARGIN), - height: scrollbar_height, + width: scroller_width as f32, + height: scroller_height, }; Some(scrollable::Scrollbar { + outer_bounds, bounds: scrollbar_bounds, + margin: scrollbar_margin, scroller: scrollable::Scroller { bounds: scroller_bounds, }, @@ -90,7 +103,7 @@ where }; let is_scrollbar_visible = - style.background.is_some() || style.border_width > 0; + style.background.is_some() || style.border_width > 0.0; let scroller = if is_mouse_over || state.is_scroller_grabbed() @@ -109,12 +122,7 @@ where let scrollbar = if is_scrollbar_visible { Primitive::Quad { - bounds: Rectangle { - x: scrollbar.bounds.x + f32::from(SCROLLBAR_MARGIN), - width: scrollbar.bounds.width - - f32::from(2 * SCROLLBAR_MARGIN), - ..scrollbar.bounds - }, + bounds: scrollbar.bounds, background: style .background .unwrap_or(Background::Color(Color::TRANSPARENT)), @@ -126,8 +134,16 @@ where Primitive::None }; + let scroll = Primitive::Clip { + bounds, + offset: Vector::new(0, 0), + content: Box::new(Primitive::Group { + primitives: vec![scrollbar, scroller], + }), + }; + Primitive::Group { - primitives: vec![clip, scrollbar, scroller], + primitives: vec![clip, scroll], } } else { content diff --git a/graphics/src/widget/slider.rs b/graphics/src/widget/slider.rs index 99f0a098..aeceec3f 100644 --- a/graphics/src/widget/slider.rs +++ b/graphics/src/widget/slider.rs @@ -1,9 +1,6 @@ //! Display an interactive selector of a single value from a range of values. //! //! A [`Slider`] has some local [`State`]. -//! -//! [`Slider`]: struct.Slider.html -//! [`State`]: struct.State.html use crate::{Backend, Primitive, Renderer}; use iced_native::mouse; use iced_native::slider; @@ -57,8 +54,8 @@ where height: 2.0, }, background: Background::Color(style.rail_colors.0), - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, Primitive::Quad { @@ -69,20 +66,18 @@ where height: 2.0, }, background: Background::Color(style.rail_colors.1), - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, ); - let (range_start, range_end) = range.into_inner(); - let (handle_width, handle_height, handle_border_radius) = match style .handle .shape { HandleShape::Circle { radius } => { - (f32::from(radius * 2), f32::from(radius * 2), radius) + (radius * 2.0, radius * 2.0, radius) } HandleShape::Rectangle { width, @@ -90,8 +85,14 @@ where } => (f32::from(width), f32::from(bounds.height), border_radius), }; - let handle_offset = (bounds.width - handle_width) - * ((value - range_start) / (range_end - range_start).max(1.0)); + let (range_start, range_end) = range.into_inner(); + + let handle_offset = if range_start >= range_end { + 0.0 + } else { + (bounds.width - handle_width) * (value - range_start) + / (range_end - range_start) + }; let handle = Primitive::Quad { bounds: Rectangle { diff --git a/graphics/src/widget/text_input.rs b/graphics/src/widget/text_input.rs index 575d67f5..c269022b 100644 --- a/graphics/src/widget/text_input.rs +++ b/graphics/src/widget/text_input.rs @@ -1,9 +1,6 @@ //! Display fields that can be filled with text. //! //! A [`TextInput`] has some local [`State`]. -//! -//! [`TextInput`]: struct.TextInput.html -//! [`State`]: struct.State.html use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::mouse; @@ -149,8 +146,8 @@ where background: Background::Color( style_sheet.value_color(), ), - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, offset, @@ -193,8 +190,8 @@ where background: Background::Color( style_sheet.selection_color(), ), - border_radius: 0, - border_width: 0, + border_radius: 0.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, if end == right { diff --git a/graphics/src/widget/toggler.rs b/graphics/src/widget/toggler.rs new file mode 100644 index 00000000..852d18ee --- /dev/null +++ b/graphics/src/widget/toggler.rs @@ -0,0 +1,99 @@ +//! Show toggle controls using togglers. +use crate::backend::{self, Backend}; +use crate::{Primitive, Renderer}; +use iced_native::mouse; +use iced_native::toggler; +use iced_native::Rectangle; + +pub use iced_style::toggler::{Style, StyleSheet}; + +/// Makes sure that the border radius of the toggler looks good at every size. +const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0; + +/// The space ratio between the background Quad and the Toggler bounds, and +/// between the background Quad and foreground Quad. +const SPACE_RATIO: f32 = 0.05; + +/// A toggler that can be toggled. +/// +/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`. +pub type Toggler<Message, Backend> = + iced_native::Toggler<Message, Renderer<Backend>>; + +impl<B> toggler::Renderer for Renderer<B> +where + B: Backend + backend::Text, +{ + type Style = Box<dyn StyleSheet>; + + const DEFAULT_SIZE: u16 = 20; + + fn draw( + &mut self, + bounds: Rectangle, + is_active: bool, + is_mouse_over: bool, + label: Option<Self::Output>, + style_sheet: &Self::Style, + ) -> Self::Output { + let style = if is_mouse_over { + style_sheet.hovered(is_active) + } else { + style_sheet.active(is_active) + }; + + let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO; + let space = SPACE_RATIO * bounds.height as f32; + + let toggler_background_bounds = Rectangle { + x: bounds.x + space, + y: bounds.y + space, + width: bounds.width - (2.0 * space), + height: bounds.height - (2.0 * space), + }; + + let toggler_background = Primitive::Quad { + bounds: toggler_background_bounds, + background: style.background.into(), + border_radius, + border_width: 1.0, + border_color: style.background_border.unwrap_or(style.background), + }; + + let toggler_foreground_bounds = Rectangle { + x: bounds.x + + if is_active { + bounds.width - 2.0 * space - (bounds.height - (4.0 * space)) + } else { + 2.0 * space + }, + y: bounds.y + (2.0 * space), + width: bounds.height - (4.0 * space), + height: bounds.height - (4.0 * space), + }; + + let toggler_foreground = Primitive::Quad { + bounds: toggler_foreground_bounds, + background: style.foreground.into(), + border_radius, + border_width: 1.0, + border_color: style.foreground_border.unwrap_or(style.foreground), + }; + + ( + Primitive::Group { + primitives: match label { + Some((l, _)) => { + vec![l, toggler_background, toggler_foreground] + } + None => vec![toggler_background, toggler_foreground], + }, + }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/graphics/src/widget/tooltip.rs b/graphics/src/widget/tooltip.rs new file mode 100644 index 00000000..493a6389 --- /dev/null +++ b/graphics/src/widget/tooltip.rs @@ -0,0 +1,168 @@ +//! Decorate content and apply alignment. +use crate::backend::{self, Backend}; +use crate::defaults::{self, Defaults}; +use crate::{Primitive, Renderer, Vector}; + +use iced_native::container; +use iced_native::layout::{self, Layout}; +use iced_native::{Element, Padding, Point, Rectangle, Size, Text}; + +/// An element decorating some content. +/// +/// This is an alias of an `iced_native` tooltip with a default +/// `Renderer`. +pub type Tooltip<'a, Message, Backend> = + iced_native::Tooltip<'a, Message, Renderer<Backend>>; + +pub use iced_native::tooltip::Position; + +impl<B> iced_native::tooltip::Renderer for Renderer<B> +where + B: Backend + backend::Text, +{ + const DEFAULT_PADDING: u16 = 5; + + fn draw<Message>( + &mut self, + defaults: &Defaults, + cursor_position: Point, + content_layout: Layout<'_>, + viewport: &Rectangle, + content: &Element<'_, Message, Self>, + tooltip: &Text<Self>, + position: Position, + style_sheet: &<Self as container::Renderer>::Style, + gap: u16, + padding: u16, + ) -> Self::Output { + let (content, mouse_interaction) = content.draw( + self, + &defaults, + content_layout, + cursor_position, + viewport, + ); + + let bounds = content_layout.bounds(); + + if bounds.contains(cursor_position) { + use iced_native::Widget; + + let gap = f32::from(gap); + let style = style_sheet.style(); + + let defaults = Defaults { + text: defaults::Text { + color: style.text_color.unwrap_or(defaults.text.color), + }, + }; + + let text_layout = Widget::<(), Self>::layout( + tooltip, + self, + &layout::Limits::new(Size::ZERO, viewport.size()) + .pad(Padding::new(padding)), + ); + + let padding = f32::from(padding); + let text_bounds = text_layout.bounds(); + let x_center = bounds.x + (bounds.width - text_bounds.width) / 2.0; + let y_center = + bounds.y + (bounds.height - text_bounds.height) / 2.0; + + let mut tooltip_bounds = { + let offset = match position { + Position::Top => Vector::new( + x_center, + bounds.y - text_bounds.height - gap - padding, + ), + Position::Bottom => Vector::new( + x_center, + bounds.y + bounds.height + gap + padding, + ), + Position::Left => Vector::new( + bounds.x - text_bounds.width - gap - padding, + y_center, + ), + Position::Right => Vector::new( + bounds.x + bounds.width + gap + padding, + y_center, + ), + Position::FollowCursor => Vector::new( + cursor_position.x, + cursor_position.y - text_bounds.height, + ), + }; + + Rectangle { + x: offset.x - padding, + y: offset.y - padding, + width: text_bounds.width + padding * 2.0, + height: text_bounds.height + padding * 2.0, + } + }; + + if tooltip_bounds.x < viewport.x { + tooltip_bounds.x = viewport.x; + } else if viewport.x + viewport.width + < tooltip_bounds.x + tooltip_bounds.width + { + tooltip_bounds.x = + viewport.x + viewport.width - tooltip_bounds.width; + } + + if tooltip_bounds.y < viewport.y { + tooltip_bounds.y = viewport.y; + } else if viewport.y + viewport.height + < tooltip_bounds.y + tooltip_bounds.height + { + tooltip_bounds.y = + viewport.y + viewport.height - tooltip_bounds.height; + } + + let (tooltip, _) = Widget::<(), Self>::draw( + tooltip, + self, + &defaults, + Layout::with_offset( + Vector::new( + tooltip_bounds.x + padding, + tooltip_bounds.y + padding, + ), + &text_layout, + ), + cursor_position, + viewport, + ); + + ( + Primitive::Group { + primitives: vec![ + content, + Primitive::Clip { + bounds: *viewport, + offset: Vector::new(0, 0), + content: Box::new( + if let Some(background) = + crate::container::background( + tooltip_bounds, + &style, + ) + { + Primitive::Group { + primitives: vec![background, tooltip], + } + } else { + tooltip + }, + ), + }, + ], + }, + mouse_interaction, + ) + } else { + (content, mouse_interaction) + } + } +} diff --git a/graphics/src/window.rs b/graphics/src/window.rs index 3e74db5f..365ddfbc 100644 --- a/graphics/src/window.rs +++ b/graphics/src/window.rs @@ -4,7 +4,7 @@ mod compositor; #[cfg(feature = "opengl")] mod gl_compositor; -pub use compositor::Compositor; +pub use compositor::{Compositor, SwapChainError}; #[cfg(feature = "opengl")] pub use gl_compositor::GLCompositor; diff --git a/graphics/src/window/compositor.rs b/graphics/src/window/compositor.rs index 7674f227..de2a6990 100644 --- a/graphics/src/window/compositor.rs +++ b/graphics/src/window/compositor.rs @@ -1,6 +1,9 @@ use crate::{Color, Error, Viewport}; + use iced_native::mouse; + use raw_window_handle::HasRawWindowHandle; +use thiserror::Error; /// A graphics compositor that can draw to windows. pub trait Compositor: Sized { @@ -16,14 +19,15 @@ pub trait Compositor: Sized { /// The swap chain of the backend. type SwapChain; - /// Creates a new [`Backend`]. - /// - /// [`Backend`]: trait.Backend.html - fn new(settings: Self::Settings) -> Result<(Self, Self::Renderer), Error>; + /// Creates a new [`Compositor`]. + fn new<W: HasRawWindowHandle>( + settings: Self::Settings, + compatible_window: Option<&W>, + ) -> Result<(Self, Self::Renderer), Error>; /// Crates a new [`Surface`] for the given window. /// - /// [`Surface`]: #associatedtype.Surface + /// [`Surface`]: Self::Surface fn create_surface<W: HasRawWindowHandle>( &mut self, window: &W, @@ -31,8 +35,8 @@ pub trait Compositor: Sized { /// Crates a new [`SwapChain`] for the given [`Surface`]. /// - /// [`SwapChain`]: #associatedtype.SwapChain - /// [`Surface`]: #associatedtype.Surface + /// [`SwapChain`]: Self::SwapChain + /// [`Surface`]: Self::Surface fn create_swap_chain( &mut self, surface: &Self::Surface, @@ -42,8 +46,7 @@ pub trait Compositor: Sized { /// Draws the output primitives to the next frame of the given [`SwapChain`]. /// - /// [`SwapChain`]: #associatedtype.SwapChain - /// [`Surface`]: #associatedtype.Surface + /// [`SwapChain`]: Self::SwapChain fn draw<T: AsRef<str>>( &mut self, renderer: &mut Self::Renderer, @@ -52,5 +55,26 @@ pub trait Compositor: Sized { background_color: Color, output: &<Self::Renderer as iced_native::Renderer>::Output, overlay: &[T], - ) -> mouse::Interaction; + ) -> Result<mouse::Interaction, SwapChainError>; +} + +/// Result of an unsuccessful call to [`Compositor::draw`]. +#[derive(Clone, PartialEq, Eq, Debug, Error)] +pub enum SwapChainError { + /// A timeout was encountered while trying to acquire the next frame. + #[error( + "A timeout was encountered while trying to acquire the next frame" + )] + Timeout, + /// The underlying surface has changed, and therefore the swap chain must be updated. + #[error( + "The underlying surface has changed, and therefore the swap chain must be updated." + )] + Outdated, + /// The swap chain has been lost and needs to be recreated. + #[error("The swap chain has been lost and needs to be recreated")] + Lost, + /// There is no more memory left to allocate a new frame. + #[error("There is no more memory left to allocate a new frame")] + OutOfMemory, } diff --git a/graphics/src/window/gl_compositor.rs b/graphics/src/window/gl_compositor.rs index 1f37642e..34d70be3 100644 --- a/graphics/src/window/gl_compositor.rs +++ b/graphics/src/window/gl_compositor.rs @@ -15,28 +15,27 @@ use core::ffi::c_void; /// If you implement an OpenGL renderer, you can implement this trait to ease /// integration with existing windowing shells, like `iced_glutin`. pub trait GLCompositor: Sized { - /// The renderer of the [`Compositor`]. + /// The renderer of the [`GLCompositor`]. /// /// This should point to your renderer type, which could be a type alias /// of the [`Renderer`] provided in this crate with with a specific /// [`Backend`]. /// - /// [`Compositor`]: trait.Compositor.html - /// [`Renderer`]: ../struct.Renderer.html - /// [`Backend`]: ../backend/trait.Backend.html + /// [`Renderer`]: crate::Renderer + /// [`Backend`]: crate::Backend type Renderer: iced_native::Renderer; - /// The settings of the [`Compositor`]. + /// The settings of the [`GLCompositor`]. /// /// It's up to you to decide the configuration supported by your renderer! type Settings: Default; - /// Creates a new [`Compositor`] and [`Renderer`] with the given + /// Creates a new [`GLCompositor`] and [`Renderer`] with the given /// [`Settings`] and an OpenGL address loader function. /// - /// [`Compositor`]: trait.Compositor.html - /// [`Renderer`]: #associatedtype.Renderer - /// [`Backend`]: ../backend/trait.Backend.html + /// [`Renderer`]: crate::Renderer + /// [`Backend`]: crate::Backend + /// [`Settings`]: Self::Settings #[allow(unsafe_code)] unsafe fn new( settings: Self::Settings, @@ -44,19 +43,15 @@ pub trait GLCompositor: Sized { ) -> Result<(Self, Self::Renderer), Error>; /// Returns the amount of samples that should be used when configuring - /// an OpenGL context for this [`Compositor`]. - /// - /// [`Compositor`]: trait.Compositor.html + /// an OpenGL context for this [`GLCompositor`]. fn sample_count(settings: &Self::Settings) -> u32; - /// Resizes the viewport of the [`Compositor`]. - /// - /// [`Compositor`]: trait.Compositor.html + /// Resizes the viewport of the [`GLCompositor`]. fn resize_viewport(&mut self, physical_size: Size<u32>); /// Draws the provided output with the given [`Renderer`]. /// - /// [`Compositor`]: trait.Compositor.html + /// [`Renderer`]: crate::Renderer fn draw<T: AsRef<str>>( &mut self, renderer: &mut Self::Renderer, |