diff options
author | 2021-07-22 12:37:39 -0500 | |
---|---|---|
committer | 2021-07-22 12:37:39 -0500 | |
commit | e822f654e44d2d7375b7fda966bb772055f377d4 (patch) | |
tree | 8707561f1bb09c9e58cc9d9884bfb16d956f9f65 /graphics | |
parent | 1c06920158e1a47977b2762bf8b34e56fd1a935a (diff) | |
parent | dc0b96ce407283f2ffd9add5ad339f89097555d3 (diff) | |
download | iced-e822f654e44d2d7375b7fda966bb772055f377d4.tar.gz iced-e822f654e44d2d7375b7fda966bb772055f377d4.tar.bz2 iced-e822f654e44d2d7375b7fda966bb772055f377d4.zip |
Merge branch 'master' of https://github.com/hecrj/iced into wgpu_outdatedframe
Diffstat (limited to 'graphics')
-rw-r--r-- | graphics/Cargo.toml | 9 | ||||
-rw-r--r-- | graphics/src/layer.rs | 40 | ||||
-rw-r--r-- | graphics/src/overlay/menu.rs | 12 | ||||
-rw-r--r-- | graphics/src/viewport.rs | 2 | ||||
-rw-r--r-- | graphics/src/widget.rs | 6 | ||||
-rw-r--r-- | graphics/src/widget/button.rs | 4 | ||||
-rw-r--r-- | graphics/src/widget/canvas.rs | 4 | ||||
-rw-r--r-- | graphics/src/widget/canvas/frame.rs | 2 | ||||
-rw-r--r-- | graphics/src/widget/canvas/program.rs | 2 | ||||
-rw-r--r-- | graphics/src/widget/image.rs | 5 | ||||
-rw-r--r-- | graphics/src/widget/image/viewer.rs | 55 | ||||
-rw-r--r-- | graphics/src/widget/pane_grid.rs | 134 | ||||
-rw-r--r-- | graphics/src/widget/pick_list.rs | 21 | ||||
-rw-r--r-- | graphics/src/widget/scrollable.rs | 10 | ||||
-rw-r--r-- | graphics/src/widget/toggler.rs | 99 | ||||
-rw-r--r-- | graphics/src/widget/tooltip.rs | 168 | ||||
-rw-r--r-- | graphics/src/window/compositor.rs | 5 |
17 files changed, 498 insertions, 80 deletions
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 73dc47bf..ea9471c6 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -1,6 +1,6 @@ [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" @@ -28,11 +28,11 @@ version = "1.4" features = ["derive"] [dependencies.iced_native] -version = "0.3" +version = "0.4" path = "../native" [dependencies.iced_style] -version = "0.2" +version = "0.3" path = "../style" [dependencies.lyon] @@ -42,9 +42,10 @@ optional = true [dependencies.qrcode] version = "0.12" optional = true +default-features = false [dependencies.font-kit] -version = "0.8" +version = "0.10" optional = true [package.metadata.docs.rs] diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index ab40b114..7dce1d4c 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -82,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 } @@ -91,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 { @@ -109,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, @@ -128,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 { @@ -146,7 +157,7 @@ impl<'a> Layer<'a> { }); } 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), @@ -167,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 @@ -175,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 { @@ -195,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(), @@ -209,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(), diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs index ffe998c5..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; @@ -45,7 +45,7 @@ where viewport: &Rectangle, options: &[T], hovered_option: Option<usize>, - padding: u16, + padding: Padding, text_size: u16, font: Font, style: &Style, @@ -53,7 +53,7 @@ where use std::f32; let is_mouse_over = bounds.contains(cursor_position); - let option_height = text_size as usize + padding as usize * 2; + let option_height = (text_size + padding.vertical()) as usize; let mut primitives = Vec::new(); @@ -72,7 +72,7 @@ where x: bounds.x, 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 { @@ -88,7 +88,7 @@ where 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/viewport.rs b/graphics/src/viewport.rs index 78d539af..2c0b541a 100644 --- a/graphics/src/viewport.rs +++ b/graphics/src/viewport.rs @@ -31,7 +31,7 @@ impl Viewport { /// Returns the physical width of the [`Viewport`]. pub fn physical_width(&self) -> u32 { - self.physical_size.height + self.physical_size.width } /// Returns the physical height of the [`Viewport`]. diff --git a/graphics/src/widget.rs b/graphics/src/widget.rs index 159ca91b..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; diff --git a/graphics/src/widget/button.rs b/graphics/src/widget/button.rs index 2e3f78ca..60400ed8 100644 --- a/graphics/src/widget/button.rs +++ b/graphics/src/widget/button.rs @@ -5,7 +5,7 @@ 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; @@ -21,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>; diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index 95ede50f..7897c8ec 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -154,9 +154,9 @@ 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(); diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs index b86f9e04..5af9d11f 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -54,7 +54,7 @@ impl Frame { self.size.width } - /// Returns the width of the [`Frame`]. + /// Returns the height of the [`Frame`]. #[inline] pub fn height(&self) -> f32 { self.size.height diff --git a/graphics/src/widget/canvas/program.rs b/graphics/src/widget/canvas/program.rs index d703caad..85a2f67b 100644 --- a/graphics/src/widget/canvas/program.rs +++ b/graphics/src/widget/canvas/program.rs @@ -34,7 +34,7 @@ pub trait Program<Message> { /// [`Geometry`] can be easily generated with a [`Frame`] or stored in a /// [`Cache`]. /// - /// [`Frame`]: crate::widget::canvas::Cache + /// [`Frame`]: crate::widget::canvas::Frame /// [`Cache`]: crate::widget::canvas::Cache fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry>; 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 f09984fc..92cdbb77 100644 --- a/graphics/src/widget/pane_grid.rs +++ b/graphics/src/widget/pane_grid.rs @@ -6,23 +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.2/examples/pane_grid -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, 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. /// @@ -34,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 @@ -61,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; @@ -78,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(); @@ -108,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 }, @@ -127,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, &bounds); + body.draw(self, defaults, body_layout, cursor_position, viewport); let background = crate::widget::container::background(bounds, &style); @@ -150,6 +206,7 @@ where defaults, title_bar_layout, cursor_position, + viewport, show_controls, ); @@ -161,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 }, @@ -187,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 { @@ -205,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 { @@ -223,7 +275,7 @@ where &defaults, controls_layout, cursor_position, - &bounds, + 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/scrollable.rs b/graphics/src/widget/scrollable.rs index 57065ba2..2220e4b8 100644 --- a/graphics/src/widget/scrollable.rs +++ b/graphics/src/widget/scrollable.rs @@ -134,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/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/compositor.rs b/graphics/src/window/compositor.rs index 39485153..7342245c 100644 --- a/graphics/src/window/compositor.rs +++ b/graphics/src/window/compositor.rs @@ -17,7 +17,10 @@ pub trait Compositor: Sized { type SwapChain; /// Creates a new [`Compositor`]. - fn new(settings: Self::Settings) -> Result<(Self, Self::Renderer), Error>; + fn new<W: HasRawWindowHandle>( + settings: Self::Settings, + compatible_window: Option<&W>, + ) -> Result<(Self, Self::Renderer), Error>; /// Crates a new [`Surface`] for the given window. /// |