From 6ce12fc0c60adbd169676ee985e8529831633a76 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 3 Oct 2022 15:53:39 -0700 Subject: Use `Cow<'static, [u8]>` in image/svg, add constructors taking &[u8]` This should resolve https://github.com/iced-rs/iced/issues/580 by providing a way to use an image included with `include_bytes!` without needing to copy it to a `Vec` to create an image handle. It would be nice if these methods could also be `const`, but that isn't possible due to the hashing being done. This is technically a breaking change since `Handle::data()` is public. But if that is used, it's most likely in used somewhere that only relies on the type derefing to `&[u8]`. --- native/src/image.rs | 31 +++++++++++++++++++++++++++---- native/src/svg.rs | 12 ++++++++++-- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/native/src/image.rs b/native/src/image.rs index 516eb2db..41e41aa4 100644 --- a/native/src/image.rs +++ b/native/src/image.rs @@ -1,6 +1,7 @@ //! Load and draw raster graphics. use crate::{Hasher, Rectangle}; +use std::borrow::Cow; use std::hash::{Hash, Hasher as _}; use std::path::PathBuf; use std::sync::Arc; @@ -29,7 +30,22 @@ impl Handle { Self::from_data(Data::Pixels { width, height, - pixels, + pixels: Cow::Owned(pixels), + }) + } + + /// Like [`Handle::from_pixels`], but from static pixel data. + /// + /// Useful for images included in binary, for instance with [`include_bytes!`]. + pub fn from_static_pixels( + width: u32, + height: u32, + pixels: &'static [u8], + ) -> Handle { + Self::from_data(Data::Pixels { + width, + height, + pixels: Cow::Borrowed(pixels), }) } @@ -40,7 +56,14 @@ impl Handle { /// This is useful if you already have your image loaded in-memory, maybe /// because you downloaded or generated it procedurally. pub fn from_memory(bytes: Vec) -> Handle { - Self::from_data(Data::Bytes(bytes)) + Self::from_data(Data::Bytes(Cow::Owned(bytes))) + } + + /// Like [`Handle::from_memory`], but from static image data. + /// + /// Useful for images included in binary, for instance with [`include_bytes!`]. + pub fn from_static_memory(bytes: &'static [u8]) -> Handle { + Self::from_data(Data::Bytes(Cow::Borrowed(bytes))) } fn from_data(data: Data) -> Handle { @@ -86,7 +109,7 @@ pub enum Data { Path(PathBuf), /// In-memory data - Bytes(Vec), + Bytes(Cow<'static, [u8]>), /// Decoded image pixels in BGRA format. Pixels { @@ -95,7 +118,7 @@ pub enum Data { /// The height of the image. height: u32, /// The pixels. - pixels: Vec, + pixels: Cow<'static, [u8]>, }, } diff --git a/native/src/svg.rs b/native/src/svg.rs index f86fec5b..c89eed3f 100644 --- a/native/src/svg.rs +++ b/native/src/svg.rs @@ -1,6 +1,7 @@ //! Load and draw vector graphics. use crate::{Hasher, Rectangle}; +use std::borrow::Cow; use std::hash::{Hash, Hasher as _}; use std::path::PathBuf; use std::sync::Arc; @@ -25,7 +26,14 @@ impl Handle { /// This is useful if you already have your SVG data in-memory, maybe /// because you downloaded or generated it procedurally. pub fn from_memory(bytes: impl Into>) -> Handle { - Self::from_data(Data::Bytes(bytes.into())) + Self::from_data(Data::Bytes(Cow::Owned(bytes.into()))) + } + + /// Like [`Handle::from_memory`], but from static image data. + /// + /// Useful for images included in binary, for instance with [`include_bytes!`]. + pub fn from_static_memory(bytes: &'static [u8]) -> Handle { + Self::from_data(Data::Bytes(Cow::Borrowed(bytes))) } fn from_data(data: Data) -> Handle { @@ -64,7 +72,7 @@ pub enum Data { /// In-memory data /// /// Can contain an SVG string or a gzip compressed data. - Bytes(Vec), + Bytes(Cow<'static, [u8]>), } impl std::fmt::Debug for Data { -- cgit From 2c103f8654943c773b6de3c70eb2927e92219422 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 27 Oct 2022 11:48:42 -0700 Subject: Constrain padding to inner & outer sizes --- core/src/padding.rs | 14 ++++++++++++++ core/src/size.rs | 27 +++++++++++++++++++++++++++ native/src/widget/button.rs | 9 ++++++--- native/src/widget/container.rs | 10 ++++++---- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/core/src/padding.rs b/core/src/padding.rs index 22467d6b..64c95c89 100644 --- a/core/src/padding.rs +++ b/core/src/padding.rs @@ -1,3 +1,5 @@ +use crate::Size; + /// An amount of space to pad for each side of a box /// /// You can leverage the `From` trait to build [`Padding`] conveniently: @@ -71,6 +73,18 @@ impl Padding { pub fn horizontal(self) -> u16 { self.left + self.right } + + /// Constrains the padding to fit between the inner & outer [`Size`] + pub fn constrain(self, inner: Size, outer: Size) -> Self { + let available = (outer - inner).max(Size::ZERO); + + Padding { + top: self.top.min((available.height / 2.0) as u16), + right: self.right.min((available.width / 2.0) as u16), + bottom: self.bottom.min((available.height / 2.0) as u16), + left: self.left.min((available.width / 2.0) as u16), + } + } } impl std::convert::From for Padding { diff --git a/core/src/size.rs b/core/src/size.rs index 2db33a88..31f3171b 100644 --- a/core/src/size.rs +++ b/core/src/size.rs @@ -34,6 +34,22 @@ impl Size { height: self.height + padding.vertical() as f32, } } + + /// Returns the minimum of each component of this size and another + pub fn min(self, other: Self) -> Self { + Size { + width: self.width.min(other.width), + height: self.height.min(other.height), + } + } + + /// Returns the maximum of each component of this size and another + pub fn max(self, other: Self) -> Self { + Size { + width: self.width.max(other.width), + height: self.height.max(other.height), + } + } } impl From<[f32; 2]> for Size { @@ -68,3 +84,14 @@ impl From for Vector { Vector::new(size.width, size.height) } } + +impl std::ops::Sub for Size { + type Output = Size; + + fn sub(self, rhs: Self) -> Self::Output { + Size { + width: self.width - rhs.width, + height: self.height - rhs.height, + } + } +} diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 6c0b8f6e..e927998c 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -426,12 +426,15 @@ pub fn layout( padding: Padding, layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node, ) -> layout::Node { - let limits = limits.width(width).height(height).pad(padding); + let limits = limits.width(width).height(height); + + let mut content = layout_content(renderer, &limits.pad(padding)); + + let padding = padding.constrain(content.size(), limits.max()); - let mut content = layout_content(renderer, &limits); content.move_to(Point::new(padding.left.into(), padding.top.into())); - let size = limits.resolve(content.size()).pad(padding); + let size = limits.pad(padding).resolve(content.size()).pad(padding); layout::Node::with_children(size, vec![content]) } diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 2afad3f2..cc886dcb 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -293,11 +293,13 @@ pub fn layout( .max_width(max_width) .max_height(max_height) .width(width) - .height(height) - .pad(padding); + .height(height); - let mut content = layout_content(renderer, &limits.loose()); - let size = limits.resolve(content.size()); + let mut content = layout_content(renderer, &limits.pad(padding).loose()); + + let padding = padding.constrain(content.size(), limits.max()); + + let size = limits.pad(padding).resolve(content.size()); content.move_to(Point::new(padding.left.into(), padding.top.into())); content.align( -- cgit From ea4b3cd6aeb3c4dcb5113389c85f577fd3714682 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 27 Oct 2022 12:10:47 -0700 Subject: Fix text input padding --- native/src/widget/text_input.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index c2d25520..6ac4a2dd 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -350,15 +350,21 @@ where { let text_size = size.unwrap_or_else(|| renderer.default_size()); - let limits = limits + let text_limits = limits .pad(padding) .width(width) .height(Length::Units(text_size)); + let limits = limits.width(width).height(Length::Shrink); + + let mut text = layout::Node::new(text_limits.resolve(Size::ZERO)); + + let padding = padding.constrain(text.size(), limits.max()); - let mut text = layout::Node::new(limits.resolve(Size::ZERO)); text.move_to(Point::new(padding.left.into(), padding.top.into())); - layout::Node::with_children(text.size().pad(padding), vec![text]) + let size = limits.pad(padding).resolve(text.size()).pad(padding); + + layout::Node::with_children(size, vec![text]) } /// Processes an [`Event`] and updates the [`State`] of a [`TextInput`] -- cgit From b761ab5e1d4ceaae6ac12c28f45dfcd84c76c329 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Wed, 2 Nov 2022 16:49:18 -0700 Subject: Add maximize / restore to PaneGrid --- native/src/widget/pane_grid.rs | 129 ++++++++++++++++++++++++++--------- native/src/widget/pane_grid/state.rs | 54 ++++++++------- 2 files changed, 128 insertions(+), 55 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 96cf78ef..1b55537f 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -101,7 +101,7 @@ where Renderer::Theme: StyleSheet + container::StyleSheet, { state: &'a state::Internal, - elements: Vec<(Pane, Content<'a, Message, Renderer>)>, + elements: Elements>, width: Length, height: Length, spacing: u16, @@ -119,17 +119,30 @@ where /// Creates a [`PaneGrid`] with the given [`State`] and view function. /// /// The view function will be called to display each [`Pane`] present in the - /// [`State`]. + /// [`State`]. [`bool`] is set if the pane is maximized. pub fn new( state: &'a State, - view: impl Fn(Pane, &'a T) -> Content<'a, Message, Renderer>, + view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Renderer>, ) -> Self { - let elements = { - state - .panes - .iter() - .map(|(pane, pane_state)| (*pane, view(*pane, pane_state))) - .collect() + let elements = if let Some((pane, pane_state)) = + state.maximized.and_then(|pane| { + state.panes.get(&pane).map(|pane_state| (pane, pane_state)) + }) { + Elements::Maximized( + pane, + view(pane, pane_state, true), + Node::Pane(pane), + ) + } else { + Elements::Normal( + state + .panes + .iter() + .map(|(pane, pane_state)| { + (*pane, view(*pane, pane_state, false)) + }) + .collect(), + ) }; Self { @@ -232,11 +245,18 @@ where } fn diff(&self, tree: &mut Tree) { - tree.diff_children_custom( - &self.elements, - |state, (_, content)| content.diff(state), - |(_, content)| content.state(), - ) + match &self.elements { + Elements::Normal(elements) => tree.diff_children_custom( + elements, + |state, (_, content)| content.diff(state), + |(_, content)| content.state(), + ), + Elements::Maximized(_, content, _) => tree.diff_children_custom( + &[content], + |state, content| content.diff(state), + |content| content.state(), + ), + } } fn width(&self) -> Length { @@ -255,11 +275,11 @@ where layout( renderer, limits, - self.state, + self.elements.node(self.state), self.width, self.height, self.spacing, - self.elements.iter().map(|(pane, content)| (*pane, content)), + self.elements.iter(), |element, renderer, limits| element.layout(renderer, limits), ) } @@ -278,13 +298,13 @@ where let event_status = update( action, - self.state, + self.elements.node(self.state), &event, layout, cursor_position, shell, self.spacing, - self.elements.iter().map(|(pane, content)| (*pane, content)), + self.elements.iter(), &self.on_click, &self.on_drag, &self.on_resize, @@ -297,7 +317,7 @@ where .zip(&mut tree.children) .zip(layout.children()) .map(|(((pane, content), tree), layout)| { - let is_picked = picked_pane == Some(*pane); + let is_picked = picked_pane == Some(pane); content.on_event( tree, @@ -323,7 +343,7 @@ where ) -> mouse::Interaction { mouse_interaction( tree.state.downcast_ref(), - self.state, + self.elements.node(self.state), layout, cursor_position, self.spacing, @@ -361,7 +381,7 @@ where ) { draw( tree.state.downcast_ref(), - self.state, + self.elements.node(self.state), layout, cursor_position, renderer, @@ -374,7 +394,7 @@ where self.elements .iter() .zip(&tree.children) - .map(|((pane, content), tree)| (*pane, (content, tree))), + .map(|((pane, content), tree)| (pane, (content, tree))), |(content, tree), renderer, style, @@ -429,7 +449,7 @@ where pub fn layout( renderer: &Renderer, limits: &layout::Limits, - state: &state::Internal, + node: &Node, width: Length, height: Length, spacing: u16, @@ -439,7 +459,7 @@ pub fn layout( let limits = limits.width(width).height(height); let size = limits.resolve(Size::ZERO); - let regions = state.pane_regions(f32::from(spacing), size); + let regions = node.pane_regions(f32::from(spacing), size); let children = elements .filter_map(|(pane, element)| { let region = regions.get(&pane)?; @@ -464,7 +484,7 @@ pub fn layout( /// accordingly. pub fn update<'a, Message, T: Draggable>( action: &mut state::Action, - state: &state::Internal, + node: &Node, event: &Event, layout: Layout<'_>, cursor_position: Point, @@ -492,7 +512,7 @@ pub fn update<'a, Message, T: Draggable>( cursor_position.y - bounds.y, ); - let splits = state.split_regions( + let splits = node.split_regions( f32::from(spacing), Size::new(bounds.width, bounds.height), ); @@ -570,7 +590,7 @@ pub fn update<'a, Message, T: Draggable>( if let Some((split, _)) = action.picked_split() { let bounds = layout.bounds(); - let splits = state.split_regions( + let splits = node.split_regions( f32::from(spacing), Size::new(bounds.width, bounds.height), ); @@ -642,7 +662,7 @@ fn click_pane<'a, Message, T>( /// Returns the current [`mouse::Interaction`] of a [`PaneGrid`]. pub fn mouse_interaction( action: &state::Action, - state: &state::Internal, + node: &Node, layout: Layout<'_>, cursor_position: Point, spacing: u16, @@ -658,7 +678,7 @@ pub fn mouse_interaction( let bounds = layout.bounds(); let splits = - state.split_regions(f32::from(spacing), bounds.size()); + node.split_regions(f32::from(spacing), bounds.size()); let relative_cursor = Point::new( cursor_position.x - bounds.x, @@ -687,7 +707,7 @@ pub fn mouse_interaction( /// Draws a [`PaneGrid`]. pub fn draw( action: &state::Action, - state: &state::Internal, + node: &Node, layout: Layout<'_>, cursor_position: Point, renderer: &mut Renderer, @@ -717,7 +737,7 @@ pub fn draw( .and_then(|(split, axis)| { let bounds = layout.bounds(); - let splits = state.split_regions(f32::from(spacing), bounds.size()); + let splits = node.split_regions(f32::from(spacing), bounds.size()); let (_axis, region, ratio) = splits.get(&split)?; @@ -736,7 +756,7 @@ pub fn draw( ); let splits = - state.split_regions(f32::from(spacing), bounds.size()); + node.split_regions(f32::from(spacing), bounds.size()); let (_split, axis, region) = hovered_split( splits.iter(), @@ -897,3 +917,48 @@ fn hovered_split<'a>( }) .next() } + +/// TODO +#[derive(Debug)] +pub enum Elements { + /// TODO + Normal(Vec<(Pane, T)>), + /// TODO + Maximized(Pane, T, Node), +} + +impl Elements { + /// TODO + pub fn iter(&self) -> Box + '_> { + match self { + Elements::Normal(elements) => Box::new( + elements.iter().map(|(pane, content)| (*pane, content)), + ), + Elements::Maximized(pane, content, _) => { + Box::new(std::iter::once((*pane, content))) + } + } + } + + /// TODO + pub fn iter_mut( + &mut self, + ) -> Box + '_> { + match self { + Elements::Normal(elements) => Box::new( + elements.iter_mut().map(|(pane, content)| (*pane, content)), + ), + Elements::Maximized(pane, content, _) => { + Box::new(std::iter::once((*pane, content))) + } + } + } + + /// TODO + pub fn node<'a>(&'a self, state: &'a state::Internal) -> &'a Node { + match self { + Elements::Normal(_) => state.layout(), + Elements::Maximized(_, _, node) => node, + } + } +} diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index cdca6267..92d26f5a 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -4,9 +4,9 @@ use crate::widget::pane_grid::{ Axis, Configuration, Direction, Node, Pane, Split, }; -use crate::{Point, Rectangle, Size}; +use crate::{Point, Size}; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; /// The state of a [`PaneGrid`]. /// @@ -31,6 +31,9 @@ pub struct State { /// /// [`PaneGrid`]: crate::widget::PaneGrid pub internal: Internal, + + /// The maximized [`Pane`] of the [`PaneGrid`] + pub(super) maximized: Option, } impl State { @@ -52,7 +55,11 @@ impl State { let internal = Internal::from_configuration(&mut panes, config.into(), 0); - State { panes, internal } + State { + panes, + internal, + maximized: None, + } } /// Returns the total amount of panes in the [`State`]. @@ -194,12 +201,28 @@ impl State { /// Closes the given [`Pane`] and returns its internal state and its closest /// sibling, if it exists. pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> { + if self.maximized == Some(*pane) { + let _ = self.maximized.take(); + } + if let Some(sibling) = self.internal.layout.remove(pane) { self.panes.remove(pane).map(|state| (state, sibling)) } else { None } } + + /// Maximize the given [`Pane`]. Only this pane will be rendered by the + /// [`PaneGrid`] until [`Self::restore()`] is called. + pub fn maximize(&mut self, pane: &Pane) { + self.maximized = Some(*pane); + } + + /// Restore the currently maximized [`Pane`] to it's normal size. All panes + /// will be rendered by the [`PaneGrid`] + pub fn restore(&mut self) { + let _ = self.maximized.take(); + } } /// The internal state of a [`PaneGrid`]. @@ -226,11 +249,13 @@ impl Internal { let Internal { layout: a, last_id: next_id, + .. } = Self::from_configuration(panes, *a, next_id); let Internal { layout: b, last_id: next_id, + .. } = Self::from_configuration(panes, *b, next_id); ( @@ -304,25 +329,8 @@ impl Action { } impl Internal { - /// Calculates the current [`Pane`] regions from the [`PaneGrid`] layout. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - pub fn pane_regions( - &self, - spacing: f32, - size: Size, - ) -> BTreeMap { - self.layout.pane_regions(spacing, size) - } - - /// Calculates the current [`Split`] regions from the [`PaneGrid`] layout. - /// - /// [`PaneGrid`]: crate::widget::PaneGrid - pub fn split_regions( - &self, - spacing: f32, - size: Size, - ) -> BTreeMap { - self.layout.split_regions(spacing, size) + /// The layout [`Node`] of the [`Internal`] state + pub fn layout(&self) -> &Node { + &self.layout } } -- cgit From e28c441c693ce9f42d60c98ba6892c6fc78d6724 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Wed, 2 Nov 2022 17:05:58 -0700 Subject: Update pane_grid example w/ maximize / restore --- examples/pane_grid/src/main.rs | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index ae8fa22b..c9f1376c 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -29,6 +29,8 @@ enum Message { Dragged(pane_grid::DragEvent), Resized(pane_grid::ResizeEvent), TogglePin(pane_grid::Pane), + Maximize(pane_grid::Pane), + Restore, Close(pane_grid::Pane), CloseFocused, } @@ -114,6 +116,10 @@ impl Application for Example { *is_pinned = !*is_pinned; } } + Message::Maximize(pane) => self.panes.maximize(&pane), + Message::Restore => { + self.panes.restore(); + } Message::Close(pane) => { if let Some((_, sibling)) = self.panes.close(&pane) { self.focus = Some(sibling); @@ -157,7 +163,7 @@ impl Application for Example { let focus = self.focus; let total_panes = self.panes.len(); - let pane_grid = PaneGrid::new(&self.panes, |id, pane| { + let pane_grid = PaneGrid::new(&self.panes, |id, pane, is_maximized| { let is_focused = focus == Some(id); let pin_button = button( @@ -178,7 +184,12 @@ impl Application for Example { .spacing(5); let title_bar = pane_grid::TitleBar::new(title) - .controls(view_controls(id, total_panes, pane.is_pinned)) + .controls(view_controls( + id, + total_panes, + pane.is_pinned, + is_maximized, + )) .padding(10) .style(if is_focused { style::title_bar_focused @@ -314,16 +325,35 @@ fn view_controls<'a>( pane: pane_grid::Pane, total_panes: usize, is_pinned: bool, + is_maximized: bool, ) -> Element<'a, Message> { - let mut button = button(text("Close").size(14)) + let mut row = row![].spacing(5); + + if total_panes > 1 { + let toggle = { + let (content, message) = if is_maximized { + ("Restore", Message::Restore) + } else { + ("Maximize", Message::Maximize(pane)) + }; + button(text(content).size(14)) + .style(theme::Button::Secondary) + .padding(3) + .on_press(message) + }; + + row = row.push(toggle); + } + + let mut close = button(text("Close").size(14)) .style(theme::Button::Destructive) .padding(3); if total_panes > 1 && !is_pinned { - button = button.on_press(Message::Close(pane)); + close = close.on_press(Message::Close(pane)); } - button.into() + row.push(close).into() } mod style { -- cgit From df7bf55ce162c5523f530a43113194a6c064a220 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Wed, 2 Nov 2022 17:12:38 -0700 Subject: Disable drag when maximized --- native/src/widget/pane_grid.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 1b55537f..a58bfc77 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -221,6 +221,12 @@ where self.style = style.into(); self } + + fn drag_enabled(&self) -> bool { + (!self.elements.is_maximized()) + .then(|| self.on_drag.is_some()) + .unwrap_or_default() + } } impl<'a, Message, Renderer> Widget @@ -296,6 +302,11 @@ where ) -> event::Status { let action = tree.state.downcast_mut::(); + let on_drag = self + .drag_enabled() + .then_some(&self.on_drag) + .unwrap_or(&None); + let event_status = update( action, self.elements.node(self.state), @@ -306,7 +317,7 @@ where self.spacing, self.elements.iter(), &self.on_click, - &self.on_drag, + on_drag, &self.on_resize, ); @@ -361,7 +372,7 @@ where cursor_position, viewport, renderer, - self.on_drag.is_some(), + self.drag_enabled(), ) }) .max() @@ -961,4 +972,9 @@ impl Elements { Elements::Maximized(_, _, node) => node, } } + + /// TODO + pub fn is_maximized(&self) -> bool { + matches!(self, Self::Maximized(..)) + } } -- cgit From 923878c7b7404739a3f8f2dd86a2e19802fa86de Mon Sep 17 00:00:00 2001 From: tarkah Date: Wed, 2 Nov 2022 19:00:07 -0700 Subject: Fix tests & lints --- native/src/widget/pane_grid.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index a58bfc77..94ef84a3 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -85,7 +85,7 @@ use crate::{ /// let (mut state, _) = pane_grid::State::new(PaneState::SomePane); /// /// let pane_grid = -/// PaneGrid::new(&state, |pane, state| { +/// PaneGrid::new(&state, |pane, state, is_maximized| { /// pane_grid::Content::new(match state { /// PaneState::SomePane => text("This is some pane"), /// PaneState::AnotherKindOfPane => text("This is another kind of pane"), @@ -302,10 +302,11 @@ where ) -> event::Status { let action = tree.state.downcast_mut::(); - let on_drag = self - .drag_enabled() - .then_some(&self.on_drag) - .unwrap_or(&None); + let on_drag = if self.drag_enabled() { + &self.on_drag + } else { + &None + }; let event_status = update( action, -- cgit From 988515d57f8c67a22ca0554f3e1327b26e5c6ecf Mon Sep 17 00:00:00 2001 From: tarkah Date: Wed, 2 Nov 2022 19:25:27 -0700 Subject: Add state::Scoped & rename Elements as Contents --- native/src/widget/pane_grid.rs | 176 +++++++++++++++++------------------ native/src/widget/pane_grid/state.rs | 18 +++- 2 files changed, 100 insertions(+), 94 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 94ef84a3..8864da0c 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -100,8 +100,8 @@ where Renderer: crate::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { - state: &'a state::Internal, - elements: Elements>, + state: state::Scoped<'a>, + contents: Contents>, width: Length, height: Length, spacing: u16, @@ -124,30 +124,32 @@ where state: &'a State, view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Renderer>, ) -> Self { - let elements = if let Some((pane, pane_state)) = + let (contents, state) = if let Some((pane, pane_state)) = state.maximized.and_then(|pane| { state.panes.get(&pane).map(|pane_state| (pane, pane_state)) }) { - Elements::Maximized( - pane, - view(pane, pane_state, true), - Node::Pane(pane), + ( + Contents::Maximized(pane, view(pane, pane_state, true)), + state::Scoped::Maximized(Node::Pane(pane)), ) } else { - Elements::Normal( - state - .panes - .iter() - .map(|(pane, pane_state)| { - (*pane, view(*pane, pane_state, false)) - }) - .collect(), + ( + Contents::All( + state + .panes + .iter() + .map(|(pane, pane_state)| { + (*pane, view(*pane, pane_state, false)) + }) + .collect(), + ), + state::Scoped::All(&state.internal), ) }; Self { - elements, - state: &state.internal, + contents, + state, width: Length::Fill, height: Length::Fill, spacing: 0, @@ -223,7 +225,7 @@ where } fn drag_enabled(&self) -> bool { - (!self.elements.is_maximized()) + (!self.contents.is_maximized()) .then(|| self.on_drag.is_some()) .unwrap_or_default() } @@ -244,20 +246,20 @@ where } fn children(&self) -> Vec { - self.elements + self.contents .iter() .map(|(_, content)| content.state()) .collect() } fn diff(&self, tree: &mut Tree) { - match &self.elements { - Elements::Normal(elements) => tree.diff_children_custom( - elements, + match &self.contents { + Contents::All(contents) => tree.diff_children_custom( + contents, |state, (_, content)| content.diff(state), |(_, content)| content.state(), ), - Elements::Maximized(_, content, _) => tree.diff_children_custom( + Contents::Maximized(_, content) => tree.diff_children_custom( &[content], |state, content| content.diff(state), |content| content.state(), @@ -281,12 +283,12 @@ where layout( renderer, limits, - self.elements.node(self.state), + &self.state, self.width, self.height, self.spacing, - self.elements.iter(), - |element, renderer, limits| element.layout(renderer, limits), + self.contents.iter(), + |content, renderer, limits| content.layout(renderer, limits), ) } @@ -310,13 +312,13 @@ where let event_status = update( action, - self.elements.node(self.state), + &self.state, &event, layout, cursor_position, shell, self.spacing, - self.elements.iter(), + self.contents.iter(), &self.on_click, on_drag, &self.on_resize, @@ -324,7 +326,7 @@ where let picked_pane = action.picked_pane().map(|(pane, _)| pane); - self.elements + self.contents .iter_mut() .zip(&mut tree.children) .zip(layout.children()) @@ -355,14 +357,14 @@ where ) -> mouse::Interaction { mouse_interaction( tree.state.downcast_ref(), - self.elements.node(self.state), + &self.state, layout, cursor_position, self.spacing, self.on_resize.as_ref().map(|(leeway, _)| *leeway), ) .unwrap_or_else(|| { - self.elements + self.contents .iter() .zip(&tree.children) .zip(layout.children()) @@ -393,7 +395,7 @@ where ) { draw( tree.state.downcast_ref(), - self.elements.node(self.state), + &self.state, layout, cursor_position, renderer, @@ -403,7 +405,7 @@ where self.spacing, self.on_resize.as_ref().map(|(leeway, _)| *leeway), self.style, - self.elements + self.contents .iter() .zip(&tree.children) .map(|((pane, content), tree)| (pane, (content, tree))), @@ -432,7 +434,7 @@ where layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - self.elements + self.contents .iter() .zip(&mut tree.children) .zip(layout.children()) @@ -458,27 +460,27 @@ where } /// Calculates the [`Layout`] of a [`PaneGrid`]. -pub fn layout( +pub fn layout<'a, Renderer, T>( renderer: &Renderer, limits: &layout::Limits, - node: &Node, + state: &state::Scoped<'a>, width: Length, height: Length, spacing: u16, - elements: impl Iterator, - layout_element: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node, + contents: impl Iterator, + layout_content: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node, ) -> layout::Node { let limits = limits.width(width).height(height); let size = limits.resolve(Size::ZERO); - let regions = node.pane_regions(f32::from(spacing), size); - let children = elements - .filter_map(|(pane, element)| { + let regions = state.layout().pane_regions(f32::from(spacing), size); + let children = contents + .filter_map(|(pane, content)| { let region = regions.get(&pane)?; let size = Size::new(region.width, region.height); - let mut node = layout_element( - element, + let mut node = layout_content( + content, renderer, &layout::Limits::new(size, size), ); @@ -496,13 +498,13 @@ pub fn layout( /// accordingly. pub fn update<'a, Message, T: Draggable>( action: &mut state::Action, - node: &Node, + state: &state::Scoped<'a>, event: &Event, layout: Layout<'_>, cursor_position: Point, shell: &mut Shell<'_, Message>, spacing: u16, - elements: impl Iterator, + contents: impl Iterator, on_click: &Option Message + 'a>>, on_drag: &Option Message + 'a>>, on_resize: &Option<(u16, Box Message + 'a>)>, @@ -524,7 +526,7 @@ pub fn update<'a, Message, T: Draggable>( cursor_position.y - bounds.y, ); - let splits = node.split_regions( + let splits = state.layout().split_regions( f32::from(spacing), Size::new(bounds.width, bounds.height), ); @@ -546,7 +548,7 @@ pub fn update<'a, Message, T: Draggable>( layout, cursor_position, shell, - elements, + contents, on_click, on_drag, ); @@ -558,7 +560,7 @@ pub fn update<'a, Message, T: Draggable>( layout, cursor_position, shell, - elements, + contents, on_click, on_drag, ); @@ -571,7 +573,7 @@ pub fn update<'a, Message, T: Draggable>( | Event::Touch(touch::Event::FingerLost { .. }) => { if let Some((pane, _)) = action.picked_pane() { if let Some(on_drag) = on_drag { - let mut dropped_region = elements + let mut dropped_region = contents .zip(layout.children()) .filter(|(_, layout)| { layout.bounds().contains(cursor_position) @@ -602,7 +604,7 @@ pub fn update<'a, Message, T: Draggable>( if let Some((split, _)) = action.picked_split() { let bounds = layout.bounds(); - let splits = node.split_regions( + let splits = state.layout().split_regions( f32::from(spacing), Size::new(bounds.width, bounds.height), ); @@ -641,13 +643,13 @@ fn click_pane<'a, Message, T>( layout: Layout<'_>, cursor_position: Point, shell: &mut Shell<'_, Message>, - elements: impl Iterator, + contents: impl Iterator, on_click: &Option Message + 'a>>, on_drag: &Option Message + 'a>>, ) where T: Draggable, { - let mut clicked_region = elements + let mut clicked_region = contents .zip(layout.children()) .filter(|(_, layout)| layout.bounds().contains(cursor_position)); @@ -672,9 +674,9 @@ fn click_pane<'a, Message, T>( } /// Returns the current [`mouse::Interaction`] of a [`PaneGrid`]. -pub fn mouse_interaction( +pub fn mouse_interaction<'a>( action: &state::Action, - node: &Node, + state: &state::Scoped<'a>, layout: Layout<'_>, cursor_position: Point, spacing: u16, @@ -689,8 +691,9 @@ pub fn mouse_interaction( resize_leeway.and_then(|leeway| { let bounds = layout.bounds(); - let splits = - node.split_regions(f32::from(spacing), bounds.size()); + let splits = state + .layout() + .split_regions(f32::from(spacing), bounds.size()); let relative_cursor = Point::new( cursor_position.x - bounds.x, @@ -717,9 +720,9 @@ pub fn mouse_interaction( } /// Draws a [`PaneGrid`]. -pub fn draw( +pub fn draw<'a, Renderer, T>( action: &state::Action, - node: &Node, + state: &state::Scoped<'a>, layout: Layout<'_>, cursor_position: Point, renderer: &mut Renderer, @@ -729,7 +732,7 @@ pub fn draw( spacing: u16, resize_leeway: Option, style: ::Style, - elements: impl Iterator, + contents: impl Iterator, draw_pane: impl Fn( T, &mut Renderer, @@ -749,7 +752,9 @@ pub fn draw( .and_then(|(split, axis)| { let bounds = layout.bounds(); - let splits = node.split_regions(f32::from(spacing), bounds.size()); + let splits = state + .layout() + .split_regions(f32::from(spacing), bounds.size()); let (_axis, region, ratio) = splits.get(&split)?; @@ -767,8 +772,9 @@ pub fn draw( cursor_position.y - bounds.y, ); - let splits = - node.split_regions(f32::from(spacing), bounds.size()); + let splits = state + .layout() + .split_regions(f32::from(spacing), bounds.size()); let (_split, axis, region) = hovered_split( splits.iter(), @@ -791,7 +797,7 @@ pub fn draw( let mut render_picked_pane = None; - for ((id, pane), layout) in elements.zip(layout.children()) { + for ((id, pane), layout) in contents.zip(layout.children()) { match picked_pane { Some((dragging, origin)) if id == dragging => { render_picked_pane = Some((pane, origin, layout)); @@ -930,52 +936,40 @@ fn hovered_split<'a>( .next() } -/// TODO +/// The visible contents of the [`PaneGrid`] #[derive(Debug)] -pub enum Elements { - /// TODO - Normal(Vec<(Pane, T)>), - /// TODO - Maximized(Pane, T, Node), +pub enum Contents { + /// All panes are visible + All(Vec<(Pane, T)>), + /// A maximized pane is visible + Maximized(Pane, T), } -impl Elements { - /// TODO +impl Contents { + /// Returns an iterator over the values of the [`Contents`] pub fn iter(&self) -> Box + '_> { match self { - Elements::Normal(elements) => Box::new( - elements.iter().map(|(pane, content)| (*pane, content)), + Contents::All(contents) => Box::new( + contents.iter().map(|(pane, content)| (*pane, content)), ), - Elements::Maximized(pane, content, _) => { + Contents::Maximized(pane, content) => { Box::new(std::iter::once((*pane, content))) } } } - /// TODO - pub fn iter_mut( - &mut self, - ) -> Box + '_> { + fn iter_mut(&mut self) -> Box + '_> { match self { - Elements::Normal(elements) => Box::new( - elements.iter_mut().map(|(pane, content)| (*pane, content)), + Contents::All(contents) => Box::new( + contents.iter_mut().map(|(pane, content)| (*pane, content)), ), - Elements::Maximized(pane, content, _) => { + Contents::Maximized(pane, content) => { Box::new(std::iter::once((*pane, content))) } } } - /// TODO - pub fn node<'a>(&'a self, state: &'a state::Internal) -> &'a Node { - match self { - Elements::Normal(_) => state.layout(), - Elements::Maximized(_, _, node) => node, - } - } - - /// TODO - pub fn is_maximized(&self) -> bool { + fn is_maximized(&self) -> bool { matches!(self, Self::Maximized(..)) } } diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 92d26f5a..b5bebc2e 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -281,6 +281,15 @@ impl Internal { } } +/// The scoped internal state of the [`PaneGrid`] +#[derive(Debug)] +pub enum Scoped<'a> { + /// The state when all panes are visible + All(&'a Internal), + /// The state when a pane is maximized + Maximized(Node), +} + /// The current action of a [`PaneGrid`]. /// /// [`PaneGrid`]: crate::widget::PaneGrid @@ -328,9 +337,12 @@ impl Action { } } -impl Internal { - /// The layout [`Node`] of the [`Internal`] state +impl<'a> Scoped<'a> { + /// The layout [`Node`] of the [`Scope`] state pub fn layout(&self) -> &Node { - &self.layout + match self { + Scoped::All(Internal { layout, .. }) => layout, + Scoped::Maximized(layout) => layout, + } } } -- cgit From 2f6c71d99a2c739c8b86bdf9d024e83ae994042d Mon Sep 17 00:00:00 2001 From: tarkah Date: Wed, 2 Nov 2022 19:54:49 -0700 Subject: Fix doc links --- native/src/widget/pane_grid/state.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index b5bebc2e..70a2aa88 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -214,12 +214,16 @@ impl State { /// Maximize the given [`Pane`]. Only this pane will be rendered by the /// [`PaneGrid`] until [`Self::restore()`] is called. + /// + /// [`PaneGrid`]: crate::widget::PaneGrid pub fn maximize(&mut self, pane: &Pane) { self.maximized = Some(*pane); } /// Restore the currently maximized [`Pane`] to it's normal size. All panes - /// will be rendered by the [`PaneGrid`] + /// will be rendered by the [`PaneGrid`]. + /// + /// [`PaneGrid`]: crate::widget::PaneGrid pub fn restore(&mut self) { let _ = self.maximized.take(); } @@ -282,6 +286,8 @@ impl Internal { } /// The scoped internal state of the [`PaneGrid`] +/// +/// [`PaneGrid`]: crate::widget::PaneGrid #[derive(Debug)] pub enum Scoped<'a> { /// The state when all panes are visible @@ -338,7 +344,7 @@ impl Action { } impl<'a> Scoped<'a> { - /// The layout [`Node`] of the [`Scope`] state + /// The layout [`Node`] of the [`Scoped`] state pub fn layout(&self) -> &Node { match self { Scoped::All(Internal { layout, .. }) => layout, -- cgit From 951fbc83ff8878be03eb6c8c43f2a28d0f0f0d4c Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 3 Nov 2022 08:02:20 -0700 Subject: Remove maximized when split occurs --- native/src/widget/pane_grid/state.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 70a2aa88..882a45f2 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -160,6 +160,7 @@ impl State { node.split(new_split, axis, new_pane); let _ = self.panes.insert(new_pane, state); + let _ = self.maximized.take(); Some((new_pane, new_split)) } -- cgit From 853ff4bcf4eff24808c680f9a35bfc0013cb779d Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 3 Nov 2022 11:32:36 -0700 Subject: Add pub method for getting maximized value --- native/src/widget/pane_grid/state.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 882a45f2..c9e9433d 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -228,6 +228,13 @@ impl State { pub fn restore(&mut self) { let _ = self.maximized.take(); } + + /// Returns the maximized [`Pane`] of the [`PaneGrid`]. + /// + /// [`PaneGrid`]: crate::widget::PaneGrid + pub fn maximized(&self) -> Option { + self.maximized + } } /// The internal state of a [`PaneGrid`]. -- cgit From 4d42ca2452cd270dfcfbdb03836c5857a3c28f73 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 3 Nov 2022 14:53:22 -0700 Subject: Combine `glow_default_system_font` and `default_system_font` features Apparently "weak dependency features" have been stable since Rust 1.60.0, allowing a feature to enable a feature of a dependency only if that dependency is enabled elsewhere. So having a separate feature for this with glow is unnecessary now. Also remove the `resolver` setting which is redundant on edition 2021. --- Cargo.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f7d578ba..0c936ac5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ documentation = "https://docs.rs/iced" readme = "README.md" keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] -resolver = "2" [features] default = ["wgpu"] @@ -25,11 +24,9 @@ qr_code = ["iced_graphics/qr_code"] # Enables the `iced_wgpu` renderer wgpu = ["iced_wgpu"] # Enables using system fonts -default_system_font = ["iced_wgpu/default_system_font"] +default_system_font = ["iced_wgpu?/default_system_font", "iced_glow?/default_system_font"] # Enables the `iced_glow` renderer. Overrides `iced_wgpu` glow = ["iced_glow", "iced_glutin"] -# Enables using system fonts for `iced_glow` -glow_default_system_font = ["iced_glow/default_system_font"] # Enables a debug view in native platforms (press F12) debug = ["iced_winit/debug"] # Enables `tokio` as the `executor::Default` on native platforms -- cgit From ab2872fe2be5b694f65125b86e5c03b1f3506ac3 Mon Sep 17 00:00:00 2001 From: traxys Date: Mon, 19 Sep 2022 16:19:15 +0200 Subject: Allow to replace an element instead of append to body --- winit/src/application.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/winit/src/application.rs b/winit/src/application.rs index 939a50c9..db0ab938 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -159,9 +159,14 @@ where let document = window.document().unwrap(); let body = document.body().unwrap(); - let _ = body - .append_child(&canvas) - .expect("Append canvas to HTML body"); + let _ = match body.query_selector("#iced_root").unwrap() { + Some(e) => body + .replace_child(&canvas, &e) + .expect("Could not replace iced_root"), + None => body + .append_child(&canvas) + .expect("Append canvas to HTML body"), + }; } let (compositor, renderer) = C::new(compositor_settings, Some(&window))?; -- cgit From d8d57a800a2e1bd11dc4d69634d630c8e6117c39 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 5 Nov 2022 01:43:28 +0100 Subject: Allow providing a DOM identifier as a `target` for Wasm --- winit/src/application.rs | 17 +++++++++++++---- winit/src/settings.rs | 12 ++++++++++-- winit/src/settings/macos.rs | 1 - winit/src/settings/wasm.rs | 11 +++++++++++ winit/src/settings/windows.rs | 1 - 5 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 winit/src/settings/wasm.rs diff --git a/winit/src/application.rs b/winit/src/application.rs index db0ab938..ffaaa8fb 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -137,6 +137,9 @@ where runtime.enter(|| A::new(flags)) }; + #[cfg(target_arch = "wasm32")] + let target = settings.window.platform_specific.target.clone(); + let builder = settings.window.into_builder( &application.title(), event_loop.primary_monitor(), @@ -159,10 +162,16 @@ where let document = window.document().unwrap(); let body = document.body().unwrap(); - let _ = match body.query_selector("#iced_root").unwrap() { - Some(e) => body - .replace_child(&canvas, &e) - .expect("Could not replace iced_root"), + let target = target.and_then(|target| { + body.query_selector(&format!("#{}", target)) + .ok() + .unwrap_or(None) + }); + + let _ = match target { + Some(node) => node + .replace_child(&canvas, &node) + .expect(&format!("Could not replace #{}", node.id())), None => body .append_child(&canvas) .expect("Append canvas to HTML body"), diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 6387454b..9bbdef5c 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -7,7 +7,15 @@ mod platform; #[path = "settings/macos.rs"] mod platform; -#[cfg(not(any(target_os = "windows", target_os = "macos")))] +#[cfg(target_arch = "wasm32")] +#[path = "settings/wasm.rs"] +mod platform; + +#[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_arch = "wasm32" +)))] #[path = "settings/other.rs"] mod platform; @@ -27,7 +35,7 @@ pub struct Settings { /// communicate with it through the windowing system. pub id: Option, - /// The [`Window`] settings + /// The [`Window`] settings. pub window: Window, /// The data needed to initialize an [`Application`]. diff --git a/winit/src/settings/macos.rs b/winit/src/settings/macos.rs index ad4c8cae..f86e63ad 100644 --- a/winit/src/settings/macos.rs +++ b/winit/src/settings/macos.rs @@ -1,4 +1,3 @@ -#![cfg(target_os = "macos")] //! Platform specific settings for macOS. /// The platform specific window settings of an application. diff --git a/winit/src/settings/wasm.rs b/winit/src/settings/wasm.rs new file mode 100644 index 00000000..8e0f1bbc --- /dev/null +++ b/winit/src/settings/wasm.rs @@ -0,0 +1,11 @@ +//! Platform specific settings for WebAssembly. + +/// The platform specific window settings of an application. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct PlatformSpecific { + /// The identifier of a DOM element that will be replaced with the + /// application. + /// + /// If set to `None`, the application will be appended to the HTML body. + pub target: Option, +} diff --git a/winit/src/settings/windows.rs b/winit/src/settings/windows.rs index 9bef1eaf..ff03a9c5 100644 --- a/winit/src/settings/windows.rs +++ b/winit/src/settings/windows.rs @@ -1,4 +1,3 @@ -#![cfg(target_os = "windows")] //! Platform specific settings for Windows. /// The platform specific window settings of an application. -- cgit From bc5986c7c69efd206b900e8d923d3df3a225f6cc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 5 Nov 2022 01:53:24 +0100 Subject: Unify methods by leveraging `Into` in `image` and `svg` --- native/src/image.rs | 28 +++++----------------------- native/src/svg.rs | 11 ++--------- 2 files changed, 7 insertions(+), 32 deletions(-) diff --git a/native/src/image.rs b/native/src/image.rs index 41e41aa4..b849ef84 100644 --- a/native/src/image.rs +++ b/native/src/image.rs @@ -26,26 +26,15 @@ impl Handle { /// pixels. /// /// This is useful if you have already decoded your image. - pub fn from_pixels(width: u32, height: u32, pixels: Vec) -> Handle { - Self::from_data(Data::Pixels { - width, - height, - pixels: Cow::Owned(pixels), - }) - } - - /// Like [`Handle::from_pixels`], but from static pixel data. - /// - /// Useful for images included in binary, for instance with [`include_bytes!`]. - pub fn from_static_pixels( + pub fn from_pixels( width: u32, height: u32, - pixels: &'static [u8], + pixels: impl Into>, ) -> Handle { Self::from_data(Data::Pixels { width, height, - pixels: Cow::Borrowed(pixels), + pixels: pixels.into(), }) } @@ -55,15 +44,8 @@ impl Handle { /// /// This is useful if you already have your image loaded in-memory, maybe /// because you downloaded or generated it procedurally. - pub fn from_memory(bytes: Vec) -> Handle { - Self::from_data(Data::Bytes(Cow::Owned(bytes))) - } - - /// Like [`Handle::from_memory`], but from static image data. - /// - /// Useful for images included in binary, for instance with [`include_bytes!`]. - pub fn from_static_memory(bytes: &'static [u8]) -> Handle { - Self::from_data(Data::Bytes(Cow::Borrowed(bytes))) + pub fn from_memory(bytes: impl Into>) -> Handle { + Self::from_data(Data::Bytes(bytes.into())) } fn from_data(data: Data) -> Handle { diff --git a/native/src/svg.rs b/native/src/svg.rs index c89eed3f..d4d20182 100644 --- a/native/src/svg.rs +++ b/native/src/svg.rs @@ -25,15 +25,8 @@ impl Handle { /// /// This is useful if you already have your SVG data in-memory, maybe /// because you downloaded or generated it procedurally. - pub fn from_memory(bytes: impl Into>) -> Handle { - Self::from_data(Data::Bytes(Cow::Owned(bytes.into()))) - } - - /// Like [`Handle::from_memory`], but from static image data. - /// - /// Useful for images included in binary, for instance with [`include_bytes!`]. - pub fn from_static_memory(bytes: &'static [u8]) -> Handle { - Self::from_data(Data::Bytes(Cow::Borrowed(bytes))) + pub fn from_memory(bytes: impl Into>) -> Handle { + Self::from_data(Data::Bytes(bytes.into())) } fn from_data(data: Data) -> Handle { -- cgit From 2c7c42ee93a61f39562590f6a75eb2dd8b220fb8 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 31 Oct 2022 13:37:56 -0700 Subject: Move image/svg handling into `iced_graphics` The `TextureStore` trait is implemented by the atlas, and can also be implemented in the glow renderer or in a software renderer. The API here may be improved in the future, but API stability is presumably not a huge issue since these types will only be used by renderer backends. --- graphics/Cargo.toml | 38 +++++++ graphics/src/image.rs | 34 ++++++ graphics/src/image/raster.rs | 236 ++++++++++++++++++++++++++++++++++++++++++ graphics/src/image/vector.rs | 183 ++++++++++++++++++++++++++++++++ graphics/src/lib.rs | 1 + wgpu/Cargo.toml | 46 +++----- wgpu/src/backend.rs | 2 +- wgpu/src/image.rs | 32 +++--- wgpu/src/image/atlas.rs | 191 +++++++++++++++++----------------- wgpu/src/image/atlas/entry.rs | 6 +- wgpu/src/image/raster.rs | 222 --------------------------------------- wgpu/src/image/vector.rs | 173 ------------------------------- 12 files changed, 627 insertions(+), 537 deletions(-) create mode 100644 graphics/src/image.rs create mode 100644 graphics/src/image/raster.rs create mode 100644 graphics/src/image/vector.rs delete mode 100644 wgpu/src/image/raster.rs delete mode 100644 wgpu/src/image/vector.rs diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 3b0e5236..749770f4 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -11,17 +11,33 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] +svg = ["resvg", "usvg", "tiny-skia"] +image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"] +png = ["image_rs/png"] +jpeg = ["image_rs/jpeg"] +jpeg_rayon = ["image_rs/jpeg_rayon"] +gif = ["image_rs/gif"] +webp = ["image_rs/webp"] +pnm = ["image_rs/pnm"] +ico = ["image_rs/ico"] +bmp = ["image_rs/bmp"] +hdr = ["image_rs/hdr"] +dds = ["image_rs/dds"] +farbfeld = ["image_rs/farbfeld"] canvas = ["lyon"] qr_code = ["qrcode", "canvas"] font-source = ["font-kit"] font-fallback = [] font-icons = [] opengl = [] +image_rs = ["kamadak-exif"] [dependencies] glam = "0.21.3" +log = "0.4" raw-window-handle = "0.5" thiserror = "1.0" +bitflags = "1.2" [dependencies.bytemuck] version = "1.4" @@ -48,6 +64,28 @@ default-features = false version = "0.10" optional = true +[dependencies.image_rs] +version = "0.23" +package = "image" +default-features = false +optional = true + +[dependencies.resvg] +version = "0.18" +optional = true + +[dependencies.usvg] +version = "0.18" +optional = true + +[dependencies.tiny-skia] +version = "0.6" +optional = true + +[dependencies.kamadak-exif] +version = "0.5" +optional = true + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] all-features = true diff --git a/graphics/src/image.rs b/graphics/src/image.rs new file mode 100644 index 00000000..f1153882 --- /dev/null +++ b/graphics/src/image.rs @@ -0,0 +1,34 @@ +//! Image loading and caching + +use std::fmt::Debug; + +#[cfg(feature = "image_rs")] +pub mod raster; + +#[cfg(feature = "svg")] +pub mod vector; + +/// Entry in the texture store +pub trait TextureStoreEntry: Debug { + /// Width and height of the entry + fn size(&self) -> (u32, u32); +} + +/// Stores cached image data for use in rendering +pub trait TextureStore { + /// Entry in the texture store + type Entry: TextureStoreEntry; + /// State passed to upload/remove + type State<'a>; + + /// Upload image data + fn upload( + &mut self, + width: u32, + height: u32, + data: &[u8], + state: &mut Self::State<'_>, + ) -> Option; + /// Remome image from store + fn remove(&mut self, entry: &Self::Entry, state: &mut Self::State<'_>); +} diff --git a/graphics/src/image/raster.rs b/graphics/src/image/raster.rs new file mode 100644 index 00000000..f9672dd5 --- /dev/null +++ b/graphics/src/image/raster.rs @@ -0,0 +1,236 @@ +//! Raster image loading and caching + +use iced_native::image; +use std::collections::{HashMap, HashSet}; + +use bitflags::bitflags; + +use super::{TextureStore, TextureStoreEntry}; + +/// Entry in cache corresponding to an image handle +#[derive(Debug)] +pub enum Memory { + /// Image data on host + Host(::image_rs::ImageBuffer<::image_rs::Rgba, Vec>), + /// Texture store entry + Device(T::Entry), + /// Image not found + NotFound, + /// Invalid image data + Invalid, +} + +impl Memory { + /// Width and height of image + pub fn dimensions(&self) -> (u32, u32) { + match self { + Memory::Host(image) => image.dimensions(), + Memory::Device(entry) => entry.size(), + Memory::NotFound => (1, 1), + Memory::Invalid => (1, 1), + } + } +} + +/// Caches image raster data +#[derive(Debug)] +pub struct Cache { + map: HashMap>, + hits: HashSet, +} + +impl Cache { + /// Load image + pub fn load(&mut self, handle: &image::Handle) -> &mut Memory { + if self.contains(handle) { + return self.get(handle).unwrap(); + } + + let memory = match handle.data() { + image::Data::Path(path) => { + if let Ok(image) = image_rs::open(path) { + let operation = std::fs::File::open(path) + .ok() + .map(std::io::BufReader::new) + .and_then(|mut reader| { + Operation::from_exif(&mut reader).ok() + }) + .unwrap_or_else(Operation::empty); + + Memory::Host(operation.perform(image.to_rgba8())) + } else { + Memory::NotFound + } + } + image::Data::Bytes(bytes) => { + if let Ok(image) = image_rs::load_from_memory(bytes) { + let operation = + Operation::from_exif(&mut std::io::Cursor::new(bytes)) + .ok() + .unwrap_or_else(Operation::empty); + + Memory::Host(operation.perform(image.to_rgba8())) + } else { + Memory::Invalid + } + } + image::Data::Pixels { + width, + height, + pixels, + } => { + if let Some(image) = image_rs::ImageBuffer::from_vec( + *width, + *height, + pixels.to_vec(), + ) { + Memory::Host(image) + } else { + Memory::Invalid + } + } + }; + + self.insert(handle, memory); + self.get(handle).unwrap() + } + + /// Load image and upload raster data + pub fn upload( + &mut self, + handle: &image::Handle, + state: &mut T::State<'_>, + store: &mut T, + ) -> Option<&T::Entry> { + let memory = self.load(handle); + + if let Memory::Host(image) = memory { + let (width, height) = image.dimensions(); + + let entry = store.upload(width, height, image, state)?; + + *memory = Memory::Device(entry); + } + + if let Memory::Device(allocation) = memory { + Some(allocation) + } else { + None + } + } + + /// Trim cache misses from cache + pub fn trim(&mut self, store: &mut T, state: &mut T::State<'_>) { + let hits = &self.hits; + + self.map.retain(|k, memory| { + let retain = hits.contains(k); + + if !retain { + if let Memory::Device(entry) = memory { + store.remove(entry, state); + } + } + + retain + }); + + self.hits.clear(); + } + + fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> { + let _ = self.hits.insert(handle.id()); + + self.map.get_mut(&handle.id()) + } + + fn insert(&mut self, handle: &image::Handle, memory: Memory) { + let _ = self.map.insert(handle.id(), memory); + } + + fn contains(&self, handle: &image::Handle) -> bool { + self.map.contains_key(&handle.id()) + } +} + +impl Default for Cache { + fn default() -> Self { + Self { + map: HashMap::new(), + hits: HashSet::new(), + } + } +} + +bitflags! { + struct Operation: u8 { + const FLIP_HORIZONTALLY = 0b001; + const ROTATE_180 = 0b010; + const FLIP_DIAGONALLY = 0b100; + } +} + +impl Operation { + // Meaning of the returned value is described e.g. at: + // https://magnushoff.com/articles/jpeg-orientation/ + fn from_exif(reader: &mut R) -> Result + where + R: std::io::BufRead + std::io::Seek, + { + let exif = exif::Reader::new().read_from_container(reader)?; + + Ok(exif + .get_field(exif::Tag::Orientation, exif::In::PRIMARY) + .and_then(|field| field.value.get_uint(0)) + .and_then(|value| u8::try_from(value).ok()) + .and_then(|value| Self::from_bits(value.saturating_sub(1))) + .unwrap_or_else(Self::empty)) + } + + fn perform

( + self, + image: image_rs::ImageBuffer>, + ) -> image_rs::ImageBuffer> + where + P: image_rs::Pixel + 'static, + { + use image_rs::imageops; + + let mut image = if self.contains(Self::FLIP_DIAGONALLY) { + flip_diagonally(image) + } else { + image + }; + + if self.contains(Self::ROTATE_180) { + imageops::rotate180_in_place(&mut image); + } + + if self.contains(Self::FLIP_HORIZONTALLY) { + imageops::flip_horizontal_in_place(&mut image); + } + + image + } +} + +fn flip_diagonally( + image: I, +) -> image_rs::ImageBuffer::Subpixel>> +where + I: image_rs::GenericImage, + I::Pixel: 'static, +{ + let (width, height) = image.dimensions(); + let mut out = image_rs::ImageBuffer::new(height, width); + + for x in 0..width { + for y in 0..height { + let p = image.get_pixel(x, y); + + out.put_pixel(y, x, p); + } + } + + out +} diff --git a/graphics/src/image/vector.rs b/graphics/src/image/vector.rs new file mode 100644 index 00000000..0062e2ce --- /dev/null +++ b/graphics/src/image/vector.rs @@ -0,0 +1,183 @@ +//! Vector image loading and caching + +use iced_native::svg; + +use std::collections::{HashMap, HashSet}; +use std::fs; + +use super::TextureStore; + +/// Entry in cache corresponding to an svg handle +pub enum Svg { + /// Parsed svg + Loaded(usvg::Tree), + /// Svg not found or failed to parse + NotFound, +} + +impl Svg { + /// Viewport width and height + pub fn viewport_dimensions(&self) -> (u32, u32) { + match self { + Svg::Loaded(tree) => { + let size = tree.svg_node().size; + + (size.width() as u32, size.height() as u32) + } + Svg::NotFound => (1, 1), + } + } +} + +/// Caches svg vector and raster data +#[derive(Debug)] +pub struct Cache { + svgs: HashMap, + rasterized: HashMap<(u64, u32, u32), T::Entry>, + svg_hits: HashSet, + rasterized_hits: HashSet<(u64, u32, u32)>, +} + +impl Cache { + /// Load svg + pub fn load(&mut self, handle: &svg::Handle) -> &Svg { + if self.svgs.contains_key(&handle.id()) { + return self.svgs.get(&handle.id()).unwrap(); + } + + let svg = match handle.data() { + svg::Data::Path(path) => { + let tree = fs::read_to_string(path).ok().and_then(|contents| { + usvg::Tree::from_str( + &contents, + &usvg::Options::default().to_ref(), + ) + .ok() + }); + + tree.map(Svg::Loaded).unwrap_or(Svg::NotFound) + } + svg::Data::Bytes(bytes) => { + match usvg::Tree::from_data( + bytes, + &usvg::Options::default().to_ref(), + ) { + Ok(tree) => Svg::Loaded(tree), + Err(_) => Svg::NotFound, + } + } + }; + + let _ = self.svgs.insert(handle.id(), svg); + self.svgs.get(&handle.id()).unwrap() + } + + /// Load svg and upload raster data + pub fn upload( + &mut self, + handle: &svg::Handle, + [width, height]: [f32; 2], + scale: f32, + state: &mut T::State<'_>, + texture_store: &mut T, + ) -> Option<&T::Entry> { + let id = handle.id(); + + let (width, height) = ( + (scale * width).ceil() as u32, + (scale * height).ceil() as u32, + ); + + // TODO: Optimize! + // We currently rerasterize the SVG when its size changes. This is slow + // as heck. A GPU rasterizer like `pathfinder` may perform better. + // It would be cool to be able to smooth resize the `svg` example. + if self.rasterized.contains_key(&(id, width, height)) { + let _ = self.svg_hits.insert(id); + let _ = self.rasterized_hits.insert((id, width, height)); + + return self.rasterized.get(&(id, width, height)); + } + + match self.load(handle) { + Svg::Loaded(tree) => { + if width == 0 || height == 0 { + return None; + } + + // TODO: Optimize! + // We currently rerasterize the SVG when its size changes. This is slow + // as heck. A GPU rasterizer like `pathfinder` may perform better. + // It would be cool to be able to smooth resize the `svg` example. + let mut img = tiny_skia::Pixmap::new(width, height)?; + + resvg::render( + tree, + if width > height { + usvg::FitTo::Width(width) + } else { + usvg::FitTo::Height(height) + }, + img.as_mut(), + )?; + + let mut rgba = img.take(); + rgba.chunks_exact_mut(4).for_each(|rgba| rgba.swap(0, 2)); + + let allocation = texture_store.upload( + width, + height, + bytemuck::cast_slice(rgba.as_slice()), + state, + )?; + log::debug!("allocating {} {}x{}", id, width, height); + + let _ = self.svg_hits.insert(id); + let _ = self.rasterized_hits.insert((id, width, height)); + let _ = self.rasterized.insert((id, width, height), allocation); + + self.rasterized.get(&(id, width, height)) + } + Svg::NotFound => None, + } + } + + /// Load svg and upload raster data + pub fn trim(&mut self, texture_store: &mut T, state: &mut T::State<'_>) { + let svg_hits = &self.svg_hits; + let rasterized_hits = &self.rasterized_hits; + + self.svgs.retain(|k, _| svg_hits.contains(k)); + self.rasterized.retain(|k, entry| { + let retain = rasterized_hits.contains(k); + + if !retain { + texture_store.remove(entry, state); + } + + retain + }); + self.svg_hits.clear(); + self.rasterized_hits.clear(); + } +} + +impl Default for Cache { + fn default() -> Self { + Self { + svgs: HashMap::new(), + rasterized: HashMap::new(), + svg_hits: HashSet::new(), + rasterized_hits: HashSet::new(), + } + } +} + +impl std::fmt::Debug for Svg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Svg::Loaded(_) => write!(f, "Svg::Loaded"), + Svg::NotFound => write!(f, "Svg::NotFound"), + } + } +} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index ec28ee58..d39dd90c 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -30,6 +30,7 @@ mod viewport; pub mod backend; pub mod font; pub mod gradient; +pub mod image; pub mod layer; pub mod overlay; pub mod renderer; diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 9a57e58b..d7da84fa 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -8,19 +8,20 @@ license = "MIT AND OFL-1.1" repository = "https://github.com/iced-rs/iced" [features] -svg = ["resvg", "usvg", "tiny-skia"] -image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"] -png = ["image_rs/png"] -jpeg = ["image_rs/jpeg"] -jpeg_rayon = ["image_rs/jpeg_rayon"] -gif = ["image_rs/gif"] -webp = ["image_rs/webp"] -pnm = ["image_rs/pnm"] -ico = ["image_rs/ico"] -bmp = ["image_rs/bmp"] -hdr = ["image_rs/hdr"] -dds = ["image_rs/dds"] -farbfeld = ["image_rs/farbfeld"] +svg = ["iced_graphics/svg"] +image = ["image_rs", "iced_graphics/image", "png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"] +image_rs = ["iced_graphics/image_rs"] +png = ["iced_graphics/png"] +jpeg = ["iced_graphics/jpeg"] +jpeg_rayon = ["iced_graphics/jpeg_rayon"] +gif = ["iced_graphics/gif"] +webp = ["iced_graphics/webp"] +pnm = ["iced_graphics/pnm"] +ico = ["iced_graphics/ico"] +bmp = ["iced_graphics/bmp"] +hdr = ["iced_graphics/hdr"] +dds = ["iced_graphics/dds"] +farbfeld = ["iced_graphics/farbfeld"] canvas = ["iced_graphics/canvas"] qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] @@ -35,7 +36,6 @@ raw-window-handle = "0.5" log = "0.4" guillotiere = "0.6" futures = "0.3" -kamadak-exif = "0.5" bitflags = "1.2" [dependencies.bytemuck] @@ -51,24 +51,6 @@ version = "0.3" path = "../graphics" features = ["font-fallback", "font-icons"] -[dependencies.image_rs] -version = "0.23" -package = "image" -default-features = false -optional = true - -[dependencies.resvg] -version = "0.18" -optional = true - -[dependencies.usvg] -version = "0.18" -optional = true - -[dependencies.tiny-skia] -version = "0.6" -optional = true - [dependencies.encase] version = "0.3.0" features = ["glam"] diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 80026673..efe35a3e 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -99,7 +99,7 @@ impl Backend { } #[cfg(any(feature = "image_rs", feature = "svg"))] - self.image_pipeline.trim_cache(); + self.image_pipeline.trim_cache(device, encoder); } fn flush( diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index d964aed7..b9f165f0 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,10 +1,10 @@ mod atlas; #[cfg(feature = "image_rs")] -mod raster; +use iced_graphics::image::raster; #[cfg(feature = "svg")] -mod vector; +use iced_graphics::image::vector; use crate::Transformation; use atlas::Atlas; @@ -25,9 +25,9 @@ use iced_native::svg; #[derive(Debug)] pub struct Pipeline { #[cfg(feature = "image_rs")] - raster_cache: RefCell, + raster_cache: RefCell>, #[cfg(feature = "svg")] - vector_cache: RefCell, + vector_cache: RefCell>, pipeline: wgpu::RenderPipeline, uniforms: wgpu::Buffer, @@ -243,10 +243,10 @@ impl Pipeline { Pipeline { #[cfg(feature = "image_rs")] - raster_cache: RefCell::new(raster::Cache::new()), + raster_cache: RefCell::new(raster::Cache::default()), #[cfg(feature = "svg")] - vector_cache: RefCell::new(vector::Cache::new()), + vector_cache: RefCell::new(vector::Cache::default()), pipeline, uniforms: uniforms_buffer, @@ -302,8 +302,7 @@ impl Pipeline { layer::Image::Raster { handle, bounds } => { if let Some(atlas_entry) = raster_cache.upload( handle, - device, - encoder, + &mut (device, encoder), &mut self.texture_atlas, ) { add_instances( @@ -325,8 +324,7 @@ impl Pipeline { handle, size, _scale, - device, - encoder, + &mut (device, encoder), &mut self.texture_atlas, ) { add_instances( @@ -446,12 +444,20 @@ impl Pipeline { } } - pub fn trim_cache(&mut self) { + pub fn trim_cache( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + ) { #[cfg(feature = "image_rs")] - self.raster_cache.borrow_mut().trim(&mut self.texture_atlas); + self.raster_cache + .borrow_mut() + .trim(&mut self.texture_atlas, &mut (device, encoder)); #[cfg(feature = "svg")] - self.vector_cache.borrow_mut().trim(&mut self.texture_atlas); + self.vector_cache + .borrow_mut() + .trim(&mut self.texture_atlas, &mut (device, encoder)); } } diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 953dd4e2..d3e0c753 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -4,6 +4,7 @@ mod allocation; mod allocator; mod layer; +use iced_graphics::image::TextureStore; use std::num::NonZeroU32; pub use allocation::Allocation; @@ -61,99 +62,6 @@ impl Atlas { self.layers.len() } - pub fn upload( - &mut self, - width: u32, - height: u32, - data: &[u8], - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - ) -> Option { - use wgpu::util::DeviceExt; - - let entry = { - let current_size = self.layers.len(); - let entry = self.allocate(width, height)?; - - // We grow the internal texture after allocating if necessary - let new_layers = self.layers.len() - current_size; - self.grow(new_layers, device, encoder); - - entry - }; - - log::info!("Allocated atlas entry: {:?}", entry); - - // It is a webgpu requirement that: - // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 - // So we calculate padded_width by rounding width up to the next - // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. - let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; - let padding = (align - (4 * width) % align) % align; - let padded_width = (4 * width + padding) as usize; - let padded_data_size = padded_width * height as usize; - - let mut padded_data = vec![0; padded_data_size]; - - for row in 0..height as usize { - let offset = row * padded_width; - - padded_data[offset..offset + 4 * width as usize].copy_from_slice( - &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], - ) - } - - let buffer = - device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("iced_wgpu::image staging buffer"), - contents: &padded_data, - usage: wgpu::BufferUsages::COPY_SRC, - }); - - match &entry { - Entry::Contiguous(allocation) => { - self.upload_allocation( - &buffer, width, height, padding, 0, allocation, encoder, - ); - } - Entry::Fragmented { fragments, .. } => { - for fragment in fragments { - let (x, y) = fragment.position; - let offset = (y * padded_width as u32 + 4 * x) as usize; - - self.upload_allocation( - &buffer, - width, - height, - padding, - offset, - &fragment.allocation, - encoder, - ); - } - } - } - - log::info!("Current atlas: {:?}", self); - - Some(entry) - } - - pub fn remove(&mut self, entry: &Entry) { - log::info!("Removing atlas entry: {:?}", entry); - - match entry { - Entry::Contiguous(allocation) => { - self.deallocate(allocation); - } - Entry::Fragmented { fragments, .. } => { - for fragment in fragments { - self.deallocate(&fragment.allocation); - } - } - } - } - fn allocate(&mut self, width: u32, height: u32) -> Option { // Allocate one layer if texture fits perfectly if width == SIZE && height == SIZE { @@ -388,3 +296,100 @@ impl Atlas { }); } } + +impl TextureStore for Atlas { + type Entry = Entry; + type State<'a> = (&'a wgpu::Device, &'a mut wgpu::CommandEncoder); + + fn upload( + &mut self, + width: u32, + height: u32, + data: &[u8], + (device, encoder): &mut Self::State<'_>, + ) -> Option { + use wgpu::util::DeviceExt; + + let entry = { + let current_size = self.layers.len(); + let entry = self.allocate(width, height)?; + + // We grow the internal texture after allocating if necessary + let new_layers = self.layers.len() - current_size; + self.grow(new_layers, device, encoder); + + entry + }; + + log::info!("Allocated atlas entry: {:?}", entry); + + // It is a webgpu requirement that: + // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 + // So we calculate padded_width by rounding width up to the next + // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. + let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; + let padding = (align - (4 * width) % align) % align; + let padded_width = (4 * width + padding) as usize; + let padded_data_size = padded_width * height as usize; + + let mut padded_data = vec![0; padded_data_size]; + + for row in 0..height as usize { + let offset = row * padded_width; + + padded_data[offset..offset + 4 * width as usize].copy_from_slice( + &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], + ) + } + + let buffer = + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("iced_wgpu::image staging buffer"), + contents: &padded_data, + usage: wgpu::BufferUsages::COPY_SRC, + }); + + match &entry { + Entry::Contiguous(allocation) => { + self.upload_allocation( + &buffer, width, height, padding, 0, allocation, encoder, + ); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + let (x, y) = fragment.position; + let offset = (y * padded_width as u32 + 4 * x) as usize; + + self.upload_allocation( + &buffer, + width, + height, + padding, + offset, + &fragment.allocation, + encoder, + ); + } + } + } + + log::info!("Current atlas: {:?}", self); + + Some(entry) + } + + fn remove(&mut self, entry: &Entry, _: &mut Self::State<'_>) { + log::info!("Removing atlas entry: {:?}", entry); + + match entry { + Entry::Contiguous(allocation) => { + self.deallocate(allocation); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + self.deallocate(&fragment.allocation); + } + } + } + } +} diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs index 9b3f16df..0c2f67fc 100644 --- a/wgpu/src/image/atlas/entry.rs +++ b/wgpu/src/image/atlas/entry.rs @@ -1,4 +1,5 @@ use crate::image::atlas; +use iced_graphics::image::TextureStoreEntry; #[derive(Debug)] pub enum Entry { @@ -9,9 +10,8 @@ pub enum Entry { }, } -impl Entry { - #[cfg(feature = "image_rs")] - pub fn size(&self) -> (u32, u32) { +impl TextureStoreEntry for Entry { + fn size(&self) -> (u32, u32) { match self { Entry::Contiguous(allocation) => allocation.size(), Entry::Fragmented { size, .. } => *size, diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs deleted file mode 100644 index 2b4d4af3..00000000 --- a/wgpu/src/image/raster.rs +++ /dev/null @@ -1,222 +0,0 @@ -use crate::image::atlas::{self, Atlas}; -use iced_native::image; -use std::collections::{HashMap, HashSet}; - -use bitflags::bitflags; - -#[derive(Debug)] -pub enum Memory { - Host(::image_rs::ImageBuffer<::image_rs::Bgra, Vec>), - Device(atlas::Entry), - NotFound, - Invalid, -} - -impl Memory { - pub fn dimensions(&self) -> (u32, u32) { - match self { - Memory::Host(image) => image.dimensions(), - Memory::Device(entry) => entry.size(), - Memory::NotFound => (1, 1), - Memory::Invalid => (1, 1), - } - } -} - -#[derive(Debug)] -pub struct Cache { - map: HashMap, - hits: HashSet, -} - -impl Cache { - pub fn new() -> Self { - Self { - map: HashMap::new(), - hits: HashSet::new(), - } - } - - pub fn load(&mut self, handle: &image::Handle) -> &mut Memory { - if self.contains(handle) { - return self.get(handle).unwrap(); - } - - let memory = match handle.data() { - image::Data::Path(path) => { - if let Ok(image) = image_rs::open(path) { - let operation = std::fs::File::open(path) - .ok() - .map(std::io::BufReader::new) - .and_then(|mut reader| { - Operation::from_exif(&mut reader).ok() - }) - .unwrap_or_else(Operation::empty); - - Memory::Host(operation.perform(image.to_bgra8())) - } else { - Memory::NotFound - } - } - image::Data::Bytes(bytes) => { - if let Ok(image) = image_rs::load_from_memory(bytes) { - let operation = - Operation::from_exif(&mut std::io::Cursor::new(bytes)) - .ok() - .unwrap_or_else(Operation::empty); - - Memory::Host(operation.perform(image.to_bgra8())) - } else { - Memory::Invalid - } - } - image::Data::Pixels { - width, - height, - pixels, - } => { - if let Some(image) = image_rs::ImageBuffer::from_vec( - *width, - *height, - pixels.to_vec(), - ) { - Memory::Host(image) - } else { - Memory::Invalid - } - } - }; - - self.insert(handle, memory); - self.get(handle).unwrap() - } - - pub fn upload( - &mut self, - handle: &image::Handle, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - atlas: &mut Atlas, - ) -> Option<&atlas::Entry> { - let memory = self.load(handle); - - if let Memory::Host(image) = memory { - let (width, height) = image.dimensions(); - - let entry = atlas.upload(width, height, image, device, encoder)?; - - *memory = Memory::Device(entry); - } - - if let Memory::Device(allocation) = memory { - Some(allocation) - } else { - None - } - } - - pub fn trim(&mut self, atlas: &mut Atlas) { - let hits = &self.hits; - - self.map.retain(|k, memory| { - let retain = hits.contains(k); - - if !retain { - if let Memory::Device(entry) = memory { - atlas.remove(entry); - } - } - - retain - }); - - self.hits.clear(); - } - - fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> { - let _ = self.hits.insert(handle.id()); - - self.map.get_mut(&handle.id()) - } - - fn insert(&mut self, handle: &image::Handle, memory: Memory) { - let _ = self.map.insert(handle.id(), memory); - } - - fn contains(&self, handle: &image::Handle) -> bool { - self.map.contains_key(&handle.id()) - } -} - -bitflags! { - struct Operation: u8 { - const FLIP_HORIZONTALLY = 0b001; - const ROTATE_180 = 0b010; - const FLIP_DIAGONALLY = 0b100; - } -} - -impl Operation { - // Meaning of the returned value is described e.g. at: - // https://magnushoff.com/articles/jpeg-orientation/ - fn from_exif(reader: &mut R) -> Result - where - R: std::io::BufRead + std::io::Seek, - { - let exif = exif::Reader::new().read_from_container(reader)?; - - Ok(exif - .get_field(exif::Tag::Orientation, exif::In::PRIMARY) - .and_then(|field| field.value.get_uint(0)) - .and_then(|value| u8::try_from(value).ok()) - .and_then(|value| Self::from_bits(value.saturating_sub(1))) - .unwrap_or_else(Self::empty)) - } - - fn perform

( - self, - image: image_rs::ImageBuffer>, - ) -> image_rs::ImageBuffer> - where - P: image_rs::Pixel + 'static, - { - use image_rs::imageops; - - let mut image = if self.contains(Self::FLIP_DIAGONALLY) { - flip_diagonally(image) - } else { - image - }; - - if self.contains(Self::ROTATE_180) { - imageops::rotate180_in_place(&mut image); - } - - if self.contains(Self::FLIP_HORIZONTALLY) { - imageops::flip_horizontal_in_place(&mut image); - } - - image - } -} - -fn flip_diagonally( - image: I, -) -> image_rs::ImageBuffer::Subpixel>> -where - I: image_rs::GenericImage, - I::Pixel: 'static, -{ - let (width, height) = image.dimensions(); - let mut out = image_rs::ImageBuffer::new(height, width); - - for x in 0..width { - for y in 0..height { - let p = image.get_pixel(x, y); - - out.put_pixel(y, x, p); - } - } - - out -} diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs deleted file mode 100644 index b08a0aa2..00000000 --- a/wgpu/src/image/vector.rs +++ /dev/null @@ -1,173 +0,0 @@ -use crate::image::atlas::{self, Atlas}; - -use iced_native::svg; - -use std::collections::{HashMap, HashSet}; -use std::fs; - -pub enum Svg { - Loaded(usvg::Tree), - NotFound, -} - -impl Svg { - pub fn viewport_dimensions(&self) -> (u32, u32) { - match self { - Svg::Loaded(tree) => { - let size = tree.svg_node().size; - - (size.width() as u32, size.height() as u32) - } - Svg::NotFound => (1, 1), - } - } -} - -#[derive(Debug)] -pub struct Cache { - svgs: HashMap, - rasterized: HashMap<(u64, u32, u32), atlas::Entry>, - svg_hits: HashSet, - rasterized_hits: HashSet<(u64, u32, u32)>, -} - -impl Cache { - pub fn new() -> Self { - Self { - svgs: HashMap::new(), - rasterized: HashMap::new(), - svg_hits: HashSet::new(), - rasterized_hits: HashSet::new(), - } - } - - pub fn load(&mut self, handle: &svg::Handle) -> &Svg { - if self.svgs.contains_key(&handle.id()) { - return self.svgs.get(&handle.id()).unwrap(); - } - - let svg = match handle.data() { - svg::Data::Path(path) => { - let tree = fs::read_to_string(path).ok().and_then(|contents| { - usvg::Tree::from_str( - &contents, - &usvg::Options::default().to_ref(), - ) - .ok() - }); - - tree.map(Svg::Loaded).unwrap_or(Svg::NotFound) - } - svg::Data::Bytes(bytes) => { - match usvg::Tree::from_data( - bytes, - &usvg::Options::default().to_ref(), - ) { - Ok(tree) => Svg::Loaded(tree), - Err(_) => Svg::NotFound, - } - } - }; - - let _ = self.svgs.insert(handle.id(), svg); - self.svgs.get(&handle.id()).unwrap() - } - - pub fn upload( - &mut self, - handle: &svg::Handle, - [width, height]: [f32; 2], - scale: f32, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - texture_atlas: &mut Atlas, - ) -> Option<&atlas::Entry> { - let id = handle.id(); - - let (width, height) = ( - (scale * width).ceil() as u32, - (scale * height).ceil() as u32, - ); - - // TODO: Optimize! - // We currently rerasterize the SVG when its size changes. This is slow - // as heck. A GPU rasterizer like `pathfinder` may perform better. - // It would be cool to be able to smooth resize the `svg` example. - if self.rasterized.contains_key(&(id, width, height)) { - let _ = self.svg_hits.insert(id); - let _ = self.rasterized_hits.insert((id, width, height)); - - return self.rasterized.get(&(id, width, height)); - } - - match self.load(handle) { - Svg::Loaded(tree) => { - if width == 0 || height == 0 { - return None; - } - - // TODO: Optimize! - // We currently rerasterize the SVG when its size changes. This is slow - // as heck. A GPU rasterizer like `pathfinder` may perform better. - // It would be cool to be able to smooth resize the `svg` example. - let mut img = tiny_skia::Pixmap::new(width, height)?; - - resvg::render( - tree, - if width > height { - usvg::FitTo::Width(width) - } else { - usvg::FitTo::Height(height) - }, - img.as_mut(), - )?; - - let mut rgba = img.take(); - rgba.chunks_exact_mut(4).for_each(|rgba| rgba.swap(0, 2)); - - let allocation = texture_atlas.upload( - width, - height, - bytemuck::cast_slice(rgba.as_slice()), - device, - encoder, - )?; - log::debug!("allocating {} {}x{}", id, width, height); - - let _ = self.svg_hits.insert(id); - let _ = self.rasterized_hits.insert((id, width, height)); - let _ = self.rasterized.insert((id, width, height), allocation); - - self.rasterized.get(&(id, width, height)) - } - Svg::NotFound => None, - } - } - - pub fn trim(&mut self, atlas: &mut Atlas) { - let svg_hits = &self.svg_hits; - let rasterized_hits = &self.rasterized_hits; - - self.svgs.retain(|k, _| svg_hits.contains(k)); - self.rasterized.retain(|k, entry| { - let retain = rasterized_hits.contains(k); - - if !retain { - atlas.remove(entry); - } - - retain - }); - self.svg_hits.clear(); - self.rasterized_hits.clear(); - } -} - -impl std::fmt::Debug for Svg { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Svg::Loaded(_) => write!(f, "Svg::Loaded"), - Svg::NotFound => write!(f, "Svg::NotFound"), - } - } -} -- cgit From 5575e6ea0897e406674e7e4239808fbf9daa07c3 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 24 Oct 2022 17:06:02 -0700 Subject: Add image/svg support to `iced_glow` https://github.com/iced-rs/iced/issues/674 Uses image/svg support in `iced_graphics`. The is not currently using an atlas, and uses one texture/draw per image. This should be good enough for now; supporting images with glow is better than not supporting them, and if something else performs better, that improvement can be made without any change to the public API. --- Cargo.toml | 4 +- glow/Cargo.toml | 19 +++- glow/src/backend.rs | 26 ++++- glow/src/image.rs | 212 ++++++++++++++++++++++++++++++++++++++ glow/src/image/textures.rs | 82 +++++++++++++++ glow/src/lib.rs | 2 + glow/src/shader/common/image.frag | 22 ++++ glow/src/shader/common/image.vert | 9 ++ 8 files changed, 368 insertions(+), 8 deletions(-) create mode 100644 glow/src/image.rs create mode 100644 glow/src/image/textures.rs create mode 100644 glow/src/shader/common/image.frag create mode 100644 glow/src/shader/common/image.vert diff --git a/Cargo.toml b/Cargo.toml index f7d578ba..e4801d7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,9 @@ resolver = "2" [features] default = ["wgpu"] # Enables the `Image` widget -image = ["iced_wgpu/image", "image_rs"] +image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"] # Enables the `Svg` widget -svg = ["iced_wgpu/svg"] +svg = ["iced_wgpu?/svg", "iced_glow?/svg"] # Enables the `Canvas` widget canvas = ["iced_graphics/canvas"] # Enables the `QRCode` widget diff --git a/glow/Cargo.toml b/glow/Cargo.toml index 18215e9b..476547d4 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -8,12 +8,23 @@ license = "MIT AND OFL-1.1" repository = "https://github.com/iced-rs/iced" [features] +svg = ["iced_graphics/svg"] +image = ["image_rs", "iced_graphics/image", "png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"] +image_rs = ["iced_graphics/image_rs"] +png = ["iced_graphics/png"] +jpeg = ["iced_graphics/jpeg"] +jpeg_rayon = ["iced_graphics/jpeg_rayon"] +gif = ["iced_graphics/gif"] +webp = ["iced_graphics/webp"] +pnm = ["iced_graphics/pnm"] +ico = ["iced_graphics/ico"] +bmp = ["iced_graphics/bmp"] +hdr = ["iced_graphics/hdr"] +dds = ["iced_graphics/dds"] +farbfeld = ["iced_graphics/farbfeld"] canvas = ["iced_graphics/canvas"] qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] -# Not supported yet! -image = [] -svg = [] [dependencies] glow = "0.11.1" @@ -22,6 +33,8 @@ glyph_brush = "0.7" euclid = "0.22" bytemuck = "1.4" log = "0.4" +kamadak-exif = "0.5" +bitflags = "1.2" [dependencies.iced_native] version = "0.5" diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 21af2ecf..1ba70a49 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -1,3 +1,5 @@ +#[cfg(any(feature = "image_rs", feature = "svg"))] +use crate::image; use crate::quad; use crate::text; use crate::{program, triangle}; @@ -15,6 +17,8 @@ use iced_native::{Font, Size}; /// [`iced`]: https://github.com/iced-rs/iced #[derive(Debug)] pub struct Backend { + #[cfg(any(feature = "image_rs", feature = "svg"))] + image_pipeline: image::Pipeline, quad_pipeline: quad::Pipeline, text_pipeline: text::Pipeline, triangle_pipeline: triangle::Pipeline, @@ -32,10 +36,14 @@ impl Backend { let shader_version = program::Version::new(gl); + #[cfg(any(feature = "image_rs", feature = "svg"))] + let image_pipeline = image::Pipeline::new(gl, &shader_version); let quad_pipeline = quad::Pipeline::new(gl, &shader_version); let triangle_pipeline = triangle::Pipeline::new(gl, &shader_version); Self { + #[cfg(any(feature = "image_rs", feature = "svg"))] + image_pipeline, quad_pipeline, text_pipeline, triangle_pipeline, @@ -70,6 +78,9 @@ impl Backend { viewport_size.height, ); } + + #[cfg(any(feature = "image_rs", feature = "svg"))] + self.image_pipeline.trim_cache(gl); } fn flush( @@ -112,6 +123,15 @@ impl Backend { ); } + #[cfg(any(feature = "image_rs", feature = "svg"))] + if !layer.images.is_empty() { + let scaled = transformation + * Transformation::scale(scale_factor, scale_factor); + + self.image_pipeline + .draw(gl, scaled, scale_factor, &layer.images); + } + if !layer.text.is_empty() { for text in layer.text.iter() { // Target physical coordinates directly to avoid blurry text @@ -236,10 +256,10 @@ impl backend::Text for Backend { } } -#[cfg(feature = "image")] +#[cfg(feature = "image_rs")] impl backend::Image for Backend { - fn dimensions(&self, _handle: &iced_native::image::Handle) -> (u32, u32) { - (50, 50) + fn dimensions(&self, handle: &iced_native::image::Handle) -> (u32, u32) { + self.image_pipeline.dimensions(handle) } } diff --git a/glow/src/image.rs b/glow/src/image.rs new file mode 100644 index 00000000..51e3016e --- /dev/null +++ b/glow/src/image.rs @@ -0,0 +1,212 @@ +use crate::program::{self, Shader}; +use crate::Transformation; +use glow::HasContext; +use iced_graphics::layer; +#[cfg(feature = "image_rs")] +use std::cell::RefCell; + +pub use iced_graphics::triangle::{Mesh2D, Vertex2D}; + +#[cfg(feature = "image_rs")] +use iced_graphics::image::raster; + +#[cfg(feature = "svg")] +use iced_graphics::image::vector; + +mod textures; +use textures::{Entry, Textures}; + +#[derive(Debug)] +pub(crate) struct Pipeline { + program: ::Program, + vertex_array: ::VertexArray, + vertex_buffer: ::Buffer, + transform_location: ::UniformLocation, + textures: Textures, + #[cfg(feature = "image_rs")] + raster_cache: RefCell>, + #[cfg(feature = "svg")] + vector_cache: vector::Cache, +} + +impl Pipeline { + pub fn new( + gl: &glow::Context, + shader_version: &program::Version, + ) -> Pipeline { + let program = unsafe { + let vertex_shader = Shader::vertex( + gl, + shader_version, + include_str!("shader/common/image.vert"), + ); + let fragment_shader = Shader::fragment( + gl, + shader_version, + include_str!("shader/common/image.frag"), + ); + + program::create( + gl, + &[vertex_shader, fragment_shader], + &[(0, "i_Position")], + ) + }; + + let transform_location = + unsafe { gl.get_uniform_location(program, "u_Transform") } + .expect("Get transform location"); + + unsafe { + gl.use_program(Some(program)); + + let transform: [f32; 16] = Transformation::identity().into(); + gl.uniform_matrix_4_f32_slice( + Some(&transform_location), + false, + &transform, + ); + + gl.use_program(None); + } + + let vertex_buffer = + unsafe { gl.create_buffer().expect("Create vertex buffer") }; + let vertex_array = + unsafe { gl.create_vertex_array().expect("Create vertex array") }; + + unsafe { + gl.bind_vertex_array(Some(vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); + + let vertices = &[0u8, 0, 1, 0, 0, 1, 1, 1]; + gl.buffer_data_size( + glow::ARRAY_BUFFER, + vertices.len() as i32, + glow::STATIC_DRAW, + ); + gl.buffer_sub_data_u8_slice( + glow::ARRAY_BUFFER, + 0, + bytemuck::cast_slice(vertices), + ); + + gl.enable_vertex_attrib_array(0); + gl.vertex_attrib_pointer_f32( + 0, + 2, + glow::UNSIGNED_BYTE, + false, + 0, + 0, + ); + + gl.bind_buffer(glow::ARRAY_BUFFER, None); + gl.bind_vertex_array(None); + } + + Pipeline { + program, + vertex_array, + vertex_buffer, + transform_location, + textures: Textures::new(), + #[cfg(feature = "image_rs")] + raster_cache: RefCell::new(raster::Cache::default()), + #[cfg(feature = "svg")] + vector_cache: vector::Cache::default(), + } + } + + #[cfg(feature = "image_rs")] + pub fn dimensions( + &self, + handle: &iced_native::image::Handle, + ) -> (u32, u32) { + self.raster_cache.borrow_mut().load(handle).dimensions() + } + + pub fn draw( + &mut self, + mut gl: &glow::Context, + transformation: Transformation, + _scale_factor: f32, + images: &[layer::Image], + ) { + unsafe { + gl.use_program(Some(self.program)); + gl.bind_vertex_array(Some(self.vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); + } + + #[cfg(feature = "image_rs")] + let mut raster_cache = self.raster_cache.borrow_mut(); + for image in images { + let (entry, bounds) = match &image { + #[cfg(feature = "image_rs")] + layer::Image::Raster { handle, bounds } => ( + raster_cache.upload(handle, &mut gl, &mut self.textures), + bounds, + ), + #[cfg(not(feature = "image_rs"))] + layer::Image::Raster { handle: _, bounds } => (None, bounds), + + #[cfg(feature = "svg")] + layer::Image::Vector { handle, bounds } => { + let size = [bounds.width, bounds.height]; + ( + self.vector_cache.upload( + handle, + size, + _scale_factor, + &mut gl, + &mut self.textures, + ), + bounds, + ) + } + + #[cfg(not(feature = "svg"))] + layer::Image::Vector { handle: _, bounds } => (None, bounds), + }; + + unsafe { + if let Some(Entry { texture, .. }) = entry { + gl.bind_texture(glow::TEXTURE_2D, Some(*texture)) + } else { + continue; + } + + let translate = Transformation::translate(bounds.x, bounds.y); + let scale = Transformation::scale(bounds.width, bounds.height); + let transformation = transformation * translate * scale; + let matrix: [f32; 16] = transformation.into(); + gl.uniform_matrix_4_f32_slice( + Some(&self.transform_location), + false, + &matrix, + ); + + gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4); + + gl.bind_texture(glow::TEXTURE_2D, None); + } + } + + unsafe { + gl.bind_buffer(glow::ARRAY_BUFFER, None); + gl.bind_vertex_array(None); + gl.use_program(None); + } + } + + pub fn trim_cache(&mut self, mut gl: &glow::Context) { + #[cfg(feature = "image_rs")] + self.raster_cache + .borrow_mut() + .trim(&mut self.textures, &mut gl); + + #[cfg(feature = "svg")] + self.vector_cache.trim(&mut self.textures, &mut gl); + } +} diff --git a/glow/src/image/textures.rs b/glow/src/image/textures.rs new file mode 100644 index 00000000..f43cae1c --- /dev/null +++ b/glow/src/image/textures.rs @@ -0,0 +1,82 @@ +use glow::HasContext; +use iced_graphics::image::{TextureStore, TextureStoreEntry}; + +#[derive(Debug)] +pub struct Textures; + +impl Textures { + pub fn new() -> Self { + Self + } +} + +impl TextureStore for Textures { + type Entry = Entry; + type State<'a> = &'a glow::Context; + + fn upload( + &mut self, + width: u32, + height: u32, + data: &[u8], + gl: &mut &glow::Context, + ) -> Option { + unsafe { + let texture = gl.create_texture().expect("create texture"); + gl.bind_texture(glow::TEXTURE_2D, Some(texture)); + gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::SRGB8_ALPHA8 as i32, + width as i32, + height as i32, + 0, + glow::BGRA, + glow::UNSIGNED_BYTE, + Some(data), + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_WRAP_S, + glow::CLAMP_TO_EDGE as _, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_WRAP_T, + glow::CLAMP_TO_EDGE as _, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MIN_FILTER, + glow::LINEAR as _, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MAG_FILTER, + glow::LINEAR as _, + ); + gl.bind_texture(glow::TEXTURE_2D, None); + + Some(Entry { + size: (width, height), + texture, + }) + } + } + + fn remove(&mut self, entry: &Entry, gl: &mut &glow::Context) { + unsafe { gl.delete_texture(entry.texture) } + } +} + +#[derive(Debug)] +pub struct Entry { + size: (u32, u32), + pub texture: glow::NativeTexture, +} + +impl TextureStoreEntry for Entry { + fn size(&self) -> (u32, u32) { + self.size + } +} diff --git a/glow/src/lib.rs b/glow/src/lib.rs index de9c0002..daeb3e32 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -24,6 +24,8 @@ pub use glow; mod backend; +#[cfg(any(feature = "image_rs", feature = "svg"))] +mod image; mod program; mod quad; mod text; diff --git a/glow/src/shader/common/image.frag b/glow/src/shader/common/image.frag new file mode 100644 index 00000000..5e05abdf --- /dev/null +++ b/glow/src/shader/common/image.frag @@ -0,0 +1,22 @@ +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#endif + +uniform sampler2D tex; +in vec2 tex_pos; + +#ifdef HIGHER_THAN_300 +out vec4 fragColor; +#define gl_FragColor fragColor +#endif +#ifdef GL_ES +#define texture texture2D +#endif + +void main() { + gl_FragColor = texture(tex, tex_pos); +} diff --git a/glow/src/shader/common/image.vert b/glow/src/shader/common/image.vert new file mode 100644 index 00000000..93e541f2 --- /dev/null +++ b/glow/src/shader/common/image.vert @@ -0,0 +1,9 @@ +uniform mat4 u_Transform; + +in vec2 i_Position; +out vec2 tex_pos; + +void main() { + gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); + tex_pos = i_Position; +} -- cgit From 8ce8d374b1e8d1d394a42a5ee2bca8af790f0b71 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 5 Nov 2022 03:13:04 +0100 Subject: Refactor some `image` traits a bit - Use `Size` were applicable. - Rename `TextureStore` to `image::Storage`. - Rename `TextureStoreEntry` to `image::storage::Entry`. - Wire up `viewport_dimensions` to `iced_glow` for `Svg`. --- glow/src/backend.rs | 8 ++-- glow/src/image.rs | 64 ++++++++++++++++++----------- glow/src/image/storage.rs | 78 ++++++++++++++++++++++++++++++++++++ glow/src/image/textures.rs | 82 -------------------------------------- graphics/src/backend.rs | 4 +- graphics/src/image.rs | 30 ++------------ graphics/src/image/raster.rs | 32 +++++++++------ graphics/src/image/storage.rs | 31 ++++++++++++++ graphics/src/image/vector.rs | 16 ++++---- graphics/src/renderer.rs | 4 +- native/src/image.rs | 4 +- native/src/svg.rs | 4 +- native/src/widget/image.rs | 4 +- native/src/widget/image/viewer.rs | 4 +- native/src/widget/svg.rs | 4 +- wgpu/src/backend.rs | 4 +- wgpu/src/image.rs | 18 +++++---- wgpu/src/image/atlas.rs | 14 ++++--- wgpu/src/image/atlas/allocation.rs | 6 ++- wgpu/src/image/atlas/allocator.rs | 4 +- wgpu/src/image/atlas/entry.rs | 10 +++-- 21 files changed, 231 insertions(+), 194 deletions(-) create mode 100644 glow/src/image/storage.rs delete mode 100644 glow/src/image/textures.rs create mode 100644 graphics/src/image/storage.rs diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 1ba70a49..35a82c0f 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -258,7 +258,7 @@ impl backend::Text for Backend { #[cfg(feature = "image_rs")] impl backend::Image for Backend { - fn dimensions(&self, handle: &iced_native::image::Handle) -> (u32, u32) { + fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { self.image_pipeline.dimensions(handle) } } @@ -267,8 +267,8 @@ impl backend::Image for Backend { impl backend::Svg for Backend { fn viewport_dimensions( &self, - _handle: &iced_native::svg::Handle, - ) -> (u32, u32) { - (50, 50) + handle: &iced_native::svg::Handle, + ) -> Size { + self.image_pipeline.viewport_dimensions(handle) } } diff --git a/glow/src/image.rs b/glow/src/image.rs index 51e3016e..66620537 100644 --- a/glow/src/image.rs +++ b/glow/src/image.rs @@ -1,20 +1,24 @@ -use crate::program::{self, Shader}; -use crate::Transformation; -use glow::HasContext; -use iced_graphics::layer; -#[cfg(feature = "image_rs")] -use std::cell::RefCell; +mod storage; + +use storage::Storage; pub use iced_graphics::triangle::{Mesh2D, Vertex2D}; +use crate::program::{self, Shader}; +use crate::Transformation; + #[cfg(feature = "image_rs")] use iced_graphics::image::raster; #[cfg(feature = "svg")] use iced_graphics::image::vector; -mod textures; -use textures::{Entry, Textures}; +use iced_graphics::layer; +use iced_graphics::Size; + +use glow::HasContext; + +use std::cell::RefCell; #[derive(Debug)] pub(crate) struct Pipeline { @@ -22,11 +26,11 @@ pub(crate) struct Pipeline { vertex_array: ::VertexArray, vertex_buffer: ::Buffer, transform_location: ::UniformLocation, - textures: Textures, + storage: Storage, #[cfg(feature = "image_rs")] - raster_cache: RefCell>, + raster_cache: RefCell>, #[cfg(feature = "svg")] - vector_cache: vector::Cache, + vector_cache: RefCell>, } impl Pipeline { @@ -110,22 +114,30 @@ impl Pipeline { vertex_array, vertex_buffer, transform_location, - textures: Textures::new(), + storage: Storage::default(), #[cfg(feature = "image_rs")] raster_cache: RefCell::new(raster::Cache::default()), #[cfg(feature = "svg")] - vector_cache: vector::Cache::default(), + vector_cache: RefCell::new(vector::Cache::default()), } } #[cfg(feature = "image_rs")] - pub fn dimensions( - &self, - handle: &iced_native::image::Handle, - ) -> (u32, u32) { + pub fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { self.raster_cache.borrow_mut().load(handle).dimensions() } + #[cfg(feature = "svg")] + pub fn viewport_dimensions( + &self, + handle: &iced_native::svg::Handle, + ) -> Size { + let mut cache = self.vector_cache.borrow_mut(); + let svg = cache.load(handle); + + svg.viewport_dimensions() + } + pub fn draw( &mut self, mut gl: &glow::Context, @@ -141,11 +153,15 @@ impl Pipeline { #[cfg(feature = "image_rs")] let mut raster_cache = self.raster_cache.borrow_mut(); + + #[cfg(feature = "svg")] + let mut vector_cache = self.vector_cache.borrow_mut(); + for image in images { let (entry, bounds) = match &image { #[cfg(feature = "image_rs")] layer::Image::Raster { handle, bounds } => ( - raster_cache.upload(handle, &mut gl, &mut self.textures), + raster_cache.upload(handle, &mut gl, &mut self.storage), bounds, ), #[cfg(not(feature = "image_rs"))] @@ -155,12 +171,12 @@ impl Pipeline { layer::Image::Vector { handle, bounds } => { let size = [bounds.width, bounds.height]; ( - self.vector_cache.upload( + vector_cache.upload( handle, size, _scale_factor, &mut gl, - &mut self.textures, + &mut self.storage, ), bounds, ) @@ -171,7 +187,7 @@ impl Pipeline { }; unsafe { - if let Some(Entry { texture, .. }) = entry { + if let Some(storage::Entry { texture, .. }) = entry { gl.bind_texture(glow::TEXTURE_2D, Some(*texture)) } else { continue; @@ -204,9 +220,11 @@ impl Pipeline { #[cfg(feature = "image_rs")] self.raster_cache .borrow_mut() - .trim(&mut self.textures, &mut gl); + .trim(&mut self.storage, &mut gl); #[cfg(feature = "svg")] - self.vector_cache.trim(&mut self.textures, &mut gl); + self.vector_cache + .borrow_mut() + .trim(&mut self.storage, &mut gl); } } diff --git a/glow/src/image/storage.rs b/glow/src/image/storage.rs new file mode 100644 index 00000000..e2171fb5 --- /dev/null +++ b/glow/src/image/storage.rs @@ -0,0 +1,78 @@ +use iced_graphics::image; +use iced_graphics::Size; + +use glow::HasContext; + +#[derive(Debug, Default)] +pub struct Storage; + +impl image::Storage for Storage { + type Entry = Entry; + type State<'a> = &'a glow::Context; + + fn upload( + &mut self, + width: u32, + height: u32, + data: &[u8], + gl: &mut &glow::Context, + ) -> Option { + unsafe { + let texture = gl.create_texture().expect("create texture"); + gl.bind_texture(glow::TEXTURE_2D, Some(texture)); + gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::SRGB8_ALPHA8 as i32, + width as i32, + height as i32, + 0, + glow::BGRA, + glow::UNSIGNED_BYTE, + Some(data), + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_WRAP_S, + glow::CLAMP_TO_EDGE as _, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_WRAP_T, + glow::CLAMP_TO_EDGE as _, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MIN_FILTER, + glow::LINEAR as _, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MAG_FILTER, + glow::LINEAR as _, + ); + gl.bind_texture(glow::TEXTURE_2D, None); + + Some(Entry { + size: Size::new(width, height), + texture, + }) + } + } + + fn remove(&mut self, entry: &Entry, gl: &mut &glow::Context) { + unsafe { gl.delete_texture(entry.texture) } + } +} + +#[derive(Debug)] +pub struct Entry { + size: Size, + pub(super) texture: glow::NativeTexture, +} + +impl image::storage::Entry for Entry { + fn size(&self) -> Size { + self.size + } +} diff --git a/glow/src/image/textures.rs b/glow/src/image/textures.rs deleted file mode 100644 index f43cae1c..00000000 --- a/glow/src/image/textures.rs +++ /dev/null @@ -1,82 +0,0 @@ -use glow::HasContext; -use iced_graphics::image::{TextureStore, TextureStoreEntry}; - -#[derive(Debug)] -pub struct Textures; - -impl Textures { - pub fn new() -> Self { - Self - } -} - -impl TextureStore for Textures { - type Entry = Entry; - type State<'a> = &'a glow::Context; - - fn upload( - &mut self, - width: u32, - height: u32, - data: &[u8], - gl: &mut &glow::Context, - ) -> Option { - unsafe { - let texture = gl.create_texture().expect("create texture"); - gl.bind_texture(glow::TEXTURE_2D, Some(texture)); - gl.tex_image_2d( - glow::TEXTURE_2D, - 0, - glow::SRGB8_ALPHA8 as i32, - width as i32, - height as i32, - 0, - glow::BGRA, - glow::UNSIGNED_BYTE, - Some(data), - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_WRAP_S, - glow::CLAMP_TO_EDGE as _, - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_WRAP_T, - glow::CLAMP_TO_EDGE as _, - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_MIN_FILTER, - glow::LINEAR as _, - ); - gl.tex_parameter_i32( - glow::TEXTURE_2D, - glow::TEXTURE_MAG_FILTER, - glow::LINEAR as _, - ); - gl.bind_texture(glow::TEXTURE_2D, None); - - Some(Entry { - size: (width, height), - texture, - }) - } - } - - fn remove(&mut self, entry: &Entry, gl: &mut &glow::Context) { - unsafe { gl.delete_texture(entry.texture) } - } -} - -#[derive(Debug)] -pub struct Entry { - size: (u32, u32), - pub texture: glow::NativeTexture, -} - -impl TextureStoreEntry for Entry { - fn size(&self) -> (u32, u32) { - self.size - } -} diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 7e0af2cc..2f8e9fc3 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -66,11 +66,11 @@ pub trait Text { /// A graphics backend that supports image rendering. pub trait Image { /// Returns the dimensions of the provided image. - fn dimensions(&self, handle: &image::Handle) -> (u32, u32); + fn dimensions(&self, handle: &image::Handle) -> Size; } /// A graphics backend that supports SVG rendering. pub trait Svg { /// Returns the viewport dimensions of the provided SVG. - fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32); + fn viewport_dimensions(&self, handle: &svg::Handle) -> Size; } diff --git a/graphics/src/image.rs b/graphics/src/image.rs index f1153882..04f4ff9d 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -1,34 +1,10 @@ -//! Image loading and caching - -use std::fmt::Debug; - +//! Render images. #[cfg(feature = "image_rs")] pub mod raster; #[cfg(feature = "svg")] pub mod vector; -/// Entry in the texture store -pub trait TextureStoreEntry: Debug { - /// Width and height of the entry - fn size(&self) -> (u32, u32); -} - -/// Stores cached image data for use in rendering -pub trait TextureStore { - /// Entry in the texture store - type Entry: TextureStoreEntry; - /// State passed to upload/remove - type State<'a>; +pub mod storage; - /// Upload image data - fn upload( - &mut self, - width: u32, - height: u32, - data: &[u8], - state: &mut Self::State<'_>, - ) -> Option; - /// Remome image from store - fn remove(&mut self, entry: &Self::Entry, state: &mut Self::State<'_>); -} +pub use storage::Storage; diff --git a/graphics/src/image/raster.rs b/graphics/src/image/raster.rs index f9672dd5..8ed9615d 100644 --- a/graphics/src/image/raster.rs +++ b/graphics/src/image/raster.rs @@ -1,15 +1,15 @@ -//! Raster image loading and caching +//! Raster image loading and caching. +use crate::image::Storage; +use crate::Size; use iced_native::image; -use std::collections::{HashMap, HashSet}; use bitflags::bitflags; - -use super::{TextureStore, TextureStoreEntry}; +use std::collections::{HashMap, HashSet}; /// Entry in cache corresponding to an image handle #[derive(Debug)] -pub enum Memory { +pub enum Memory { /// Image data on host Host(::image_rs::ImageBuffer<::image_rs::Rgba, Vec>), /// Texture store entry @@ -20,26 +20,32 @@ pub enum Memory { Invalid, } -impl Memory { +impl Memory { /// Width and height of image - pub fn dimensions(&self) -> (u32, u32) { + pub fn dimensions(&self) -> Size { + use crate::image::storage::Entry; + match self { - Memory::Host(image) => image.dimensions(), + Memory::Host(image) => { + let (width, height) = image.dimensions(); + + Size::new(width, height) + } Memory::Device(entry) => entry.size(), - Memory::NotFound => (1, 1), - Memory::Invalid => (1, 1), + Memory::NotFound => Size::new(1, 1), + Memory::Invalid => Size::new(1, 1), } } } /// Caches image raster data #[derive(Debug)] -pub struct Cache { +pub struct Cache { map: HashMap>, hits: HashSet, } -impl Cache { +impl Cache { /// Load image pub fn load(&mut self, handle: &image::Handle) -> &mut Memory { if self.contains(handle) { @@ -153,7 +159,7 @@ impl Cache { } } -impl Default for Cache { +impl Default for Cache { fn default() -> Self { Self { map: HashMap::new(), diff --git a/graphics/src/image/storage.rs b/graphics/src/image/storage.rs new file mode 100644 index 00000000..2098c7b2 --- /dev/null +++ b/graphics/src/image/storage.rs @@ -0,0 +1,31 @@ +//! Store images. +use crate::Size; + +use std::fmt::Debug; + +/// Stores cached image data for use in rendering +pub trait Storage { + /// The type of an [`Entry`] in the [`Storage`]. + type Entry: Entry; + + /// State provided to upload or remove a [`Self::Entry`]. + type State<'a>; + + /// Upload the image data of a [`Self::Entry`]. + fn upload( + &mut self, + width: u32, + height: u32, + data: &[u8], + state: &mut Self::State<'_>, + ) -> Option; + + /// Romve a [`Self::Entry`] from the [`Storage`]. + fn remove(&mut self, entry: &Self::Entry, state: &mut Self::State<'_>); +} + +/// An entry in some [`Storage`], +pub trait Entry: Debug { + /// The [`Size`] of the [`Entry`]. + fn size(&self) -> Size; +} diff --git a/graphics/src/image/vector.rs b/graphics/src/image/vector.rs index 0062e2ce..818fdd20 100644 --- a/graphics/src/image/vector.rs +++ b/graphics/src/image/vector.rs @@ -1,12 +1,12 @@ //! Vector image loading and caching +use crate::image::Storage; use iced_native::svg; +use iced_native::Size; use std::collections::{HashMap, HashSet}; use std::fs; -use super::TextureStore; - /// Entry in cache corresponding to an svg handle pub enum Svg { /// Parsed svg @@ -17,28 +17,28 @@ pub enum Svg { impl Svg { /// Viewport width and height - pub fn viewport_dimensions(&self) -> (u32, u32) { + pub fn viewport_dimensions(&self) -> Size { match self { Svg::Loaded(tree) => { let size = tree.svg_node().size; - (size.width() as u32, size.height() as u32) + Size::new(size.width() as u32, size.height() as u32) } - Svg::NotFound => (1, 1), + Svg::NotFound => Size::new(1, 1), } } } /// Caches svg vector and raster data #[derive(Debug)] -pub struct Cache { +pub struct Cache { svgs: HashMap, rasterized: HashMap<(u64, u32, u32), T::Entry>, svg_hits: HashSet, rasterized_hits: HashSet<(u64, u32, u32)>, } -impl Cache { +impl Cache { /// Load svg pub fn load(&mut self, handle: &svg::Handle) -> &Svg { if self.svgs.contains_key(&handle.id()) { @@ -162,7 +162,7 @@ impl Cache { } } -impl Default for Cache { +impl Default for Cache { fn default() -> Self { Self { svgs: HashMap::new(), diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index cdbc4f40..036b398c 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -183,7 +183,7 @@ where { type Handle = image::Handle; - fn dimensions(&self, handle: &image::Handle) -> (u32, u32) { + fn dimensions(&self, handle: &image::Handle) -> Size { self.backend().dimensions(handle) } @@ -196,7 +196,7 @@ impl svg::Renderer for Renderer where B: Backend + backend::Svg, { - fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) { + fn dimensions(&self, handle: &svg::Handle) -> Size { self.backend().viewport_dimensions(handle) } diff --git a/native/src/image.rs b/native/src/image.rs index b849ef84..b313d96d 100644 --- a/native/src/image.rs +++ b/native/src/image.rs @@ -1,5 +1,5 @@ //! Load and draw raster graphics. -use crate::{Hasher, Rectangle}; +use crate::{Hasher, Rectangle, Size}; use std::borrow::Cow; use std::hash::{Hash, Hasher as _}; @@ -126,7 +126,7 @@ pub trait Renderer: crate::Renderer { type Handle: Clone + Hash; /// Returns the dimensions of an image for the given [`Handle`]. - fn dimensions(&self, handle: &Self::Handle) -> (u32, u32); + fn dimensions(&self, handle: &Self::Handle) -> Size; /// Draws an image with the given [`Handle`] and inside the provided /// `bounds`. diff --git a/native/src/svg.rs b/native/src/svg.rs index d4d20182..a8e481d2 100644 --- a/native/src/svg.rs +++ b/native/src/svg.rs @@ -1,5 +1,5 @@ //! Load and draw vector graphics. -use crate::{Hasher, Rectangle}; +use crate::{Hasher, Rectangle, Size}; use std::borrow::Cow; use std::hash::{Hash, Hasher as _}; @@ -82,7 +82,7 @@ impl std::fmt::Debug for Data { /// [renderer]: crate::renderer pub trait Renderer: crate::Renderer { /// Returns the default dimensions of an SVG for the given [`Handle`]. - fn dimensions(&self, handle: &Handle) -> (u32, u32); + fn dimensions(&self, handle: &Handle) -> Size; /// Draws an SVG with the given [`Handle`] and inside the provided `bounds`. fn draw(&mut self, handle: Handle, bounds: Rectangle); diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 91d68e34..8bd8ca1e 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -85,7 +85,7 @@ where { // The raw w/h of the underlying image let image_size = { - let (width, height) = renderer.dimensions(handle); + let Size { width, height } = renderer.dimensions(handle); Size::new(width as f32, height as f32) }; @@ -149,7 +149,7 @@ where _cursor_position: Point, _viewport: &Rectangle, ) { - let (width, height) = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.dimensions(&self.handle); let image_size = Size::new(width as f32, height as f32); let bounds = layout.bounds(); diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index b1fe596c..9c83287e 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -108,7 +108,7 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let (width, height) = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.dimensions(&self.handle); let mut size = limits .width(self.width) @@ -409,7 +409,7 @@ pub fn image_size( where Renderer: image::Renderer, { - let (width, height) = renderer.dimensions(handle); + let Size { width, height } = renderer.dimensions(handle); let (width, height) = { let dimensions = (width as f32, height as f32); diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs index aa68bfb8..1015ed0a 100644 --- a/native/src/widget/svg.rs +++ b/native/src/widget/svg.rs @@ -83,7 +83,7 @@ where limits: &layout::Limits, ) -> layout::Node { // The raw w/h of the underlying image - let (width, height) = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.dimensions(&self.handle); let image_size = Size::new(width as f32, height as f32); // The size to be available to the widget prior to `Shrink`ing @@ -120,7 +120,7 @@ where _cursor_position: Point, _viewport: &Rectangle, ) { - let (width, height) = renderer.dimensions(&self.handle); + let Size { width, height } = renderer.dimensions(&self.handle); let image_size = Size::new(width as f32, height as f32); let bounds = layout.bounds(); diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index efe35a3e..e8a8efa7 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -296,7 +296,7 @@ impl backend::Text for Backend { #[cfg(feature = "image_rs")] impl backend::Image for Backend { - fn dimensions(&self, handle: &iced_native::image::Handle) -> (u32, u32) { + fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { self.image_pipeline.dimensions(handle) } } @@ -306,7 +306,7 @@ impl backend::Svg for Backend { fn viewport_dimensions( &self, handle: &iced_native::svg::Handle, - ) -> (u32, u32) { + ) -> Size { self.image_pipeline.viewport_dimensions(handle) } } diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index b9f165f0..a4a422ce 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -10,7 +10,8 @@ use crate::Transformation; use atlas::Atlas; use iced_graphics::layer; -use iced_native::Rectangle; +use iced_native::{Rectangle, Size}; + use std::cell::RefCell; use std::mem; @@ -262,7 +263,7 @@ impl Pipeline { } #[cfg(feature = "image_rs")] - pub fn dimensions(&self, handle: &image::Handle) -> (u32, u32) { + pub fn dimensions(&self, handle: &image::Handle) -> Size { let mut cache = self.raster_cache.borrow_mut(); let memory = cache.load(handle); @@ -270,7 +271,7 @@ impl Pipeline { } #[cfg(feature = "svg")] - pub fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32) { + pub fn viewport_dimensions(&self, handle: &svg::Handle) -> Size { let mut cache = self.vector_cache.borrow_mut(); let svg = cache.load(handle); @@ -515,15 +516,18 @@ fn add_instances( add_instance(image_position, image_size, allocation, instances); } atlas::Entry::Fragmented { fragments, size } => { - let scaling_x = image_size[0] / size.0 as f32; - let scaling_y = image_size[1] / size.1 as f32; + let scaling_x = image_size[0] / size.width as f32; + let scaling_y = image_size[1] / size.height as f32; for fragment in fragments { let allocation = &fragment.allocation; let [x, y] = image_position; let (fragment_x, fragment_y) = fragment.position; - let (fragment_width, fragment_height) = allocation.size(); + let Size { + width: fragment_width, + height: fragment_height, + } = allocation.size(); let position = [ x + fragment_x as f32 * scaling_x, @@ -549,7 +553,7 @@ fn add_instance( instances: &mut Vec, ) { let (x, y) = allocation.position(); - let (width, height) = allocation.size(); + let Size { width, height } = allocation.size(); let layer = allocation.layer(); let instance = Instance { diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index d3e0c753..bfb3a9f1 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -4,9 +4,6 @@ mod allocation; mod allocator; mod layer; -use iced_graphics::image::TextureStore; -use std::num::NonZeroU32; - pub use allocation::Allocation; pub use entry::Entry; pub use layer::Layer; @@ -15,6 +12,11 @@ use allocator::Allocator; pub const SIZE: u32 = 2048; +use iced_graphics::image; +use iced_graphics::Size; + +use std::num::NonZeroU32; + #[derive(Debug)] pub struct Atlas { texture: wgpu::Texture, @@ -112,7 +114,7 @@ impl Atlas { } return Some(Entry::Fragmented { - size: (width, height), + size: Size::new(width, height), fragments, }); } @@ -192,7 +194,7 @@ impl Atlas { encoder: &mut wgpu::CommandEncoder, ) { let (x, y) = allocation.position(); - let (width, height) = allocation.size(); + let Size { width, height } = allocation.size(); let layer = allocation.layer(); let extent = wgpu::Extent3d { @@ -297,7 +299,7 @@ impl Atlas { } } -impl TextureStore for Atlas { +impl image::Storage for Atlas { type Entry = Entry; type State<'a> = (&'a wgpu::Device, &'a mut wgpu::CommandEncoder); diff --git a/wgpu/src/image/atlas/allocation.rs b/wgpu/src/image/atlas/allocation.rs index 59b7239f..43aba875 100644 --- a/wgpu/src/image/atlas/allocation.rs +++ b/wgpu/src/image/atlas/allocation.rs @@ -1,5 +1,7 @@ use crate::image::atlas::{self, allocator}; +use iced_graphics::Size; + #[derive(Debug)] pub enum Allocation { Partial { @@ -19,10 +21,10 @@ impl Allocation { } } - pub fn size(&self) -> (u32, u32) { + pub fn size(&self) -> Size { match self { Allocation::Partial { region, .. } => region.size(), - Allocation::Full { .. } => (atlas::SIZE, atlas::SIZE), + Allocation::Full { .. } => Size::new(atlas::SIZE, atlas::SIZE), } } diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs index 7a4ff5b1..03effdcb 100644 --- a/wgpu/src/image/atlas/allocator.rs +++ b/wgpu/src/image/atlas/allocator.rs @@ -46,10 +46,10 @@ impl Region { (rectangle.min.x as u32, rectangle.min.y as u32) } - pub fn size(&self) -> (u32, u32) { + pub fn size(&self) -> iced_graphics::Size { let size = self.allocation.rectangle.size(); - (size.width as u32, size.height as u32) + iced_graphics::Size::new(size.width as u32, size.height as u32) } } diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs index 0c2f67fc..69c05a50 100644 --- a/wgpu/src/image/atlas/entry.rs +++ b/wgpu/src/image/atlas/entry.rs @@ -1,17 +1,19 @@ use crate::image::atlas; -use iced_graphics::image::TextureStoreEntry; + +use iced_graphics::image; +use iced_graphics::Size; #[derive(Debug)] pub enum Entry { Contiguous(atlas::Allocation), Fragmented { - size: (u32, u32), + size: Size, fragments: Vec, }, } -impl TextureStoreEntry for Entry { - fn size(&self) -> (u32, u32) { +impl image::storage::Entry for Entry { + fn size(&self) -> Size { match self { Entry::Contiguous(allocation) => allocation.size(), Entry::Fragmented { size, .. } => *size, -- cgit From 438f97a6d00ad0312e7c84b4c1529968bdfba849 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 5 Nov 2022 03:18:13 +0100 Subject: Use RGBA texture for `image` and `svg` pipelines --- glow/src/image/storage.rs | 2 +- graphics/src/image/raster.rs | 2 +- native/src/image.rs | 10 +++++----- wgpu/src/image/atlas.rs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/glow/src/image/storage.rs b/glow/src/image/storage.rs index e2171fb5..9bc20641 100644 --- a/glow/src/image/storage.rs +++ b/glow/src/image/storage.rs @@ -27,7 +27,7 @@ impl image::Storage for Storage { width as i32, height as i32, 0, - glow::BGRA, + glow::RGBA, glow::UNSIGNED_BYTE, Some(data), ); diff --git a/graphics/src/image/raster.rs b/graphics/src/image/raster.rs index 8ed9615d..bad017a9 100644 --- a/graphics/src/image/raster.rs +++ b/graphics/src/image/raster.rs @@ -80,7 +80,7 @@ impl Cache { Memory::Invalid } } - image::Data::Pixels { + image::Data::Rgba { width, height, pixels, diff --git a/native/src/image.rs b/native/src/image.rs index b313d96d..06fd7ae6 100644 --- a/native/src/image.rs +++ b/native/src/image.rs @@ -22,7 +22,7 @@ impl Handle { } /// Creates an image [`Handle`] containing the image pixels directly. This - /// function expects the input data to be provided as a `Vec` of BGRA + /// function expects the input data to be provided as a `Vec` of RGBA /// pixels. /// /// This is useful if you have already decoded your image. @@ -31,7 +31,7 @@ impl Handle { height: u32, pixels: impl Into>, ) -> Handle { - Self::from_data(Data::Pixels { + Self::from_data(Data::Rgba { width, height, pixels: pixels.into(), @@ -93,8 +93,8 @@ pub enum Data { /// In-memory data Bytes(Cow<'static, [u8]>), - /// Decoded image pixels in BGRA format. - Pixels { + /// Decoded image pixels in RGBA format. + Rgba { /// The width of the image. width: u32, /// The height of the image. @@ -109,7 +109,7 @@ impl std::fmt::Debug for Data { match self { Data::Path(path) => write!(f, "Path({:?})", path), Data::Bytes(_) => write!(f, "Bytes(...)"), - Data::Pixels { width, height, .. } => { + Data::Rgba { width, height, .. } => { write!(f, "Pixels({} * {})", width, height) } } diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index bfb3a9f1..eafe2f96 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -38,7 +38,7 @@ impl Atlas { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING, @@ -246,7 +246,7 @@ impl Atlas { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING, -- cgit From d3b613df446aaf47b62c7c24422026f8872e9448 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 5 Nov 2022 03:22:06 +0100 Subject: Remove unnecessary dependencies from `iced_glow` --- glow/Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/glow/Cargo.toml b/glow/Cargo.toml index 476547d4..31a548c8 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -33,8 +33,6 @@ glyph_brush = "0.7" euclid = "0.22" bytemuck = "1.4" log = "0.4" -kamadak-exif = "0.5" -bitflags = "1.2" [dependencies.iced_native] version = "0.5" -- cgit From 0a23f518c70d27f2e44af38e5d3be2a0ab1a9bc1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 5 Nov 2022 03:26:19 +0100 Subject: Remove redundant features in `iced_wgpu` and `iced_glow` --- glow/Cargo.toml | 3 +-- glow/src/backend.rs | 14 +++++++------- glow/src/image.rs | 16 ++++++++-------- glow/src/lib.rs | 2 +- wgpu/Cargo.toml | 3 +-- wgpu/src/backend.rs | 14 +++++++------- wgpu/src/image.rs | 18 +++++++++--------- wgpu/src/lib.rs | 2 +- 8 files changed, 35 insertions(+), 37 deletions(-) diff --git a/glow/Cargo.toml b/glow/Cargo.toml index 31a548c8..a50fd375 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -9,8 +9,7 @@ repository = "https://github.com/iced-rs/iced" [features] svg = ["iced_graphics/svg"] -image = ["image_rs", "iced_graphics/image", "png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"] -image_rs = ["iced_graphics/image_rs"] +image = ["iced_graphics/image"] png = ["iced_graphics/png"] jpeg = ["iced_graphics/jpeg"] jpeg_rayon = ["iced_graphics/jpeg_rayon"] diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 35a82c0f..1a41d540 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -1,4 +1,4 @@ -#[cfg(any(feature = "image_rs", feature = "svg"))] +#[cfg(any(feature = "image", feature = "svg"))] use crate::image; use crate::quad; use crate::text; @@ -17,7 +17,7 @@ use iced_native::{Font, Size}; /// [`iced`]: https://github.com/iced-rs/iced #[derive(Debug)] pub struct Backend { - #[cfg(any(feature = "image_rs", feature = "svg"))] + #[cfg(any(feature = "image", feature = "svg"))] image_pipeline: image::Pipeline, quad_pipeline: quad::Pipeline, text_pipeline: text::Pipeline, @@ -36,13 +36,13 @@ impl Backend { let shader_version = program::Version::new(gl); - #[cfg(any(feature = "image_rs", feature = "svg"))] + #[cfg(any(feature = "image", feature = "svg"))] let image_pipeline = image::Pipeline::new(gl, &shader_version); let quad_pipeline = quad::Pipeline::new(gl, &shader_version); let triangle_pipeline = triangle::Pipeline::new(gl, &shader_version); Self { - #[cfg(any(feature = "image_rs", feature = "svg"))] + #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, quad_pipeline, text_pipeline, @@ -79,7 +79,7 @@ impl Backend { ); } - #[cfg(any(feature = "image_rs", feature = "svg"))] + #[cfg(any(feature = "image", feature = "svg"))] self.image_pipeline.trim_cache(gl); } @@ -123,7 +123,7 @@ impl Backend { ); } - #[cfg(any(feature = "image_rs", feature = "svg"))] + #[cfg(any(feature = "image", feature = "svg"))] if !layer.images.is_empty() { let scaled = transformation * Transformation::scale(scale_factor, scale_factor); @@ -256,7 +256,7 @@ impl backend::Text for Backend { } } -#[cfg(feature = "image_rs")] +#[cfg(feature = "image")] impl backend::Image for Backend { fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { self.image_pipeline.dimensions(handle) diff --git a/glow/src/image.rs b/glow/src/image.rs index 66620537..f906cd4c 100644 --- a/glow/src/image.rs +++ b/glow/src/image.rs @@ -7,7 +7,7 @@ pub use iced_graphics::triangle::{Mesh2D, Vertex2D}; use crate::program::{self, Shader}; use crate::Transformation; -#[cfg(feature = "image_rs")] +#[cfg(feature = "image")] use iced_graphics::image::raster; #[cfg(feature = "svg")] @@ -27,7 +27,7 @@ pub(crate) struct Pipeline { vertex_buffer: ::Buffer, transform_location: ::UniformLocation, storage: Storage, - #[cfg(feature = "image_rs")] + #[cfg(feature = "image")] raster_cache: RefCell>, #[cfg(feature = "svg")] vector_cache: RefCell>, @@ -115,14 +115,14 @@ impl Pipeline { vertex_buffer, transform_location, storage: Storage::default(), - #[cfg(feature = "image_rs")] + #[cfg(feature = "image")] raster_cache: RefCell::new(raster::Cache::default()), #[cfg(feature = "svg")] vector_cache: RefCell::new(vector::Cache::default()), } } - #[cfg(feature = "image_rs")] + #[cfg(feature = "image")] pub fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { self.raster_cache.borrow_mut().load(handle).dimensions() } @@ -151,7 +151,7 @@ impl Pipeline { gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); } - #[cfg(feature = "image_rs")] + #[cfg(feature = "image")] let mut raster_cache = self.raster_cache.borrow_mut(); #[cfg(feature = "svg")] @@ -159,12 +159,12 @@ impl Pipeline { for image in images { let (entry, bounds) = match &image { - #[cfg(feature = "image_rs")] + #[cfg(feature = "image")] layer::Image::Raster { handle, bounds } => ( raster_cache.upload(handle, &mut gl, &mut self.storage), bounds, ), - #[cfg(not(feature = "image_rs"))] + #[cfg(not(feature = "image"))] layer::Image::Raster { handle: _, bounds } => (None, bounds), #[cfg(feature = "svg")] @@ -217,7 +217,7 @@ impl Pipeline { } pub fn trim_cache(&mut self, mut gl: &glow::Context) { - #[cfg(feature = "image_rs")] + #[cfg(feature = "image")] self.raster_cache .borrow_mut() .trim(&mut self.storage, &mut gl); diff --git a/glow/src/lib.rs b/glow/src/lib.rs index daeb3e32..e3690a69 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -24,7 +24,7 @@ pub use glow; mod backend; -#[cfg(any(feature = "image_rs", feature = "svg"))] +#[cfg(any(feature = "image", feature = "svg"))] mod image; mod program; mod quad; diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index d7da84fa..e9509db2 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -9,8 +9,7 @@ repository = "https://github.com/iced-rs/iced" [features] svg = ["iced_graphics/svg"] -image = ["image_rs", "iced_graphics/image", "png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"] -image_rs = ["iced_graphics/image_rs"] +image = ["iced_graphics/image"] png = ["iced_graphics/png"] jpeg = ["iced_graphics/jpeg"] jpeg_rayon = ["iced_graphics/jpeg_rayon"] diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index e8a8efa7..946eb712 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -10,7 +10,7 @@ use iced_graphics::{Primitive, Viewport}; use iced_native::alignment; use iced_native::{Font, Size}; -#[cfg(any(feature = "image_rs", feature = "svg"))] +#[cfg(any(feature = "image", feature = "svg"))] use crate::image; /// A [`wgpu`] graphics backend for [`iced`]. @@ -23,7 +23,7 @@ pub struct Backend { text_pipeline: text::Pipeline, triangle_pipeline: triangle::Pipeline, - #[cfg(any(feature = "image_rs", feature = "svg"))] + #[cfg(any(feature = "image", feature = "svg"))] image_pipeline: image::Pipeline, default_text_size: u16, @@ -47,7 +47,7 @@ impl Backend { let triangle_pipeline = triangle::Pipeline::new(device, format, settings.antialiasing); - #[cfg(any(feature = "image_rs", feature = "svg"))] + #[cfg(any(feature = "image", feature = "svg"))] let image_pipeline = image::Pipeline::new(device, format); Self { @@ -55,7 +55,7 @@ impl Backend { text_pipeline, triangle_pipeline, - #[cfg(any(feature = "image_rs", feature = "svg"))] + #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, default_text_size: settings.default_text_size, @@ -98,7 +98,7 @@ impl Backend { ); } - #[cfg(any(feature = "image_rs", feature = "svg"))] + #[cfg(any(feature = "image", feature = "svg"))] self.image_pipeline.trim_cache(device, encoder); } @@ -148,7 +148,7 @@ impl Backend { ); } - #[cfg(any(feature = "image_rs", feature = "svg"))] + #[cfg(any(feature = "image", feature = "svg"))] { if !layer.images.is_empty() { let scaled = transformation @@ -294,7 +294,7 @@ impl backend::Text for Backend { } } -#[cfg(feature = "image_rs")] +#[cfg(feature = "image")] impl backend::Image for Backend { fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { self.image_pipeline.dimensions(handle) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index a4a422ce..d06815bb 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,6 +1,6 @@ mod atlas; -#[cfg(feature = "image_rs")] +#[cfg(feature = "image")] use iced_graphics::image::raster; #[cfg(feature = "svg")] @@ -17,7 +17,7 @@ use std::mem; use bytemuck::{Pod, Zeroable}; -#[cfg(feature = "image_rs")] +#[cfg(feature = "image")] use iced_native::image; #[cfg(feature = "svg")] @@ -25,7 +25,7 @@ use iced_native::svg; #[derive(Debug)] pub struct Pipeline { - #[cfg(feature = "image_rs")] + #[cfg(feature = "image")] raster_cache: RefCell>, #[cfg(feature = "svg")] vector_cache: RefCell>, @@ -243,7 +243,7 @@ impl Pipeline { }); Pipeline { - #[cfg(feature = "image_rs")] + #[cfg(feature = "image")] raster_cache: RefCell::new(raster::Cache::default()), #[cfg(feature = "svg")] @@ -262,7 +262,7 @@ impl Pipeline { } } - #[cfg(feature = "image_rs")] + #[cfg(feature = "image")] pub fn dimensions(&self, handle: &image::Handle) -> Size { let mut cache = self.raster_cache.borrow_mut(); let memory = cache.load(handle); @@ -291,7 +291,7 @@ impl Pipeline { ) { let instances: &mut Vec = &mut Vec::new(); - #[cfg(feature = "image_rs")] + #[cfg(feature = "image")] let mut raster_cache = self.raster_cache.borrow_mut(); #[cfg(feature = "svg")] @@ -299,7 +299,7 @@ impl Pipeline { for image in images { match &image { - #[cfg(feature = "image_rs")] + #[cfg(feature = "image")] layer::Image::Raster { handle, bounds } => { if let Some(atlas_entry) = raster_cache.upload( handle, @@ -314,7 +314,7 @@ impl Pipeline { ); } } - #[cfg(not(feature = "image_rs"))] + #[cfg(not(feature = "image"))] layer::Image::Raster { .. } => {} #[cfg(feature = "svg")] @@ -450,7 +450,7 @@ impl Pipeline { device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, ) { - #[cfg(feature = "image_rs")] + #[cfg(feature = "image")] self.raster_cache .borrow_mut() .trim(&mut self.texture_atlas, &mut (device, encoder)); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 1295516b..dcb699e8 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -56,7 +56,7 @@ pub use settings::Settings; pub(crate) use iced_graphics::Transformation; -#[cfg(any(feature = "image_rs", feature = "svg"))] +#[cfg(any(feature = "image", feature = "svg"))] mod image; /// A [`wgpu`] graphics renderer for [`iced`]. -- cgit From 7a24b4ba69e19459646648634c96d6426eaed462 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 5 Nov 2022 03:39:59 +0100 Subject: Replace `texture_store` and `store` with `storage` --- graphics/src/image/raster.rs | 10 +++++----- graphics/src/image/vector.rs | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/graphics/src/image/raster.rs b/graphics/src/image/raster.rs index bad017a9..da46c30f 100644 --- a/graphics/src/image/raster.rs +++ b/graphics/src/image/raster.rs @@ -12,7 +12,7 @@ use std::collections::{HashMap, HashSet}; pub enum Memory { /// Image data on host Host(::image_rs::ImageBuffer<::image_rs::Rgba, Vec>), - /// Texture store entry + /// Storage entry Device(T::Entry), /// Image not found NotFound, @@ -106,14 +106,14 @@ impl Cache { &mut self, handle: &image::Handle, state: &mut T::State<'_>, - store: &mut T, + storage: &mut T, ) -> Option<&T::Entry> { let memory = self.load(handle); if let Memory::Host(image) = memory { let (width, height) = image.dimensions(); - let entry = store.upload(width, height, image, state)?; + let entry = storage.upload(width, height, image, state)?; *memory = Memory::Device(entry); } @@ -126,7 +126,7 @@ impl Cache { } /// Trim cache misses from cache - pub fn trim(&mut self, store: &mut T, state: &mut T::State<'_>) { + pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) { let hits = &self.hits; self.map.retain(|k, memory| { @@ -134,7 +134,7 @@ impl Cache { if !retain { if let Memory::Device(entry) = memory { - store.remove(entry, state); + storage.remove(entry, state); } } diff --git a/graphics/src/image/vector.rs b/graphics/src/image/vector.rs index 818fdd20..dc271c9e 100644 --- a/graphics/src/image/vector.rs +++ b/graphics/src/image/vector.rs @@ -79,7 +79,7 @@ impl Cache { [width, height]: [f32; 2], scale: f32, state: &mut T::State<'_>, - texture_store: &mut T, + storage: &mut T, ) -> Option<&T::Entry> { let id = handle.id(); @@ -124,7 +124,7 @@ impl Cache { let mut rgba = img.take(); rgba.chunks_exact_mut(4).for_each(|rgba| rgba.swap(0, 2)); - let allocation = texture_store.upload( + let allocation = storage.upload( width, height, bytemuck::cast_slice(rgba.as_slice()), @@ -143,7 +143,7 @@ impl Cache { } /// Load svg and upload raster data - pub fn trim(&mut self, texture_store: &mut T, state: &mut T::State<'_>) { + pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) { let svg_hits = &self.svg_hits; let rasterized_hits = &self.rasterized_hits; @@ -152,7 +152,7 @@ impl Cache { let retain = rasterized_hits.contains(k); if !retain { - texture_store.remove(entry, state); + storage.remove(entry, state); } retain -- cgit From 078cadfed0e67560d9047d84435e87b8671c5992 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 5 Nov 2022 03:47:08 +0100 Subject: Update `image` dependency to `0.24` --- Cargo.toml | 2 +- graphics/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4801d7d..386eab48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,7 +104,7 @@ iced_glow = { version = "0.3", path = "glow", optional = true } thiserror = "1.0" [dependencies.image_rs] -version = "0.23" +version = "0.24" package = "image" optional = true diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 749770f4..57079b95 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -65,7 +65,7 @@ version = "0.10" optional = true [dependencies.image_rs] -version = "0.23" +version = "0.24" package = "image" default-features = false optional = true -- cgit From a250aab958b837ccf3a315617c39c7bd3e423c42 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 4 Nov 2022 23:25:01 -0700 Subject: Don't convert svg to BGRA before passing to shader Now that the shader is using RGBA, this incorrectly swaps the components. --- graphics/src/image/vector.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/graphics/src/image/vector.rs b/graphics/src/image/vector.rs index dc271c9e..42f4b500 100644 --- a/graphics/src/image/vector.rs +++ b/graphics/src/image/vector.rs @@ -121,15 +121,8 @@ impl Cache { img.as_mut(), )?; - let mut rgba = img.take(); - rgba.chunks_exact_mut(4).for_each(|rgba| rgba.swap(0, 2)); - - let allocation = storage.upload( - width, - height, - bytemuck::cast_slice(rgba.as_slice()), - state, - )?; + let allocation = + storage.upload(width, height, img.data(), state)?; log::debug!("allocating {} {}x{}", id, width, height); let _ = self.svg_hits.insert(id); -- cgit From 7476663069572adec25161b46c26570f864f736f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 8 Nov 2022 03:56:05 +0100 Subject: Rename `Padding::constrain` to `fit` --- core/src/padding.rs | 4 ++-- native/src/widget/button.rs | 2 +- native/src/widget/container.rs | 2 +- native/src/widget/text_input.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/padding.rs b/core/src/padding.rs index 64c95c89..ad5d1f0f 100644 --- a/core/src/padding.rs +++ b/core/src/padding.rs @@ -74,8 +74,8 @@ impl Padding { self.left + self.right } - /// Constrains the padding to fit between the inner & outer [`Size`] - pub fn constrain(self, inner: Size, outer: Size) -> Self { + /// Fits the [`Padding`] between the provided `inner` and `outer` [`Size`]. + pub fn fit(self, inner: Size, outer: Size) -> Self { let available = (outer - inner).max(Size::ZERO); Padding { diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index e927998c..01b528ec 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -430,7 +430,7 @@ pub fn layout( let mut content = layout_content(renderer, &limits.pad(padding)); - let padding = padding.constrain(content.size(), limits.max()); + let padding = padding.fit(content.size(), limits.max()); content.move_to(Point::new(padding.left.into(), padding.top.into())); diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index cc886dcb..1c060375 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -297,7 +297,7 @@ pub fn layout( let mut content = layout_content(renderer, &limits.pad(padding).loose()); - let padding = padding.constrain(content.size(), limits.max()); + let padding = padding.fit(content.size(), limits.max()); let size = limits.pad(padding).resolve(content.size()); diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 6ac4a2dd..a71c3b63 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -358,7 +358,7 @@ where let mut text = layout::Node::new(text_limits.resolve(Size::ZERO)); - let padding = padding.constrain(text.size(), limits.max()); + let padding = padding.fit(text.size(), limits.max()); text.move_to(Point::new(padding.left.into(), padding.top.into())); -- cgit From 914f0993428c752937d8db0a70a48f6f6f29c839 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 8 Nov 2022 04:04:01 +0100 Subject: Rearrange `layout` code to improve readability --- native/src/widget/button.rs | 4 +--- native/src/widget/container.rs | 2 -- native/src/widget/text_input.rs | 4 +--- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 01b528ec..1582188b 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -429,13 +429,11 @@ pub fn layout( let limits = limits.width(width).height(height); let mut content = layout_content(renderer, &limits.pad(padding)); - let padding = padding.fit(content.size(), limits.max()); + let size = limits.pad(padding).resolve(content.size()).pad(padding); content.move_to(Point::new(padding.left.into(), padding.top.into())); - let size = limits.pad(padding).resolve(content.size()).pad(padding); - layout::Node::with_children(size, vec![content]) } diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 1c060375..10a80b58 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -296,9 +296,7 @@ pub fn layout( .height(height); let mut content = layout_content(renderer, &limits.pad(padding).loose()); - let padding = padding.fit(content.size(), limits.max()); - let size = limits.pad(padding).resolve(content.size()); content.move_to(Point::new(padding.left.into(), padding.top.into())); diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index a71c3b63..dfc49a8d 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -357,13 +357,11 @@ where let limits = limits.width(width).height(Length::Shrink); let mut text = layout::Node::new(text_limits.resolve(Size::ZERO)); - let padding = padding.fit(text.size(), limits.max()); + let size = limits.pad(padding).resolve(text.size()).pad(padding); text.move_to(Point::new(padding.left.into(), padding.top.into())); - let size = limits.pad(padding).resolve(text.size()).pad(padding); - layout::Node::with_children(size, vec![text]) } -- cgit From 24d031b51c85507199b0e33e44c5a871882f6b32 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 8 Nov 2022 04:11:06 +0100 Subject: Cast to `u16` first then divide by `2` in `Padding::fit` --- core/src/padding.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/padding.rs b/core/src/padding.rs index ad5d1f0f..8d701f80 100644 --- a/core/src/padding.rs +++ b/core/src/padding.rs @@ -79,10 +79,10 @@ impl Padding { let available = (outer - inner).max(Size::ZERO); Padding { - top: self.top.min((available.height / 2.0) as u16), - right: self.right.min((available.width / 2.0) as u16), - bottom: self.bottom.min((available.height / 2.0) as u16), - left: self.left.min((available.width / 2.0) as u16), + top: self.top.min((available.height as u16) / 2), + right: self.right.min((available.width as u16) / 2), + bottom: self.bottom.min((available.height as u16) / 2), + left: self.left.min((available.width as u16) / 2), } } } -- cgit From 04087b2a867520cfc43a648fa9e93cee2a6daf3c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 8 Nov 2022 04:11:45 +0100 Subject: Remove redundant `std::convert` namespace in `padding` --- core/src/padding.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/padding.rs b/core/src/padding.rs index 8d701f80..140ad8ee 100644 --- a/core/src/padding.rs +++ b/core/src/padding.rs @@ -87,7 +87,7 @@ impl Padding { } } -impl std::convert::From for Padding { +impl From for Padding { fn from(p: u16) -> Self { Padding { top: p, @@ -98,7 +98,7 @@ impl std::convert::From for Padding { } } -impl std::convert::From<[u16; 2]> for Padding { +impl From<[u16; 2]> for Padding { fn from(p: [u16; 2]) -> Self { Padding { top: p[0], @@ -109,7 +109,7 @@ impl std::convert::From<[u16; 2]> for Padding { } } -impl std::convert::From<[u16; 4]> for Padding { +impl From<[u16; 4]> for Padding { fn from(p: [u16; 4]) -> Self { Padding { top: p[0], -- cgit From 7de9d2475dbf4ed93c4248580514901f82a0fc0e Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Tue, 8 Nov 2022 08:49:26 -0800 Subject: Couple layout & content to avoid desync --- native/src/widget/pane_grid.rs | 103 ++++++++++++++++++----------------- native/src/widget/pane_grid/state.rs | 20 +------ 2 files changed, 55 insertions(+), 68 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 8864da0c..fd771f8b 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -100,8 +100,7 @@ where Renderer: crate::Renderer, Renderer::Theme: StyleSheet + container::StyleSheet, { - state: state::Scoped<'a>, - contents: Contents>, + contents: Contents<'a, Content<'a, Message, Renderer>>, width: Length, height: Length, spacing: u16, @@ -124,32 +123,30 @@ where state: &'a State, view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Renderer>, ) -> Self { - let (contents, state) = if let Some((pane, pane_state)) = + let contents = if let Some((pane, pane_state)) = state.maximized.and_then(|pane| { state.panes.get(&pane).map(|pane_state| (pane, pane_state)) }) { - ( - Contents::Maximized(pane, view(pane, pane_state, true)), - state::Scoped::Maximized(Node::Pane(pane)), + Contents::Maximized( + pane, + view(pane, pane_state, true), + Node::Pane(pane), ) } else { - ( - Contents::All( - state - .panes - .iter() - .map(|(pane, pane_state)| { - (*pane, view(*pane, pane_state, false)) - }) - .collect(), - ), - state::Scoped::All(&state.internal), + Contents::All( + state + .panes + .iter() + .map(|(pane, pane_state)| { + (*pane, view(*pane, pane_state, false)) + }) + .collect(), + &state.internal, ) }; Self { contents, - state, width: Length::Fill, height: Length::Fill, spacing: 0, @@ -254,12 +251,12 @@ where fn diff(&self, tree: &mut Tree) { match &self.contents { - Contents::All(contents) => tree.diff_children_custom( + Contents::All(contents, _) => tree.diff_children_custom( contents, |state, (_, content)| content.diff(state), |(_, content)| content.state(), ), - Contents::Maximized(_, content) => tree.diff_children_custom( + Contents::Maximized(_, content, _) => tree.diff_children_custom( &[content], |state, content| content.diff(state), |content| content.state(), @@ -283,7 +280,7 @@ where layout( renderer, limits, - &self.state, + self.contents.layout(), self.width, self.height, self.spacing, @@ -312,7 +309,7 @@ where let event_status = update( action, - &self.state, + self.contents.layout(), &event, layout, cursor_position, @@ -357,7 +354,7 @@ where ) -> mouse::Interaction { mouse_interaction( tree.state.downcast_ref(), - &self.state, + self.contents.layout(), layout, cursor_position, self.spacing, @@ -395,7 +392,7 @@ where ) { draw( tree.state.downcast_ref(), - &self.state, + self.contents.layout(), layout, cursor_position, renderer, @@ -460,10 +457,10 @@ where } /// Calculates the [`Layout`] of a [`PaneGrid`]. -pub fn layout<'a, Renderer, T>( +pub fn layout( renderer: &Renderer, limits: &layout::Limits, - state: &state::Scoped<'a>, + node: &Node, width: Length, height: Length, spacing: u16, @@ -473,7 +470,7 @@ pub fn layout<'a, Renderer, T>( let limits = limits.width(width).height(height); let size = limits.resolve(Size::ZERO); - let regions = state.layout().pane_regions(f32::from(spacing), size); + let regions = node.pane_regions(f32::from(spacing), size); let children = contents .filter_map(|(pane, content)| { let region = regions.get(&pane)?; @@ -498,7 +495,7 @@ pub fn layout<'a, Renderer, T>( /// accordingly. pub fn update<'a, Message, T: Draggable>( action: &mut state::Action, - state: &state::Scoped<'a>, + node: &Node, event: &Event, layout: Layout<'_>, cursor_position: Point, @@ -526,7 +523,7 @@ pub fn update<'a, Message, T: Draggable>( cursor_position.y - bounds.y, ); - let splits = state.layout().split_regions( + let splits = node.split_regions( f32::from(spacing), Size::new(bounds.width, bounds.height), ); @@ -604,7 +601,7 @@ pub fn update<'a, Message, T: Draggable>( if let Some((split, _)) = action.picked_split() { let bounds = layout.bounds(); - let splits = state.layout().split_regions( + let splits = node.split_regions( f32::from(spacing), Size::new(bounds.width, bounds.height), ); @@ -674,9 +671,9 @@ fn click_pane<'a, Message, T>( } /// Returns the current [`mouse::Interaction`] of a [`PaneGrid`]. -pub fn mouse_interaction<'a>( +pub fn mouse_interaction( action: &state::Action, - state: &state::Scoped<'a>, + node: &Node, layout: Layout<'_>, cursor_position: Point, spacing: u16, @@ -691,9 +688,8 @@ pub fn mouse_interaction<'a>( resize_leeway.and_then(|leeway| { let bounds = layout.bounds(); - let splits = state - .layout() - .split_regions(f32::from(spacing), bounds.size()); + let splits = + node.split_regions(f32::from(spacing), bounds.size()); let relative_cursor = Point::new( cursor_position.x - bounds.x, @@ -720,9 +716,9 @@ pub fn mouse_interaction<'a>( } /// Draws a [`PaneGrid`]. -pub fn draw<'a, Renderer, T>( +pub fn draw( action: &state::Action, - state: &state::Scoped<'a>, + node: &Node, layout: Layout<'_>, cursor_position: Point, renderer: &mut Renderer, @@ -752,9 +748,7 @@ pub fn draw<'a, Renderer, T>( .and_then(|(split, axis)| { let bounds = layout.bounds(); - let splits = state - .layout() - .split_regions(f32::from(spacing), bounds.size()); + let splits = node.split_regions(f32::from(spacing), bounds.size()); let (_axis, region, ratio) = splits.get(&split)?; @@ -772,9 +766,8 @@ pub fn draw<'a, Renderer, T>( cursor_position.y - bounds.y, ); - let splits = state - .layout() - .split_regions(f32::from(spacing), bounds.size()); + let splits = + node.split_regions(f32::from(spacing), bounds.size()); let (_split, axis, region) = hovered_split( splits.iter(), @@ -938,21 +931,29 @@ fn hovered_split<'a>( /// The visible contents of the [`PaneGrid`] #[derive(Debug)] -pub enum Contents { +pub enum Contents<'a, T> { /// All panes are visible - All(Vec<(Pane, T)>), + All(Vec<(Pane, T)>, &'a state::Internal), /// A maximized pane is visible - Maximized(Pane, T), + Maximized(Pane, T, Node), } -impl Contents { +impl<'a, T> Contents<'a, T> { + /// Returns the layout [`Node`] of the [`Contents`] + pub fn layout(&self) -> &Node { + match self { + Contents::All(_, state) => state.layout(), + Contents::Maximized(_, _, layout) => layout, + } + } + /// Returns an iterator over the values of the [`Contents`] pub fn iter(&self) -> Box + '_> { match self { - Contents::All(contents) => Box::new( + Contents::All(contents, _) => Box::new( contents.iter().map(|(pane, content)| (*pane, content)), ), - Contents::Maximized(pane, content) => { + Contents::Maximized(pane, content, _) => { Box::new(std::iter::once((*pane, content))) } } @@ -960,10 +961,10 @@ impl Contents { fn iter_mut(&mut self) -> Box + '_> { match self { - Contents::All(contents) => Box::new( + Contents::All(contents, _) => Box::new( contents.iter_mut().map(|(pane, content)| (*pane, content)), ), - Contents::Maximized(pane, content) => { + Contents::Maximized(pane, content, _) => { Box::new(std::iter::once((*pane, content))) } } diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index c9e9433d..58397444 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -293,17 +293,6 @@ impl Internal { } } -/// The scoped internal state of the [`PaneGrid`] -/// -/// [`PaneGrid`]: crate::widget::PaneGrid -#[derive(Debug)] -pub enum Scoped<'a> { - /// The state when all panes are visible - All(&'a Internal), - /// The state when a pane is maximized - Maximized(Node), -} - /// The current action of a [`PaneGrid`]. /// /// [`PaneGrid`]: crate::widget::PaneGrid @@ -351,12 +340,9 @@ impl Action { } } -impl<'a> Scoped<'a> { - /// The layout [`Node`] of the [`Scoped`] state +impl Internal { + /// The layout [`Node`] of the [`Internal`] state pub fn layout(&self) -> &Node { - match self { - Scoped::All(Internal { layout, .. }) => layout, - Scoped::Maximized(layout) => layout, - } + &self.layout } } -- cgit From 9841d1938142cb453495a50e4a88059c6eae3074 Mon Sep 17 00:00:00 2001 From: bungoboingo Date: Tue, 8 Nov 2022 11:32:27 -0800 Subject: Fixed issues with old GL versions ( <= 2.1 ) --- examples/modern_art/Cargo.toml | 1 + examples/modern_art/src/main.rs | 2 ++ glow/src/shader/common/gradient.frag | 32 ++++++++++++++++---------------- glow/src/shader/common/triangle.frag | 2 +- glow/src/triangle/gradient.rs | 4 ++-- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/examples/modern_art/Cargo.toml b/examples/modern_art/Cargo.toml index a48361ae..4242d209 100644 --- a/examples/modern_art/Cargo.toml +++ b/examples/modern_art/Cargo.toml @@ -8,3 +8,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["canvas", "tokio", "debug"] } rand = "0.8.5" +env_logger = "0.9" diff --git a/examples/modern_art/src/main.rs b/examples/modern_art/src/main.rs index 0dd21c74..28ed3e21 100644 --- a/examples/modern_art/src/main.rs +++ b/examples/modern_art/src/main.rs @@ -9,6 +9,8 @@ use iced::{ use rand::{thread_rng, Rng}; fn main() -> iced::Result { + env_logger::builder().format_timestamp(None).init(); + ModernArt::run(Settings { antialiasing: true, ..Settings::default() diff --git a/glow/src/shader/common/gradient.frag b/glow/src/shader/common/gradient.frag index 42d0201f..9af0cb6e 100644 --- a/glow/src/shader/common/gradient.frag +++ b/glow/src/shader/common/gradient.frag @@ -1,20 +1,20 @@ #ifdef GL_ES - #ifdef GL_FRAGMENT_PRECISION_HIGH - precision highp float; - #else - precision mediump float; - #endif +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif #endif #ifdef HIGHER_THAN_300 - layout (location = 0) out vec4 fragColor; - #define gl_FragColor fragColor +layout (location = 0) out vec4 fragColor; +#define gl_FragColor fragColor #endif in vec2 raw_position; uniform vec4 gradient_direction; -uniform uint color_stops_size; +uniform int color_stops_size; // GLSL does not support dynamically sized arrays without SSBOs so this is capped to 16 stops //stored as color(vec4) -> offset(vec4) sequentially; uniform vec4 color_stops[32]; @@ -28,23 +28,23 @@ void main() { vec2 unit = normalize(gradient_vec); float coord_offset = dot(unit, current_vec) / length(gradient_vec); //if a gradient has a start/end stop that is identical, the mesh will have a transparent fill - fragColor = vec4(0.0, 0.0, 0.0, 0.0); + gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); float min_offset = color_stops[1].x; - float max_offset = color_stops[color_stops_size - 1u].x; + float max_offset = color_stops[color_stops_size - 1].x; - for (uint i = 0u; i < color_stops_size - 2u; i += 2u) { - float curr_offset = color_stops[i+1u].x; - float next_offset = color_stops[i+3u].x; + for (int i = 0; i < color_stops_size - 2; i += 2) { + float curr_offset = color_stops[i+1].x; + float next_offset = color_stops[i+3].x; if (coord_offset <= min_offset) { //current coordinate is before the first defined offset, set it to the start color - fragColor = color_stops[0]; + gl_FragColor = color_stops[0]; } if (curr_offset <= coord_offset && coord_offset <= next_offset) { //current fragment is between the current offset processing & the next one, interpolate colors - fragColor = mix(color_stops[i], color_stops[i+2u], smoothstep( + gl_FragColor = mix(color_stops[i], color_stops[i+2], smoothstep( curr_offset, next_offset, coord_offset @@ -53,7 +53,7 @@ void main() { if (coord_offset >= max_offset) { //current coordinate is before the last defined offset, set it to the last color - fragColor = color_stops[color_stops_size - 2u]; + gl_FragColor = color_stops[color_stops_size - 2]; } } } diff --git a/glow/src/shader/common/triangle.frag b/glow/src/shader/common/triangle.frag index ead40fe5..8260f6a6 100644 --- a/glow/src/shader/common/triangle.frag +++ b/glow/src/shader/common/triangle.frag @@ -14,5 +14,5 @@ out vec4 fragColor; uniform vec4 color; void main() { - fragColor = color; + gl_FragColor = color; } diff --git a/glow/src/triangle/gradient.rs b/glow/src/triangle/gradient.rs index 5225612e..d5f26877 100644 --- a/glow/src/triangle/gradient.rs +++ b/glow/src/triangle/gradient.rs @@ -71,14 +71,14 @@ impl Program { linear.end.y, ); - gl.uniform_1_u32( + gl.uniform_1_i32( Some( &self .uniform_data .uniform_locations .color_stops_size_location, ), - (linear.color_stops.len() * 2) as u32, + (linear.color_stops.len() * 2) as i32, ); let mut stops = [0.0; 128]; -- cgit From 18fb74f20092b2703a90afdb01f39754445998da Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 9 Nov 2022 04:05:31 +0100 Subject: Introduce `Custom` variants for every style in the built-in `Theme` --- examples/solar_system/src/main.rs | 12 +- native/src/overlay/menu.rs | 6 +- native/src/widget/button.rs | 4 +- native/src/widget/checkbox.rs | 4 +- native/src/widget/container.rs | 2 +- native/src/widget/helpers.rs | 9 +- native/src/widget/pane_grid.rs | 4 +- native/src/widget/pane_grid/content.rs | 2 +- native/src/widget/pane_grid/title_bar.rs | 2 +- native/src/widget/pick_list.rs | 36 +- native/src/widget/progress_bar.rs | 2 +- native/src/widget/radio.rs | 4 +- native/src/widget/rule.rs | 2 +- native/src/widget/scrollable.rs | 4 +- native/src/widget/slider.rs | 10 +- native/src/widget/text_input.rs | 6 +- native/src/widget/toggler.rs | 4 +- native/src/widget/tooltip.rs | 4 +- style/src/application.rs | 4 +- style/src/button.rs | 10 +- style/src/checkbox.rs | 6 +- style/src/container.rs | 4 +- style/src/menu.rs | 4 +- style/src/pane_grid.rs | 6 +- style/src/pick_list.rs | 15 +- style/src/progress_bar.rs | 4 +- style/src/radio.rs | 6 +- style/src/rule.rs | 4 +- style/src/scrollable.rs | 8 +- style/src/slider.rs | 8 +- style/src/text_input.rs | 14 +- style/src/theme.rs | 642 ++++++++++++++++++++++--------- style/src/toggler.rs | 6 +- winit/src/application/state.rs | 4 +- 34 files changed, 576 insertions(+), 286 deletions(-) diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 56787a99..9e303576 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -79,10 +79,14 @@ impl Application for SolarSystem { } fn style(&self) -> theme::Application { - theme::Application::Custom(|_theme| application::Appearance { - background_color: Color::BLACK, - text_color: Color::WHITE, - }) + fn dark_background(_theme: &Theme) -> application::Appearance { + application::Appearance { + background_color: Color::BLACK, + text_color: Color::WHITE, + } + } + + theme::Application::from(dark_background as fn(&Theme) -> _) } fn subscription(&self) -> Subscription { diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 08135872..3b55eba1 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -178,7 +178,7 @@ where font, text_size, padding, - style, + style: style.clone(), })); state.tree.diff(&container as &dyn Widget<_, _>); @@ -288,7 +288,7 @@ where layout: Layout<'_>, cursor_position: Point, ) { - let appearance = theme.appearance(self.style); + let appearance = theme.appearance(&self.style); let bounds = layout.bounds(); renderer.fill_quad( @@ -460,7 +460,7 @@ where _cursor_position: Point, viewport: &Rectangle, ) { - let appearance = theme.appearance(self.style); + let appearance = theme.appearance(&self.style); let bounds = layout.bounds(); let text_size = diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 1582188b..fa5da24b 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -231,7 +231,7 @@ where cursor_position, self.on_press.is_some(), theme, - self.style, + &self.style, || tree.state.downcast_ref::(), ); @@ -361,7 +361,7 @@ pub fn draw<'a, Renderer: crate::Renderer>( style_sheet: &dyn StyleSheet< Style = ::Style, >, - style: ::Style, + style: &::Style, state: impl FnOnce() -> &'a State, ) -> Appearance where diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index dc3c0bd0..77d639a9 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -224,9 +224,9 @@ where let mut children = layout.children(); let custom_style = if is_mouse_over { - theme.hovered(self.style, self.is_checked) + theme.hovered(&self.style, self.is_checked) } else { - theme.active(self.style, self.is_checked) + theme.active(&self.style, self.is_checked) }; { diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 10a80b58..16537c50 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -228,7 +228,7 @@ where cursor_position: Point, viewport: &Rectangle, ) { - let style = theme.appearance(self.style); + let style = theme.appearance(&self.style); draw_background(renderer, &style, layout.bounds()); diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index 79751878..fe6fb815 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -1,4 +1,5 @@ //! Helper functions to create pure widgets. +use crate::overlay; use crate::widget; use crate::{Element, Length}; @@ -84,6 +85,7 @@ pub fn button<'a, Message, Renderer>( where Renderer: crate::Renderer, Renderer::Theme: widget::button::StyleSheet, + ::Style: Default, { widget::Button::new(content) } @@ -208,7 +210,12 @@ where T: ToString + Eq + 'static, [T]: ToOwned>, Renderer: crate::text::Renderer, - Renderer::Theme: widget::pick_list::StyleSheet, + Renderer::Theme: widget::pick_list::StyleSheet + + widget::scrollable::StyleSheet + + overlay::menu::StyleSheet + + widget::container::StyleSheet, + ::Style: + From<::Style>, { widget::PickList::new(options, selected, on_selected) } diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index fd771f8b..ff902aa7 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -401,7 +401,7 @@ where viewport, self.spacing, self.on_resize.as_ref().map(|(leeway, _)| *leeway), - self.style, + &self.style, self.contents .iter() .zip(&tree.children) @@ -727,7 +727,7 @@ pub fn draw( viewport: &Rectangle, spacing: u16, resize_leeway: Option, - style: ::Style, + style: &::Style, contents: impl Iterator, draw_pane: impl Fn( T, diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index c236d820..405dc0b2 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -103,7 +103,7 @@ where let bounds = layout.bounds(); { - let style = theme.appearance(self.style); + let style = theme.appearance(&self.style); container::draw_background(renderer, &style, bounds); } diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index eb85f924..783a14c3 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -129,7 +129,7 @@ where use container::StyleSheet; let bounds = layout.bounds(); - let style = theme.appearance(self.style); + let style = theme.appearance(&self.style); let inherited_style = renderer::Style { text_color: style.text_color.unwrap_or(inherited_style.text_color), }; diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 896f5b35..a6459cd6 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -9,6 +9,8 @@ use crate::overlay::menu::{self, Menu}; use crate::renderer; use crate::text::{self, Text}; use crate::touch; +use crate::widget::container; +use crate::widget::scrollable; use crate::widget::tree::{self, Tree}; use crate::{ Clipboard, Element, Layout, Length, Padding, Point, Rectangle, Shell, Size, @@ -42,7 +44,12 @@ where T: ToString + Eq, [T]: ToOwned>, Renderer: text::Renderer, - Renderer::Theme: StyleSheet, + Renderer::Theme: StyleSheet + + scrollable::StyleSheet + + menu::StyleSheet + + container::StyleSheet, + ::Style: + From<::Style>, { /// The default padding of a [`PickList`]. pub const DEFAULT_PADDING: Padding = Padding::new(5); @@ -114,7 +121,12 @@ where [T]: ToOwned>, Message: 'a, Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet, + Renderer::Theme: StyleSheet + + scrollable::StyleSheet + + menu::StyleSheet + + container::StyleSheet, + ::Style: + From<::Style>, { fn tag(&self) -> tree::Tag { tree::Tag::of::>() @@ -202,7 +214,7 @@ where &self.font, self.placeholder.as_deref(), self.selected.as_ref(), - self.style, + &self.style, ) } @@ -221,7 +233,7 @@ where self.text_size, self.font.clone(), &self.options, - self.style, + self.style.clone(), ) } } @@ -233,7 +245,12 @@ where [T]: ToOwned>, Message: 'a, Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet, + Renderer::Theme: StyleSheet + + scrollable::StyleSheet + + menu::StyleSheet + + container::StyleSheet, + ::Style: + From<::Style>, { fn from(pick_list: PickList<'a, T, Message, Renderer>) -> Self { Self::new(pick_list) @@ -456,7 +473,12 @@ where T: Clone + ToString, Message: 'a, Renderer: text::Renderer + 'a, - Renderer::Theme: StyleSheet, + Renderer::Theme: StyleSheet + + scrollable::StyleSheet + + menu::StyleSheet + + container::StyleSheet, + ::Style: + From<::Style>, { if state.is_open { let bounds = layout.bounds(); @@ -493,7 +515,7 @@ pub fn draw( font: &Renderer::Font, placeholder: Option<&str>, selected: Option<&T>, - style: ::Style, + style: &::Style, ) where Renderer: text::Renderer, Renderer::Theme: StyleSheet, diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs index 8a945433..b053d959 100644 --- a/native/src/widget/progress_bar.rs +++ b/native/src/widget/progress_bar.rs @@ -124,7 +124,7 @@ where / (range_end - range_start) }; - let style = theme.appearance(self.style); + let style = theme.appearance(&self.style); renderer.fill_quad( renderer::Quad { diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index cb83f745..743689c7 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -230,9 +230,9 @@ where let mut children = layout.children(); let custom_style = if is_mouse_over { - theme.hovered(self.style, self.is_selected) + theme.hovered(&self.style, self.is_selected) } else { - theme.active(self.style, self.is_selected) + theme.active(&self.style, self.is_selected) }; { diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs index 56f8c80d..e44d8d99 100644 --- a/native/src/widget/rule.rs +++ b/native/src/widget/rule.rs @@ -88,7 +88,7 @@ where _viewport: &Rectangle, ) { let bounds = layout.bounds(); - let style = theme.style(self.style); + let style = theme.appearance(&self.style); let bounds = if self.is_horizontal { let line_y = (bounds.y + (bounds.height / 2.0) diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 4ebb07a0..b257cbe5 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -233,7 +233,7 @@ where self.scrollbar_width, self.scrollbar_margin, self.scroller_width, - self.style, + &self.style, |renderer, layout, cursor_position, viewport| { self.content.as_widget().draw( &tree.children[0], @@ -627,7 +627,7 @@ pub fn draw( scrollbar_width: u16, scrollbar_margin: u16, scroller_width: u16, - style: ::Style, + style: &::Style, draw_content: impl FnOnce(&mut Renderer, Layout<'_>, Point, &Rectangle), ) where Renderer: crate::Renderer, diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 585d9c35..92ed72e9 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -222,7 +222,7 @@ where self.value, &self.range, theme, - self.style, + &self.style, ) } @@ -353,7 +353,7 @@ pub fn draw( value: T, range: &RangeInclusive, style_sheet: &dyn StyleSheet