message: Implement allocating large images across multiple texture array layers.

diff:
@@ -9,7 +9,7 @@ use crate::image::raster::Memory;
use crate::Transformation;
use iced_native::{image, svg, Rectangle};
-use std::{collections::{HashMap, HashSet}, mem};
+use std::mem;
#[cfg(any(feature = "image", feature = "svg"))]
use std::cell::RefCell;
@@ -31,7 +31,7 @@ pub struct Pipeline {
instances: wgpu::Buffer,
constants: wgpu::BindGroup,
texture_layout: wgpu::BindGroupLayout,
- atlas_array: AtlasArray,
+ texture_array: TextureArray,
}
impl Pipeline {
@@ -217,7 +217,7 @@ impl Pipeline {
usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST,
});
- let atlas_array = AtlasArray::new(1, device);
+ let texture_array = TextureArray::new(device);
Pipeline {
#[cfg(feature = "image")]
@@ -233,7 +233,7 @@ impl Pipeline {
instances,
constants: constant_bind_group,
texture_layout,
- atlas_array,
+ texture_array,
}
}
@@ -259,8 +259,8 @@ impl Pipeline {
encoder: &mut wgpu::CommandEncoder,
instances: &[Image],
transformation: Transformation,
- bounds: Rectangle<u32>,
- target: &wgpu::TextureView,
+ _bounds: Rectangle<u32>,
+ _target: &wgpu::TextureView,
_scale: f32,
) {
let uniforms_buffer = device
@@ -277,25 +277,27 @@ impl Pipeline {
std::mem::size_of::<Uniforms>() as u64,
);
- #[cfg(any(feature = "image", feature = "svg"))]
- let mut recs = HashMap::new();
-
- for (index, image) in instances.iter().enumerate() {
+ for image in instances {
match &image.handle {
Handle::Raster(_handle) => {
#[cfg(feature = "image")]
{
let mut raster_cache = self.raster_cache.borrow_mut();
- if let Memory::Device { layer, allocation } = raster_cache.upload(
+ if let Memory::Device(allocation) = raster_cache.upload(
_handle,
device,
encoder,
- &mut self.atlas_array,
+ &mut self.texture_array,
) {
- let rec = (*layer, allocation.rectangle);
-
- let _ = recs.insert(index, rec);
+ self.draw_image(
+ device,
+ encoder,
+ image,
+ allocation,
+ _bounds,
+ _target,
+ );
}
}
}
@@ -305,49 +307,123 @@ impl Pipeline {
let mut vector_cache = self.vector_cache.borrow_mut();
// Upload rasterized svg to texture atlas
- if let Some((layer, allocation)) = vector_cache.upload(
+ if let Some(allocation) = vector_cache.upload(
_handle,
image.scale,
_scale,
device,
encoder,
- &mut self.atlas_array,
+ &mut self.texture_array,
) {
- let rec = (*layer, allocation.rectangle);
-
- let _ = recs.insert(index, rec);
+ self.draw_image(
+ device,
+ encoder,
+ image,
+ allocation,
+ _bounds,
+ _target,
+ );
+ }
}
}
}
}
}
+ pub fn trim_cache(&mut self) {
+ #[cfg(feature = "image")]
+ self.raster_cache.borrow_mut().trim(&mut self.texture_array);
+
+ #[cfg(feature = "svg")]
+ self.vector_cache.borrow_mut().trim(&mut self.texture_array);
+ }
+
+ fn draw_image(
+ &self,
+ device: &mut wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ image: &Image,
+ allocation: &ImageAllocation,
+ bounds: Rectangle<u32>,
+ target: &wgpu::TextureView,
+ ) {
let texture = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.texture_layout,
bindings: &[wgpu::Binding {
binding: 0,
resource: wgpu::BindingResource::TextureView(
- &self.atlas_array.texture().create_default_view(),
+ &self.texture_array.texture.create_default_view(),
),
}],
});
- #[cfg(any(feature = "image", feature = "svg"))]
- for (index, image) in instances.iter().enumerate() {
- if let Some((layer, rec)) = recs.get(&index) {
- let x = (rec.min.x as f32 + 0.5) / (ATLAS_SIZE as f32);
- let y = (rec.min.y as f32 + 0.5) / (ATLAS_SIZE as f32);
- let w = (rec.size().width as f32 - 0.5) / (ATLAS_SIZE as f32);
- let h = (rec.size().height as f32 - 0.5) / (ATLAS_SIZE as f32);
+ match allocation {
+ ImageAllocation::SingleAllocation(allocation) => {
+ self.draw_allocation(
+ device,
+ encoder,
+ image.position,
+ image.scale,
+ allocation,
+ &texture,
+ bounds,
+ target,
+ )
+ }
+ ImageAllocation::MultipleAllocations { mappings, size } => {
+ let scaling_x = image.scale[0] / size.0 as f32;
+ let scaling_y = image.scale[1] / size.1 as f32;
+
+ for mapping in mappings {
+ let mut position = image.position;
+ let mut scale = image.scale;
+
+ position[0] += mapping.src_pos.0 as f32 * scaling_x;
+ position[1] += mapping.src_pos.1 as f32 * scaling_y;
+ scale[0] = mapping.allocation.size().0 as f32 * scaling_x;
+ scale[1] = mapping.allocation.size().1 as f32 * scaling_y;
+
+ self.draw_allocation(
+ device,
+ encoder,
+ position,
+ scale,
+ &mapping.allocation,
+ &texture,
+ bounds,
+ target,
+ )
+ }
+ }
+ _ => {}
+ }
+ }
+
+ fn draw_allocation(
+ &self,
+ device: &mut wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ position: [f32; 2],
+ scale: [f32; 2],
+ allocation: &ArrayAllocation,
+ texture: &wgpu::BindGroup,
+ bounds: Rectangle<u32>,
+ target: &wgpu::TextureView,
+ ) {
+ let x = (allocation.position().0 as f32 + 0.5) / (ATLAS_SIZE as f32);
+ let y = (allocation.position().1 as f32 + 0.5) / (ATLAS_SIZE as f32);
+ let w = (allocation.size().0 as f32 - 0.5) / (ATLAS_SIZE as f32);
+ let h = (allocation.size().1 as f32 - 0.5) / (ATLAS_SIZE as f32);
+ let layer = allocation.layer() as f32;
let instance_buffer = device
.create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
.fill_from_slice(&[Instance {
- _position: image.position,
- _scale: image.scale,
+ _position: position,
+ _scale: scale,
_position_in_atlas: [x, y],
_scale_in_atlas: [w, h],
- _layer: *layer as f32,
+ _layer: layer,
}]);
encoder.copy_buffer_to_buffer(
@@ -400,16 +476,6 @@ impl Pipeline {
);
}
}
- }
-
- pub fn trim_cache(&mut self) {
- #[cfg(feature = "image")]
- self.raster_cache.borrow_mut().trim(&mut self.atlas_array);
-
- #[cfg(feature = "svg")]
- self.vector_cache.borrow_mut().trim(&mut self.atlas_array);
- }
-}
pub struct Image {
pub handle: Handle,
@@ -422,17 +488,96 @@ pub enum Handle {
Vector(svg::Handle),
}
+#[derive(Debug)]
+pub struct ArrayAllocationMapping {
+ src_pos: (u32, u32),
+ allocation: ArrayAllocation,
+}
+
+#[derive(Debug)]
+pub enum ImageAllocation {
+ SingleAllocation(ArrayAllocation),
+ MultipleAllocations {
+ mappings: Vec<ArrayAllocationMapping>,
+ size: (u32, u32),
+ },
+ Error,
+}
+
+impl ImageAllocation {
+ pub fn size(&self) -> (u32, u32) {
+ match self {
+ ImageAllocation::SingleAllocation(allocation) => {
+ allocation.size()
+ }
+ ImageAllocation::MultipleAllocations { size, .. } => {
+ *size
+ }
+ _ => (0, 0)
+ }
+ }
+}
+
#[derive(DebugStub)]
-pub struct AtlasArray {
- texture: wgpu::Texture,
+pub enum ArrayAllocation {
+ AtlasAllocation {
+ layer: usize,
#[debug_stub = "ReplacementValue"]
- allocators: HashMap<u32, AtlasAllocator>,
- layers_without_allocators: HashSet<u32>,
- size: u32,
+ allocation: Allocation,
+ },
+ WholeLayer {
+ layer: usize,
+ }
}
-impl AtlasArray {
- pub fn new(array_size: u32, device: &wgpu::Device) -> Self {
+impl ArrayAllocation {
+ pub fn size(&self) -> (u32, u32) {
+ match self {
+ ArrayAllocation::AtlasAllocation { allocation, .. } => {
+ let size = allocation.rectangle.size();
+ (size.width as u32, size.height as u32)
+ }
+ ArrayAllocation::WholeLayer { .. } => (ATLAS_SIZE, ATLAS_SIZE)
+ }
+ }
+
+ pub fn position(&self) -> (u32, u32) {
+ match self {
+ ArrayAllocation::AtlasAllocation { allocation, .. } => {
+ let min = &allocation.rectangle.min;
+ (min.x as u32, min.y as u32)
+ }
+ ArrayAllocation::WholeLayer { .. } => (0, 0)
+ }
+ }
+
+ pub fn layer(&self) -> usize {
+ match self {
+ ArrayAllocation::AtlasAllocation { layer, .. } => *layer,
+ ArrayAllocation::WholeLayer { layer } => *layer,
+ }
+ }
+}
+
+#[derive(DebugStub)]
+pub enum TextureLayer {
+ Whole,
+ Atlas(
+ #[debug_stub="ReplacementValue"]
+ AtlasAllocator
+ ),
+ Empty,
+}
+
+#[derive(Debug)]
+pub struct TextureArray {
+ texture: wgpu::Texture,
+ texture_array_size: u32,
+ layers: Vec<TextureLayer>,
+}
+
+impl TextureArray {
+ pub fn new(device: &wgpu::Device) -> Self {
let (width, height) = (ATLAS_SIZE, ATLAS_SIZE);
let extent = wgpu::Extent3d {
@@ -443,7 +588,7 @@ impl AtlasArray {
let texture = device.create_texture(&wgpu::TextureDescriptor {
size: extent,
- array_layer_count: array_size,
+ array_layer_count: 1,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
@@ -453,53 +598,217 @@ impl AtlasArray {
| wgpu::TextureUsage::SAMPLED,
});
- AtlasArray {
+ let size = Size::new(ATLAS_SIZE as i32, ATLAS_SIZE as i32);
+
+ TextureArray {
texture,
- allocators: HashMap::new(),
- layers_without_allocators: HashSet::new(),
- size: array_size,
+ texture_array_size: 1,
+ layers: vec!(TextureLayer::Atlas(AtlasAllocator::new(size))),
}
}
- pub fn texture(&self) -> &wgpu::Texture {
- &self.texture
+ pub fn allocate(&mut self, size: Size) -> ImageAllocation {
+ // Allocate one layer if allocation fits perfectly
+ if size.width == ATLAS_SIZE as i32 && size.height == ATLAS_SIZE as i32 {
+ for (i, layer) in &mut self.layers.iter_mut().enumerate() {
+ if let TextureLayer::Empty = layer
+ {
+ *layer = TextureLayer::Whole;
+ return ImageAllocation::SingleAllocation(
+ ArrayAllocation::WholeLayer { layer: i }
+ );
+ }
}
- pub fn allocate(&mut self, size: Size) -> Option<(u32, Allocation)> {
- for layer in 0..self.size {
- if self.layers_without_allocators.contains(&layer) {
- continue;
+ self.layers.push(TextureLayer::Whole);
+ return ImageAllocation::SingleAllocation(
+ ArrayAllocation::WholeLayer { layer: self.layers.len() - 1 }
+ );
}
- let allocator = self.allocators.entry(layer)
- .or_insert_with(|| AtlasAllocator::new(
- Size::new(ATLAS_SIZE as i32, ATLAS_SIZE as i32)
- ));
+ // Split big allocations across multiple layers
+ if size.width > ATLAS_SIZE as i32 || size.height > ATLAS_SIZE as i32 {
+ let mut mappings = Vec::new();
+
+ let mut y = 0;
+ while y < size.height {
+ let height = std::cmp::min(size.height - y, ATLAS_SIZE as i32);
+ let mut x = 0;
- if let Some(a) = allocator.allocate(size.clone()) {
- return Some((layer, a));
+ while x < size.width {
+ let width = std::cmp::min(size.width - x, ATLAS_SIZE as i32);
+ if let ImageAllocation::SingleAllocation(allocation) = self.allocate(Size::new(width, height)) {
+ let src_pos = (x as u32, y as u32);
+ mappings.push(ArrayAllocationMapping { src_pos, allocation });
}
+
+ x += width;
+ }
+ y += height;
}
- None
+ return ImageAllocation::MultipleAllocations {
+ mappings,
+ size: (size.width as u32, size.height as u32),
+ };
+ }
+
+ // Try allocating on an existing layer
+ for (i, layer) in self.layers.iter_mut().enumerate() {
+ if let TextureLayer::Atlas(allocator) = layer {
+ if let Some(allocation) = allocator.allocate(size.clone()) {
+ let array_allocation = ArrayAllocation::AtlasAllocation { layer: i, allocation };
+ return ImageAllocation::SingleAllocation(array_allocation);
+ }
+ }
}
- pub fn deallocate(&mut self, layer: u32, allocation: &Allocation) {
- if let Some(allocator) = self.allocators.get_mut(&layer) {
+ // Create new layer with atlas allocator
+ let mut allocator = AtlasAllocator::new(Size::new(ATLAS_SIZE as i32, ATLAS_SIZE as i32));
+ if let Some(allocation) = allocator.allocate(size) {
+ self.layers.push(TextureLayer::Atlas(allocator));
+
+ return ImageAllocation::SingleAllocation(
+ ArrayAllocation::AtlasAllocation {
+ layer: self.layers.len() - 1,
+ allocation,
+ }
+ );
+ }
+
+ // One of the above should have worked
+ ImageAllocation::Error
+ }
+
+ pub fn deallocate(&mut self, allocation: &ImageAllocation) {
+ match allocation {
+ ImageAllocation::SingleAllocation(allocation) => {
+ if let Some(layer) = self.layers.get_mut(allocation.layer()) {
+ match allocation {
+ ArrayAllocation::WholeLayer { .. } => {
+ *layer = TextureLayer::Empty;
+ }
+ ArrayAllocation::AtlasAllocation { allocation, .. } => {
+ if let TextureLayer::Atlas(allocator) = layer {
+ allocator.deallocate(allocation.id);
+ }
+ }
+ }
+ }
+ }
+ ImageAllocation::MultipleAllocations { mappings, .. } => {
+ for mapping in mappings {
+ if let Some(layer) = self.layers.get_mut(mapping.allocation.layer()) {
+ match &mapping.allocation {
+ ArrayAllocation::WholeLayer { .. } => {
+ *layer = TextureLayer::Empty;
+ }
+ ArrayAllocation::AtlasAllocation { allocation, .. } => {
+ if let TextureLayer::Atlas(allocator) = layer {
allocator.deallocate(allocation.id);
}
}
+ }
+ }
+ }
+ }
+ _ => {}
+ }
+
+ }
- pub fn upload<T: Copy + 'static>(
+ fn upload<C, I>(
&mut self,
- data: &[T],
- layer: u32,
- allocation: &guillotiere::Allocation,
+ image: &I,
+ allocation: &ImageAllocation,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
+ )
+ where
+ I: RawImageData<Chunk = C>,
+ C: Copy + 'static,
+ {
+ match allocation {
+ ImageAllocation::SingleAllocation(allocation) => {
+ let data = image.data();
+ let buffer = device
+ .create_buffer_mapped(
+ data.len(),
+ wgpu::BufferUsage::COPY_SRC,
+ )
+ .fill_from_slice(data);
+
+ if allocation.layer() >= self.texture_array_size as usize {
+ self.grow(1, device, encoder);
+ }
+
+ self.upload_texture(
+ &buffer,
+ allocation,
+ encoder,
+ );
+ }
+ ImageAllocation::MultipleAllocations { mappings, .. } => {
+ let chunks_per_pixel = 4 / std::mem::size_of::<C>();
+ let chunks_per_line = chunks_per_pixel * image.width() as usize;
+
+ for mapping in mappings {
+ let sub_width = mapping.allocation.size().0 as usize;
+ let sub_height = mapping.allocation.size().1 as usize;
+ let sub_line_start = mapping.src_pos.0 as usize * chunks_per_pixel;
+ let sub_line_end = (mapping.src_pos.0 as usize + sub_width) * chunks_per_pixel;
+
+ let mut sub_lines = image
+ .data()
+ .chunks(chunks_per_line)
+ .skip(mapping.src_pos.1 as usize)
+ .take(sub_height)
+ .map(|line| &line[sub_line_start..sub_line_end]);
+
+ let buffer = device
+ .create_buffer_mapped(
+ chunks_per_pixel * sub_width * sub_height,
+ wgpu::BufferUsage::COPY_SRC,
+ );
+
+ let mut buffer_lines = buffer.data.chunks_mut(sub_width * chunks_per_pixel);
+
+ while let (Some(buffer_line), Some(sub_line)) = (buffer_lines.next(), sub_lines.next()) {
+ buffer_line.copy_from_slice(sub_line);
+ }
+
+ let highest_layer = mappings
+ .iter()
+ .map(|m| m.allocation.layer() as u32)
+ .max()
+ .unwrap_or(0);
+
+ if highest_layer >= self.texture_array_size {
+ let grow_by = 1 + highest_layer - self.texture_array_size;
+ self.grow(grow_by, device, encoder);
+ }
+
+ self.upload_texture(
+ &buffer.finish(),
+ &mapping.allocation,
+ encoder,
+ );
+ }
+ }
+ _ => {}
+ }
+
+ }
+
+ fn upload_texture(
+ &mut self,
+ buffer: &wgpu::Buffer,
+ allocation: &ArrayAllocation,
+ encoder: &mut wgpu::CommandEncoder,
) {
- let size = allocation.rectangle.size();
- let (width, height) = (size.width as u32, size.height as u32);
+ let array_layer = allocation.layer() as u32;
+
+ let (width, height) = allocation.size();
let extent = wgpu::Extent3d {
width,
@@ -507,27 +816,22 @@ impl AtlasArray {
depth: 1,
};
- let temp_buf = device
- .create_buffer_mapped(
- data.len(),
- wgpu::BufferUsage::COPY_SRC,
- )
- .fill_from_slice(data);
+ let (x, y) = allocation.position();
encoder.copy_buffer_to_texture(
wgpu::BufferCopyView {
- buffer: &temp_buf,
+ buffer,
offset: 0,
row_pitch: 4 * width,
image_height: height,
},
wgpu::TextureCopyView {
texture: &self.texture,
- array_layer: layer as u32,
+ array_layer,
mip_level: 0,
origin: wgpu::Origin3d {
- x: allocation.rectangle.min.x as f32,
- y: allocation.rectangle.min.y as f32,
+ x: x as f32,
+ y: y as f32,
z: 0.0,
},
},
@@ -535,13 +839,17 @@ impl AtlasArray {
);
}
- pub fn grow(
+ fn grow(
&mut self,
grow_by: u32,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
) {
- let old_atlas_array_size = self.size;
+ if grow_by == 0 {
+ return;
+ }
+
+ let old_texture_array_size = self.texture_array_size;
let new_texture = device.create_texture(&wgpu::TextureDescriptor {
size: wgpu::Extent3d {
@@ -549,7 +857,7 @@ impl AtlasArray {
height: ATLAS_SIZE,
depth: 1,
},
- array_layer_count: old_atlas_array_size + grow_by,
+ array_layer_count: old_texture_array_size + grow_by,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
@@ -559,11 +867,10 @@ impl AtlasArray {
| wgpu::TextureUsage::SAMPLED,
});
- for i in 0..old_atlas_array_size {
encoder.copy_texture_to_texture(
wgpu::TextureCopyView {
texture: &self.texture,
- array_layer: i,
+ array_layer: 0,
mip_level: 0,
origin: wgpu::Origin3d {
x: 0.0,
@@ -573,7 +880,7 @@ impl AtlasArray {
},
wgpu::TextureCopyView {
texture: &new_texture,
- array_layer: i,
+ array_layer: 0,
mip_level: 0,
origin: wgpu::Origin3d {
x: 0.0,
@@ -584,15 +891,57 @@ impl AtlasArray {
wgpu::Extent3d {
width: ATLAS_SIZE,
height: ATLAS_SIZE,
- depth: 1,
+ depth: self.texture_array_size,
}
);
- }
+ self.texture_array_size += grow_by;
self.texture = new_texture;
}
}
+trait RawImageData {
+ type Chunk;
+
+ fn data(&self) -> &[Self::Chunk];
+ fn width(&self) -> u32;
+ fn height(&self) -> u32;
+}
+
+#[cfg(feature = "image")]
+impl RawImageData for ::image::ImageBuffer<::image::Bgra<u8>, Vec<u8>> {
+ type Chunk = u8;
+
+ fn data(&self) -> &[Self::Chunk] {
+ &self
+ }
+
+ fn width(&self) -> u32 {
+ self.dimensions().0
+ }
+
+ fn height(&self) -> u32 {
+ self.dimensions().1
+ }
+}
+
+#[cfg(feature = "svg")]
+impl RawImageData for resvg::raqote::DrawTarget {
+ type Chunk = u32;
+
+ fn data(&self) -> &[Self::Chunk] {
+ self.get_data()
+ }
+
+ fn width(&self) -> u32 {
+ self.width() as u32
+ }
+
+ fn height(&self) -> u32 {
+ self.height() as u32
+ }
+}
+
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Vertex {
@@ -616,7 +965,7 @@ const QUAD_VERTS: [Vertex; 4] = [
},
];
-const ATLAS_SIZE: u32 = 8192;
+const ATLAS_SIZE: u32 = 4096;
#[repr(C)]
#[derive(Clone, Copy)]

diff:
-use crate::image::AtlasArray;
+use crate::image::{TextureArray, ImageAllocation};
use iced_native::image;
use std::{
collections::{HashMap, HashSet},
};
-use guillotiere::{Allocation, Size};
+use guillotiere::Size;
use debug_stub_derive::*;
#[derive(DebugStub)]
pub enum Memory {
Host(::image::ImageBuffer<::image::Bgra<u8>, Vec<u8>>),
- Device {
- layer: u32,
- #[debug_stub="ReplacementValue"]
- allocation: Allocation,
- },
+ Device(ImageAllocation),
NotFound,
Invalid,
}
@@ -22,10 +18,7 @@ impl Memory {
pub fn dimensions(&self) -> (u32, u32) {
match self {
Memory::Host(image) => image.dimensions(),
- Memory::Device { allocation, .. } => {
- let size = &allocation.rectangle.size();
- (size.width as u32, size.height as u32)
- },
+ Memory::Device(allocation) => allocation.size(),
Memory::NotFound => (1, 1),
Memory::Invalid => (1, 1),
}
@@ -77,7 +70,7 @@ impl Cache {
handle: &image::Handle,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
- atlas_array: &mut AtlasArray,
+ atlas_array: &mut TextureArray,
) -> &Memory {
let _ = self.load(handle);
@@ -87,29 +80,23 @@ impl Cache {
let (width, height) = image.dimensions();
let size = Size::new(width as i32, height as i32);
- let (layer, allocation) = atlas_array.allocate(size).unwrap_or_else(|| {
- atlas_array.grow(1, device, encoder);
- atlas_array.allocate(size).unwrap()
- });
+ let allocation = atlas_array.allocate(size);
- let flat_samples = image.as_flat_samples();
- let slice = flat_samples.as_slice();
+ atlas_array.upload(image, &allocation, device, encoder);
- atlas_array.upload(slice, layer, &allocation, device, encoder);
-
- *memory = Memory::Device { layer, allocation };
+ *memory = Memory::Device(allocation);
}
memory
}
- pub fn trim(&mut self, atlas_array: &mut AtlasArray) {
+ pub fn trim(&mut self, texture_array: &mut TextureArray) {
let hits = &self.hits;
for (id, mem) in &self.map {
- if let Memory::Device { layer, allocation } = mem {
+ if let Memory::Device(allocation) = mem {
if !hits.contains(&id) {
- atlas_array.deallocate(*layer, allocation);
+ texture_array.deallocate(allocation);
}
}
}

diff:
-use crate::image::AtlasArray;
+use crate::image::{TextureArray, ImageAllocation};
use iced_native::svg;
use std::{
collections::{HashMap, HashSet},
};
-use guillotiere::{Allocation, Size};
+use guillotiere::Size;
use debug_stub_derive::*;
#[derive(DebugStub)]
@@ -32,7 +32,7 @@ impl Svg {
pub struct Cache {
svgs: HashMap<u64, Svg>,
#[debug_stub="ReplacementValue"]
- rasterized: HashMap<(u64, u32, u32), (u32, Allocation)>,
+ rasterized: HashMap<(u64, u32, u32), ImageAllocation>,
svg_hits: HashSet<u64>,
rasterized_hits: HashSet<(u64, u32, u32)>,
}
@@ -70,8 +70,8 @@ impl Cache {
scale: f32,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
- atlas_array: &mut AtlasArray,
- ) -> Option<&(u32, Allocation)> {
+ texture_array: &mut TextureArray,
+ ) -> Option<&ImageAllocation> {
let id = handle.id();
let (width, height) = (
@@ -100,10 +100,7 @@ impl Cache {
let size = Size::new(width as i32, height as i32);
- let (layer, allocation) = atlas_array.allocate(size).unwrap_or_else(|| {
- atlas_array.grow(1, device, encoder);
- atlas_array.allocate(size).unwrap()
- });
+ let array_allocation = texture_array.allocate(size);
// TODO: Optimize!
// We currently rerasterize the SVG when its size changes. This is slow
@@ -124,15 +121,13 @@ impl Cache {
&mut canvas,
);
- let slice = canvas.get_data();
-
- atlas_array.upload(slice, layer, &allocation, device, encoder);
+ texture_array.upload(&canvas, &array_allocation, device, encoder);
let _ = self.svg_hits.insert(id);
let _ = self.rasterized_hits.insert((id, width, height));
let _ = self
.rasterized
- .insert((id, width, height), (layer, allocation));
+ .insert((id, width, height), array_allocation);
self.rasterized.get(&(id, width, height))
}
@@ -140,13 +135,13 @@ impl Cache {
}
}
- pub fn trim(&mut self, atlas_array: &mut AtlasArray) {
+ pub fn trim(&mut self, texture_array: &mut TextureArray) {
let svg_hits = &self.svg_hits;
let rasterized_hits = &self.rasterized_hits;
- for (k, (layer, allocation)) in &self.rasterized {
+ for (k, allocation) in &self.rasterized {
if !rasterized_hits.contains(k) {
- atlas_array.deallocate(*layer, allocation);
+ texture_array.deallocate(allocation);
}
}


message: Batch image draw calls into one with multiple instances

change_type: MODIFY

diff:
@@ -28,7 +28,6 @@ pub struct Pipeline {
uniforms: wgpu::Buffer,
vertices: wgpu::Buffer,
indices: wgpu::Buffer,
- instances: wgpu::Buffer,
constants: wgpu::BindGroup,
texture_layout: wgpu::BindGroupLayout,
texture_array: TextureArray,
@@ -212,11 +211,6 @@ impl Pipeline {
.create_buffer_mapped(QUAD_INDICES.len(), wgpu::BufferUsage::INDEX)
.fill_from_slice(&QUAD_INDICES);
- let instances = device.create_buffer(&wgpu::BufferDescriptor {
- size: mem::size_of::<Instance>() as u64,
- usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST,
- });
-
let texture_array = TextureArray::new(device);
Pipeline {
@@ -230,7 +224,6 @@ impl Pipeline {
uniforms: uniforms_buffer,
vertices,
indices,
- instances,
constants: constant_bind_group,
texture_layout,
texture_array,
@@ -257,10 +250,10 @@ impl Pipeline {
&mut self,
device: &mut wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
- instances: &[Image],
+ images: &[Image],
transformation: Transformation,
- _bounds: Rectangle<u32>,
- _target: &wgpu::TextureView,
+ bounds: Rectangle<u32>,
+ target: &wgpu::TextureView,
_scale: f32,
) {
let uniforms_buffer = device
@@ -277,7 +270,9 @@ impl Pipeline {
std::mem::size_of::<Uniforms>() as u64,
);
- for image in instances {
+ let mut instances: Vec<Instance> = Vec::new();
+
+ for image in images {
match &image.handle {
Handle::Raster(_handle) => {
#[cfg(feature = "image")]
@@ -290,13 +285,10 @@ impl Pipeline {
encoder,
&mut self.texture_array,
) {
- self.draw_image(
- device,
- encoder,
+ add_instances(
image,
allocation,
- _bounds,
- _target,
+ &mut instances,
);
}
}
@@ -315,38 +307,17 @@ impl Pipeline {
encoder,
&mut self.texture_array,
) {
- self.draw_image(
- device,
- encoder,
+ add_instances(
image,
allocation,
- _bounds,
- _target,
+ &mut instances,
);
}
}
}
}
}
- }
-
- pub fn trim_cache(&mut self) {
- #[cfg(feature = "image")]
- self.raster_cache.borrow_mut().trim(&mut self.texture_array);
-
- #[cfg(feature = "svg")]
- self.vector_cache.borrow_mut().trim(&mut self.texture_array);
- }
- fn draw_image(
- &self,
- device: &mut wgpu::Device,
- encoder: &mut wgpu::CommandEncoder,
- image: &Image,
- allocation: &ImageAllocation,
- bounds: Rectangle<u32>,
- target: &wgpu::TextureView,
- ) {
let texture = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.texture_layout,
bindings: &[wgpu::Binding {
@@ -357,82 +328,10 @@ impl Pipeline {
}],
});
- match allocation {
- ImageAllocation::SingleAllocation(allocation) => {
- self.draw_allocation(
- device,
- encoder,
- image.position,
- image.scale,
- allocation,
- &texture,
- bounds,
- target,
- )
- }
- ImageAllocation::MultipleAllocations { mappings, size } => {
- let scaling_x = image.scale[0] / size.0 as f32;
- let scaling_y = image.scale[1] / size.1 as f32;
-
- for mapping in mappings {
- let mut position = image.position;
- let mut scale = image.scale;
-
- position[0] += mapping.src_pos.0 as f32 * scaling_x;
- position[1] += mapping.src_pos.1 as f32 * scaling_y;
- scale[0] = mapping.allocation.size().0 as f32 * scaling_x;
- scale[1] = mapping.allocation.size().1 as f32 * scaling_y;
-
- self.draw_allocation(
- device,
- encoder,
- position,
- scale,
- &mapping.allocation,
- &texture,
- bounds,
- target,
- )
- }
- }
- _ => {}
- }
- }
-
- fn draw_allocation(
- &self,
- device: &mut wgpu::Device,
- encoder: &mut wgpu::CommandEncoder,
- position: [f32; 2],
- scale: [f32; 2],
- allocation: &ArrayAllocation,
- texture: &wgpu::BindGroup,
- bounds: Rectangle<u32>,
- target: &wgpu::TextureView,
- ) {
- let x = (allocation.position().0 as f32 + 0.5) / (ATLAS_SIZE as f32);
- let y = (allocation.position().1 as f32 + 0.5) / (ATLAS_SIZE as f32);
- let w = (allocation.size().0 as f32 - 0.5) / (ATLAS_SIZE as f32);
- let h = (allocation.size().1 as f32 - 0.5) / (ATLAS_SIZE as f32);
- let layer = allocation.layer() as f32;
-
- let instance_buffer = device
- .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
- .fill_from_slice(&[Instance {
- _position: position,
- _scale: scale,
- _position_in_atlas: [x, y],
- _scale_in_atlas: [w, h],
- _layer: layer,
- }]);
-
- encoder.copy_buffer_to_buffer(
- &instance_buffer,
- 0,
- &self.instances,
- 0,
- mem::size_of::<Instance>() as u64,
- );
+ let instances_buffer = device.create_buffer_mapped(
+ instances.len(),
+ wgpu::BufferUsage::VERTEX,
+ ).fill_from_slice(&instances);
let mut render_pass = encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
@@ -460,8 +359,9 @@ impl Pipeline {
render_pass.set_index_buffer(&self.indices, 0);
render_pass.set_vertex_buffers(
0,
- &[(&self.vertices, 0), (&self.instances, 0)],
+ &[(&self.vertices, 0), (&instances_buffer, 0)],
);
+
render_pass.set_scissor_rect(
bounds.x,
bounds.y,
@@ -472,9 +372,69 @@ impl Pipeline {
render_pass.draw_indexed(
0..QUAD_INDICES.len() as u32,
0,
- 0..1 as u32,
+ 0..instances.len() as u32,
);
}
+
+ pub fn trim_cache(&mut self) {
+ #[cfg(feature = "image")]
+ self.raster_cache.borrow_mut().trim(&mut self.texture_array);
+
+ #[cfg(feature = "svg")]
+ self.vector_cache.borrow_mut().trim(&mut self.texture_array);
+ }
+}
+
+fn add_instances(
+ image: &Image,
+ allocation: &ImageAllocation,
+ instances: &mut Vec<Instance>,
+) {
+ match allocation {
+ ImageAllocation::SingleAllocation(allocation) => {
+ add_instance(image.position, image.scale, allocation, instances);
+ }
+ ImageAllocation::MultipleAllocations { mappings, size } => {
+ let scaling_x = image.scale[0] / size.0 as f32;
+ let scaling_y = image.scale[1] / size.1 as f32;
+
+ for mapping in mappings {
+ let allocation = &mapping.allocation;
+ let mut position = image.position;
+ let mut scale = image.scale;
+
+ position[0] += mapping.src_pos.0 as f32 * scaling_x;
+ position[1] += mapping.src_pos.1 as f32 * scaling_y;
+ scale[0] = allocation.size().0 as f32 * scaling_x;
+ scale[1] = allocation.size().1 as f32 * scaling_y;
+
+ add_instance(position, scale, allocation, instances);
+ }
+ }
+ }
+}
+
+fn add_instance(
+ position: [f32; 2],
+ scale: [f32; 2],
+ allocation: &ArrayAllocation,
+ instances: &mut Vec<Instance>,
+) {
+ let x = (allocation.position().0 as f32 + 0.5) / (ATLAS_SIZE as f32);
+ let y = (allocation.position().1 as f32 + 0.5) / (ATLAS_SIZE as f32);
+ let w = (allocation.size().0 as f32 - 0.5) / (ATLAS_SIZE as f32);
+ let h = (allocation.size().1 as f32 - 0.5) / (ATLAS_SIZE as f32);
+ let layer = allocation.layer() as f32;
+
+ let instance = Instance {
+ _position: position,
+ _scale: scale,
+ _position_in_atlas: [x, y],
+ _scale_in_atlas: [w, h],
+ _layer: layer,
+ };
+
+ instances.push(instance);
}
pub struct Image {
@@ -501,10 +461,10 @@ pub enum ImageAllocation {
mappings: Vec<ArrayAllocationMapping>,
size: (u32, u32),
},
- Error,
}
impl ImageAllocation {
+ #[cfg(feature = "image")]
pub fn size(&self) -> (u32, u32) {
match self {
ImageAllocation::SingleAllocation(allocation) => {
@@ -513,7 +473,6 @@ impl ImageAllocation {
ImageAllocation::MultipleAllocations { size, .. } => {
*size
}
- _ => (0, 0)
}
}
}
@@ -522,7 +481,7 @@ impl ImageAllocation {
pub enum ArrayAllocation {
AtlasAllocation {
layer: usize,
- #[debug_stub = "ReplacementValue"]
+ #[debug_stub = "Allocation"]
allocation: Allocation,
},
WholeLayer {
@@ -563,7 +522,7 @@ impl ArrayAllocation {
pub enum TextureLayer {
Whole,
Atlas(
- #[debug_stub="ReplacementValue"]
+ #[debug_stub="AtlasAllocator"]
AtlasAllocator
),
Empty,
@@ -577,7 +536,7 @@ pub struct TextureArray {
}
impl TextureArray {
- pub fn new(device: &wgpu::Device) -> Self {
+ fn new(device: &wgpu::Device) -> Self {
let (width, height) = (ATLAS_SIZE, ATLAS_SIZE);
let extent = wgpu::Extent3d {
@@ -598,32 +557,30 @@ impl TextureArray {
| wgpu::TextureUsage::SAMPLED,
});
- let size = Size::new(ATLAS_SIZE as i32, ATLAS_SIZE as i32);
-
TextureArray {
texture,
texture_array_size: 1,
- layers: vec!(TextureLayer::Atlas(AtlasAllocator::new(size))),
+ layers: vec!(TextureLayer::Empty),
}
}
- pub fn allocate(&mut self, size: Size) -> ImageAllocation {
+ fn allocate(&mut self, size: Size) -> Option<ImageAllocation> {
// Allocate one layer if allocation fits perfectly
if size.width == ATLAS_SIZE as i32 && size.height == ATLAS_SIZE as i32 {
- for (i, layer) in &mut self.layers.iter_mut().enumerate() {
+ for (i, layer) in self.layers.iter_mut().enumerate() {
if let TextureLayer::Empty = layer
{
*layer = TextureLayer::Whole;
- return ImageAllocation::SingleAllocation(
+ return Some(ImageAllocation::SingleAllocation(
ArrayAllocation::WholeLayer { layer: i }
- );
+ ));
}
}
self.layers.push(TextureLayer::Whole);
- return ImageAllocation::SingleAllocation(
+ return Some(ImageAllocation::SingleAllocation(
ArrayAllocation::WholeLayer { layer: self.layers.len() - 1 }
- );
+ ));
}
// Split big allocations across multiple layers
@@ -637,7 +594,11 @@ impl TextureArray {
while x < size.width {
let width = std::cmp::min(size.width - x, ATLAS_SIZE as i32);
- if let ImageAllocation::SingleAllocation(allocation) = self.allocate(Size::new(width, height)) {
+ let allocation = self
+ .allocate(Size::new(width, height))
+ .expect("Allocating texture space");
+
+ if let ImageAllocation::SingleAllocation(allocation) = allocation {
let src_pos = (x as u32, y as u32);
mappings.push(ArrayAllocationMapping { src_pos, allocation });
}
@@ -647,10 +608,10 @@ impl TextureArray {
y += height;
}
- return ImageAllocation::MultipleAllocations {
+ return Some(ImageAllocation::MultipleAllocations {
mappings,
size: (size.width as u32, size.height as u32),
- };
+ });
}
// Try allocating on an existing layer
@@ -658,7 +619,7 @@ impl TextureArray {
if let TextureLayer::Atlas(allocator) = layer {
if let Some(allocation) = allocator.allocate(size.clone()) {
let array_allocation = ArrayAllocation::AtlasAllocation { layer: i, allocation };
- return ImageAllocation::SingleAllocation(array_allocation);
+ return Some(ImageAllocation::SingleAllocation(array_allocation));
}
}
}
@@ -668,19 +629,19 @@ impl TextureArray {
if let Some(allocation) = allocator.allocate(size) {
self.layers.push(TextureLayer::Atlas(allocator));
- return ImageAllocation::SingleAllocation(
+ return Some(ImageAllocation::SingleAllocation(
ArrayAllocation::AtlasAllocation {
layer: self.layers.len() - 1,
allocation,
}
- );
+ ));
}
// One of the above should have worked
- ImageAllocation::Error
+ None
}
- pub fn deallocate(&mut self, allocation: &ImageAllocation) {
+ fn deallocate(&mut self, allocation: &ImageAllocation) {
match allocation {
ImageAllocation::SingleAllocation(allocation) => {
if let Some(layer) = self.layers.get_mut(allocation.layer()) {
@@ -712,7 +673,6 @@ impl TextureArray {
}
}
}
- _ => {}
}
}
@@ -720,15 +680,17 @@ impl TextureArray {
fn upload<C, I>(
&mut self,
image: &I,
- allocation: &ImageAllocation,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
- )
+ ) -> ImageAllocation
where
I: RawImageData<Chunk = C>,
C: Copy + 'static,
{
- match allocation {
+ let size = Size::new(image.width() as i32, image.height() as i32);
+ let allocation = self.allocate(size).expect("Allocating texture space");
+
+ match &allocation {
ImageAllocation::SingleAllocation(allocation) => {
let data = image.data();
let buffer = device
@@ -744,7 +706,7 @@ impl TextureArray {
self.upload_texture(
&buffer,
- allocation,
+ &allocation,
encoder,
);
}
@@ -752,6 +714,17 @@ impl TextureArray {
let chunks_per_pixel = 4 / std::mem::size_of::<C>();
let chunks_per_line = chunks_per_pixel * image.width() as usize;
+ let highest_layer = mappings
+ .iter()
+ .map(|m| m.allocation.layer() as u32)
+ .max()
+ .unwrap_or(0);
+
+ if highest_layer >= self.texture_array_size {
+ let grow_by = 1 + highest_layer - self.texture_array_size;
+ self.grow(grow_by, device, encoder);
+ }
+
for mapping in mappings {
let sub_width = mapping.allocation.size().0 as usize;
let sub_height = mapping.allocation.size().1 as usize;
@@ -777,17 +750,6 @@ impl TextureArray {
buffer_line.copy_from_slice(sub_line);
}
- let highest_layer = mappings
- .iter()
- .map(|m| m.allocation.layer() as u32)
- .max()
- .unwrap_or(0);
-
- if highest_layer >= self.texture_array_size {
- let grow_by = 1 + highest_layer - self.texture_array_size;
- self.grow(grow_by, device, encoder);
- }
-
self.upload_texture(
&buffer.finish(),
&mapping.allocation,
@@ -795,9 +757,9 @@ impl TextureArray {
);
}
}
- _ => {}
}
+ allocation
}
fn upload_texture(
@@ -867,10 +829,19 @@ impl TextureArray {
| wgpu::TextureUsage::SAMPLED,
});
+ for (i, layer) in self.layers.iter().enumerate() {
+ if i >= old_texture_array_size as usize {
+ break;
+ }
+
+ if let TextureLayer::Empty = layer {
+ continue;
+ }
+
encoder.copy_texture_to_texture(
wgpu::TextureCopyView {
texture: &self.texture,
- array_layer: 0,
+ array_layer: i as u32,
mip_level: 0,
origin: wgpu::Origin3d {
x: 0.0,
@@ -880,7 +851,7 @@ impl TextureArray {
},
wgpu::TextureCopyView {
texture: &new_texture,
- array_layer: 0,
+ array_layer: i as u32,
mip_level: 0,
origin: wgpu::Origin3d {
x: 0.0,
@@ -891,9 +862,10 @@ impl TextureArray {
wgpu::Extent3d {
width: ATLAS_SIZE,
height: ATLAS_SIZE,
- depth: self.texture_array_size,
+ depth: 1,
}
);
+ }
self.texture_array_size += grow_by;
self.texture = new_texture;
@@ -968,7 +940,7 @@ const QUAD_VERTS: [Vertex; 4] = [
const ATLAS_SIZE: u32 = 4096;
#[repr(C)]
-#[derive(Clone, Copy)]
+#[derive(Debug, Clone, Copy)]
struct Instance {
_position: [f32; 2],
_scale: [f32; 2],

diff:
@@ -3,7 +3,6 @@ use iced_native::image;
use std::{
collections::{HashMap, HashSet},
};
-use guillotiere::Size;
use debug_stub_derive::*;
#[derive(DebugStub)]
@@ -72,17 +71,10 @@ impl Cache {
encoder: &mut wgpu::CommandEncoder,
atlas_array: &mut TextureArray,
) -> &Memory {
- let _ = self.load(handle);
-
- let memory = self.map.get_mut(&handle.id()).unwrap();
+ let memory = self.load(handle);
if let Memory::Host(image) = memory {
- let (width, height) = image.dimensions();
- let size = Size::new(width as i32, height as i32);
-
- let allocation = atlas_array.allocate(size);
-
- atlas_array.upload(image, &allocation, device, encoder);
+ let allocation = atlas_array.upload(image, device, encoder);
*memory = Memory::Device(allocation);
}

diff:
@@ -3,7 +3,6 @@ use iced_native::svg;
use std::{
collections::{HashMap, HashSet},
};
-use guillotiere::Size;
use debug_stub_derive::*;
#[derive(DebugStub)]
@@ -83,25 +82,19 @@ impl Cache {
// 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.get(&(id, width, height)).is_some() {
+ 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));
}
- let _ = self.load(handle);
-
- match self.svgs.get(&handle.id()).unwrap() {
+ match self.load(handle) {
Svg::Loaded(tree) => {
if width == 0 || height == 0 {
return None;
}
- let size = Size::new(width as i32, height as i32);
-
- let array_allocation = texture_array.allocate(size);
-
// TODO: Optimize!
// We currently rerasterize the SVG when its size changes. This is slow
// as heck. A GPU rasterizer like `pathfinder` may perform better.
@@ -121,13 +114,13 @@ impl Cache {
&mut canvas,
);
- texture_array.upload(&canvas, &array_allocation, device, encoder);
+ let allocation = texture_array.upload(&canvas, device, encoder);
let _ = self.svg_hits.insert(id);
let _ = self.rasterized_hits.insert((id, width, height));
let _ = self
.rasterized
- .insert((id, width, height), array_allocation);
+ .insert((id, width, height), allocation);
self.rasterized.get(&(id, width, height))
}

license: MIT License

repo: iced-rs/iced


message: Updated shaders and removed debug_stub_derive dependency

diff:
@@ -22,7 +22,6 @@ glam = "0.8"
font-kit = "0.4"
log = "0.4"
guillotiere = "0.4"
-debug_stub_derive = "0.3"
[dependencies.image]
version = "0.22"

diff:
@@ -15,7 +15,6 @@ use std::mem;
use std::cell::RefCell;
use guillotiere::{Allocation, AtlasAllocator, Size};
-use debug_stub_derive::*;
#[derive(Debug)]
pub struct Pipeline {
@@ -477,11 +476,9 @@ impl ImageAllocation {
}
}
-#[derive(DebugStub)]
pub enum ArrayAllocation {
AtlasAllocation {
layer: usize,
- #[debug_stub = "Allocation"]
allocation: Allocation,
},
WholeLayer {
@@ -518,16 +515,35 @@ impl ArrayAllocation {
}
}
-#[derive(DebugStub)]
+impl std::fmt::Debug for ArrayAllocation {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ArrayAllocation::AtlasAllocation { layer, .. } => {
+ write!(f, "ArrayAllocation::AtlasAllocation {{ layer: {} }}", layer)
+ },
+ ArrayAllocation::WholeLayer { layer } => {
+ write!(f, "ArrayAllocation::WholeLayer {{ layer: {} }}", layer)
+ }
+ }
+ }
+}
+
pub enum TextureLayer {
Whole,
- Atlas(
- #[debug_stub="AtlasAllocator"]
- AtlasAllocator
- ),
+ Atlas(AtlasAllocator),
Empty,
}
+impl std::fmt::Debug for TextureLayer {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ TextureLayer::Whole => write!(f, "TextureLayer::Whole"),
+ TextureLayer::Atlas(_) => write!(f, "TextureLayer::Atlas"),
+ TextureLayer::Empty => write!(f, "TextureLayer::Empty"),
+ }
+ }
+}
+
#[derive(Debug)]
pub struct TextureArray {
texture: wgpu::Texture,

diff:
@@ -3,9 +3,7 @@ use iced_native::image;
use std::{
collections::{HashMap, HashSet},
};
-use debug_stub_derive::*;
-#[derive(DebugStub)]
pub enum Memory {
Host(::image::ImageBuffer<::image::Bgra<u8>, Vec<u8>>),
Device(ImageAllocation),
@@ -24,6 +22,17 @@ impl Memory {
}
}
+impl std::fmt::Debug for Memory {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Memory::Host(_) => write!(f, "Memory::Host"),
+ Memory::Device(_) => write!(f, "Memory::Device"),
+ Memory::NotFound => write!(f, "Memory::NotFound"),
+ Memory::Invalid => write!(f, "Memory::Invalid"),
+ }
+ }
+}
+
#[derive(Debug)]
pub struct Cache {
map: HashMap<u64, Memory>,

diff:
@@ -3,14 +3,9 @@ use iced_native::svg;
use std::{
collections::{HashMap, HashSet},
};
-use debug_stub_derive::*;
-#[derive(DebugStub)]
pub enum Svg {
- Loaded(
- #[debug_stub="ReplacementValue"]
- resvg::usvg::Tree
- ),
+ Loaded(resvg::usvg::Tree),
NotFound,
}
@@ -27,10 +22,17 @@ impl Svg {
}
}
-#[derive(DebugStub)]
+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"),
+ }
+ }
+}
+
pub struct Cache {
svgs: HashMap<u64, Svg>,
- #[debug_stub="ReplacementValue"]
rasterized: HashMap<(u64, u32, u32), ImageAllocation>,
svg_hits: HashSet<u64>,
rasterized_hits: HashSet<(u64, u32, u32)>,
@@ -144,3 +146,9 @@ impl Cache {
self.rasterized_hits.clear();
}
}
+
+impl std::fmt::Debug for Cache {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "vector::Cache")
+ }
+}
\ No newline at end of file

diff: Binary files a/wgpu/src/shader/image.frag.spv and b/wgpu/src/shader/image.frag.spv differ

diff: Binary files a/wgpu/src/shader/image.vert.spv and b/wgpu/src/shader/image.vert.spv differ


message: When deallocating the last allocation in an allocator mark its layer as empty

diff:
@@ -660,38 +660,37 @@ impl TextureArray {
fn deallocate(&mut self, allocation: &ImageAllocation) {
match allocation {
ImageAllocation::SingleAllocation(allocation) => {
- if let Some(layer) = self.layers.get_mut(allocation.layer()) {
- match allocation {
- ArrayAllocation::WholeLayer { .. } => {
- *layer = TextureLayer::Empty;
- }
- ArrayAllocation::AtlasAllocation { allocation, .. } => {
- if let TextureLayer::Atlas(allocator) = layer {
- allocator.deallocate(allocation.id);
+ self.deallocate_single_allocation(allocation);
}
+ ImageAllocation::MultipleAllocations { mappings, .. } => {
+ for mapping in mappings {
+ self.deallocate_single_allocation(&mapping.allocation);
}
}
}
}
- ImageAllocation::MultipleAllocations { mappings, .. } => {
- for mapping in mappings {
- if let Some(layer) = self.layers.get_mut(mapping.allocation.layer()) {
- match &mapping.allocation {
+
+ fn deallocate_single_allocation(&mut self, allocation: &ArrayAllocation) {
+ if let Some(layer) = self.layers.get_mut(allocation.layer()) {
+ match allocation {
ArrayAllocation::WholeLayer { .. } => {
*layer = TextureLayer::Empty;
}
ArrayAllocation::AtlasAllocation { allocation, .. } => {
if let TextureLayer::Atlas(allocator) = layer {
allocator.deallocate(allocation.id);
+
+ let mut empty_allocator = true;
+ allocator.for_each_allocated_rectangle(|_, _| empty_allocator = false);
+
+ if empty_allocator {
+ *layer = TextureLayer::Empty;
}
}
}
}
}
}
- }
-
- }
fn upload<C, I>(
&mut self,
@@ -953,7 +952,7 @@ const QUAD_VERTS: [Vertex; 4] = [
},
];
-const ATLAS_SIZE: u32 = 4096;
+const ATLAS_SIZE: u32 = 256;
#[repr(C)]
#[derive(Debug, Clone, Copy)]


message: Implemented automatic deallocation of texture space for dropped allocations

diff:
@@ -9,10 +9,7 @@ use crate::image::raster::Memory;
use crate::Transformation;
use iced_native::{image, svg, Rectangle};
-use std::mem;
-
-#[cfg(any(feature = "image", feature = "svg"))]
-use std::cell::RefCell;
+use std::{cell::RefCell, mem, rc::Rc};
use guillotiere::{Allocation, AtlasAllocator, Size};
@@ -377,10 +374,10 @@ impl Pipeline {
pub fn trim_cache(&mut self) {
#[cfg(feature = "image")]
- self.raster_cache.borrow_mut().trim(&mut self.texture_array);
+ self.raster_cache.borrow_mut().trim();
#[cfg(feature = "svg")]
- self.vector_cache.borrow_mut().trim(&mut self.texture_array);
+ self.vector_cache.borrow_mut().trim();
}
}
@@ -423,14 +420,14 @@ fn add_instance(
let y = (allocation.position().1 as f32 + 0.5) / (ATLAS_SIZE as f32);
let w = (allocation.size().0 as f32 - 0.5) / (ATLAS_SIZE as f32);
let h = (allocation.size().1 as f32 - 0.5) / (ATLAS_SIZE as f32);
- let layer = allocation.layer() as f32;
+ let layer_index = allocation.layer_index() as f32;
let instance = Instance {
_position: position,
_scale: scale,
_position_in_atlas: [x, y],
_scale_in_atlas: [w, h],
- _layer: layer,
+ _layer: layer_index,
};
instances.push(instance);
@@ -478,11 +475,13 @@ impl ImageAllocation {
pub enum ArrayAllocation {
AtlasAllocation {
- layer: usize,
+ layer_index: usize,
+ layer: Rc<RefCell<TextureLayer>>,
allocation: Allocation,
},
WholeLayer {
- layer: usize,
+ layer_index: usize,
+ layer: Rc<RefCell<TextureLayer>>,
}
}
@@ -507,10 +506,10 @@ impl ArrayAllocation {
}
}
- pub fn layer(&self) -> usize {
+ pub fn layer_index(&self) -> usize {
match self {
- ArrayAllocation::AtlasAllocation { layer, .. } => *layer,
- ArrayAllocation::WholeLayer { layer } => *layer,
+ ArrayAllocation::AtlasAllocation { layer_index, .. } => *layer_index,
+ ArrayAllocation::WholeLayer { layer_index, .. } => *layer_index,
}
}
}
@@ -518,11 +517,34 @@ impl ArrayAllocation {
impl std::fmt::Debug for ArrayAllocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
- ArrayAllocation::AtlasAllocation { layer, .. } => {
- write!(f, "ArrayAllocation::AtlasAllocation {{ layer: {} }}", layer)
+ ArrayAllocation::AtlasAllocation { layer_index, .. } => {
+ write!(f, "ArrayAllocation::AtlasAllocation {{ layer_index: {:} }}", layer_index)
},
- ArrayAllocation::WholeLayer { layer } => {
- write!(f, "ArrayAllocation::WholeLayer {{ layer: {} }}", layer)
+ ArrayAllocation::WholeLayer { layer_index, .. } => {
+ write!(f, "ArrayAllocation::WholeLayer {{ layer_index: {} }}", layer_index)
+ }
+ }
+ }
+}
+
+impl Drop for ArrayAllocation {
+ fn drop(&mut self) {
+ match self {
+ ArrayAllocation::WholeLayer { layer, .. } => {
+ let _ = layer.replace(TextureLayer::Whole);
+ }
+ ArrayAllocation::AtlasAllocation { allocation, layer, .. } => {
+ let mut layer = layer.borrow_mut();
+ if let Some(allocator) = layer.allocator_mut() {
+ allocator.deallocate(allocation.id);
+
+ let mut empty_allocator = true;
+ allocator.for_each_allocated_rectangle(|_, _| empty_allocator = false);
+
+ if empty_allocator {
+ *layer = TextureLayer::Empty;
+ }
+ }
}
}
}
@@ -534,6 +556,23 @@ pub enum TextureLayer {
Empty,
}
+impl TextureLayer {
+ pub fn is_empty(&self) -> bool {
+ if let TextureLayer::Empty = self {
+ true
+ } else {
+ false
+ }
+ }
+
+ pub fn allocator_mut(&mut self) -> Option<&mut AtlasAllocator> {
+ match self {
+ TextureLayer::Atlas(allocator) => Some(allocator),
+ _ => None
+ }
+ }
+}
+
impl std::fmt::Debug for TextureLayer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@@ -544,11 +583,17 @@ impl std::fmt::Debug for TextureLayer {
}
}
+impl From<AtlasAllocator> for TextureLayer {
+ fn from(allocator: AtlasAllocator) -> Self {
+ TextureLayer::Atlas(allocator)
+ }
+}
+
#[derive(Debug)]
pub struct TextureArray {
texture: wgpu::Texture,
texture_array_size: u32,
- layers: Vec<TextureLayer>,
+ layers: Vec<Rc<RefCell<TextureLayer>>>,
}
impl TextureArray {
@@ -576,7 +621,7 @@ impl TextureArray {
TextureArray {
texture,
texture_array_size: 1,
- layers: vec!(TextureLayer::Empty),
+ layers: vec!(Rc::new(RefCell::new(TextureLayer::Empty))),
}
}
@@ -584,18 +629,19 @@ impl TextureArray {
// Allocate one layer if allocation fits perfectly
if size.width == ATLAS_SIZE as i32 && size.height == ATLAS_SIZE as i32 {
for (i, layer) in self.layers.iter_mut().enumerate() {
- if let TextureLayer::Empty = layer
+ if layer.borrow().is_empty()
{
- *layer = TextureLayer::Whole;
+ let _ = layer.replace(TextureLayer::Whole);
return Some(ImageAllocation::SingleAllocation(
- ArrayAllocation::WholeLayer { layer: i }
+ ArrayAllocation::WholeLayer { layer: layer.clone(), layer_index: i }
));
}
}
- self.layers.push(TextureLayer::Whole);
+ let layer = Rc::new(RefCell::new(TextureLayer::Whole));
+ self.layers.push(layer.clone());
return Some(ImageAllocation::SingleAllocation(
- ArrayAllocation::WholeLayer { layer: self.layers.len() - 1 }
+ ArrayAllocation::WholeLayer { layer, layer_index: self.layers.len() - 1 }
));
}
@@ -632,9 +678,13 @@ impl TextureArray {
// Try allocating on an existing layer
for (i, layer) in self.layers.iter_mut().enumerate() {
- if let TextureLayer::Atlas(allocator) = layer {
+ if let Some(allocator) = layer.borrow_mut().allocator_mut() {
if let Some(allocation) = allocator.allocate(size.clone()) {
- let array_allocation = ArrayAllocation::AtlasAllocation { layer: i, allocation };
+ let array_allocation = ArrayAllocation::AtlasAllocation {
+ layer: layer.clone(),
+ layer_index: i,
+ allocation
+ };
return Some(ImageAllocation::SingleAllocation(array_allocation));
}
}
@@ -643,11 +693,13 @@ impl TextureArray {
// Create new layer with atlas allocator
let mut allocator = AtlasAllocator::new(Size::new(ATLAS_SIZE as i32, ATLAS_SIZE as i32));
if let Some(allocation) = allocator.allocate(size) {
- self.layers.push(TextureLayer::Atlas(allocator));
+ let layer = Rc::new(RefCell::new(allocator.into()));
+ self.layers.push(layer.clone());
return Some(ImageAllocation::SingleAllocation(
ArrayAllocation::AtlasAllocation {
- layer: self.layers.len() - 1,
+ layer,
+ layer_index: self.layers.len() - 1,
allocation,
}
));
@@ -657,41 +709,6 @@ impl TextureArray {
None
}
- fn deallocate(&mut self, allocation: &ImageAllocation) {
- match allocation {
- ImageAllocation::SingleAllocation(allocation) => {
- self.deallocate_single_allocation(allocation);
- }
- ImageAllocation::MultipleAllocations { mappings, .. } => {
- for mapping in mappings {
- self.deallocate_single_allocation(&mapping.allocation);
- }
- }
- }
- }
-
- fn deallocate_single_allocation(&mut self, allocation: &ArrayAllocation) {
- if let Some(layer) = self.layers.get_mut(allocation.layer()) {
- match allocation {
- ArrayAllocation::WholeLayer { .. } => {
- *layer = TextureLayer::Empty;
- }
- ArrayAllocation::AtlasAllocation { allocation, .. } => {
- if let TextureLayer::Atlas(allocator) = layer {
- allocator.deallocate(allocation.id);
-
- let mut empty_allocator = true;
- allocator.for_each_allocated_rectangle(|_, _| empty_allocator = false);
-
- if empty_allocator {
- *layer = TextureLayer::Empty;
- }
- }
- }
- }
- }
- }
-
fn upload<C, I>(
&mut self,
image: &I,
@@ -715,7 +732,7 @@ impl TextureArray {
)
.fill_from_slice(data);
- if allocation.layer() >= self.texture_array_size as usize {
+ if allocation.layer_index() >= self.texture_array_size as usize {
self.grow(1, device, encoder);
}
@@ -731,7 +748,7 @@ impl TextureArray {
let highest_layer = mappings
.iter()
- .map(|m| m.allocation.layer() as u32)
+ .map(|m| m.allocation.layer_index() as u32)
.max()
.unwrap_or(0);
@@ -783,7 +800,7 @@ impl TextureArray {
allocation: &ArrayAllocation,
encoder: &mut wgpu::CommandEncoder,
) {
- let array_layer = allocation.layer() as u32;
+ let array_layer = allocation.layer_index() as u32;
let (width, height) = allocation.size();
@@ -844,12 +861,12 @@ impl TextureArray {
| wgpu::TextureUsage::SAMPLED,
});
- for (i, layer) in self.layers.iter().enumerate() {
+ for (i, layer) in self.layers.iter_mut().enumerate() {
if i >= old_texture_array_size as usize {
break;
}
- if let TextureLayer::Empty = layer {
+ if layer.borrow().is_empty() {
continue;
}
@@ -952,7 +969,7 @@ const QUAD_VERTS: [Vertex; 4] = [
},
];
-const ATLAS_SIZE: u32 = 256;
+const ATLAS_SIZE: u32 = 4096;
#[repr(C)]
#[derive(Debug, Clone, Copy)]

diff:
@@ -91,17 +91,9 @@ impl Cache {
memory
}
- pub fn trim(&mut self, texture_array: &mut TextureArray) {
+ pub fn trim(&mut self) {
let hits = &self.hits;
- for (id, mem) in &self.map {
- if let Memory::Device(allocation) = mem {
- if !hits.contains(&id) {
- texture_array.deallocate(allocation);
- }
- }
- }
-
self.map.retain(|k, _| hits.contains(k));
self.hits.clear();
}

diff:
@@ -130,16 +130,10 @@ impl Cache {
}
}
- pub fn trim(&mut self, texture_array: &mut TextureArray) {
+ pub fn trim(&mut self) {
let svg_hits = &self.svg_hits;
let rasterized_hits = &self.rasterized_hits;
- for (k, allocation) in &self.rasterized {
- if !rasterized_hits.contains(k) {
- texture_array.deallocate(allocation);
- }
- }
-
self.svgs.retain(|k, _| svg_hits.contains(k));
self.rasterized.retain(|k, _| rasterized_hits.contains(k));
self.svg_hits.clear();


message: Implement From<Color> for Option<Background>

diff:
@@ -13,3 +13,9 @@ impl From<Color> for Background {
Background::Color(color)
}
}
+
+impl From<Color> for Option<Background> {
+ fn from(color: Color) -> Self {
+ Some(Background::from(color))
+ }
+}
\ No newline at end of file


message:
Update styling example
Also run `cargo fmt`

diff:
@@ -241,16 +241,14 @@ mod style {
}
mod light {
- use iced::{button, Background, Color, Vector};
+ use iced::{button, Color, Vector};
pub struct Button;
impl button::StyleSheet for Button {
fn active(&self) -> button::Style {
button::Style {
- background: Some(Background::Color(Color::from_rgb(
- 0.11, 0.42, 0.87,
- ))),
+ background: Color::from_rgb(0.11, 0.42, 0.87).into(),
border_radius: 12,
shadow_offset: Vector::new(1.0, 1.0),
text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE),
@@ -271,7 +269,7 @@ mod style {
mod dark {
use iced::{
button, checkbox, container, progress_bar, radio, rule, scrollable,
- slider, text_input, Background, Color,
+ slider, text_input, Color,
};
const SURFACE: Color = Color::from_rgb(
@@ -303,10 +301,8 @@ mod style {
impl container::StyleSheet for Container {
fn style(&self) -> container::Style {
container::Style {
- background: Some(Background::Color(Color::from_rgb8(
- 0x36, 0x39, 0x3F,
- ))),
- text_color: Some(Color::WHITE),
+ background: Color::from_rgb8(0x36, 0x39, 0x3F).into(),
+ text_color: Color::WHITE.into(),
..container::Style::default()
}
}
@@ -317,7 +313,7 @@ mod style {
impl radio::StyleSheet for Radio {
fn active(&self) -> radio::Style {
radio::Style {
- background: Background::Color(SURFACE),
+ background: SURFACE.into(),
dot_color: ACTIVE,
border_width: 1,
border_color: ACTIVE,
@@ -326,7 +322,7 @@ mod style {
fn hovered(&self) -> radio::Style {
radio::Style {
- background: Background::Color(Color { a: 0.5, ..SURFACE }),
+ background: Color { a: 0.5, ..SURFACE }.into(),
..self.active()
}
}
@@ -337,7 +333,7 @@ mod style {
impl text_input::StyleSheet for TextInput {
fn active(&self) -> text_input::Style {
text_input::Style {
- background: Background::Color(SURFACE),
+ background: SURFACE.into(),
border_radius: 2,
border_width: 0,
border_color: Color::TRANSPARENT,
@@ -378,7 +374,7 @@ mod style {
impl button::StyleSheet for Button {
fn active(&self) -> button::Style {
button::Style {
- background: Some(Background::Color(ACTIVE)),
+ background: ACTIVE.into(),
border_radius: 3,
text_color: Color::WHITE,
..button::Style::default()
@@ -387,7 +383,7 @@ mod style {
fn hovered(&self) -> button::Style {
button::Style {
- background: Some(Background::Color(HOVERED)),
+ background: HOVERED.into(),
text_color: Color::WHITE,
..self.active()
}
@@ -407,7 +403,7 @@ mod style {
impl scrollable::StyleSheet for Scrollable {
fn active(&self) -> scrollable::Scrollbar {
scrollable::Scrollbar {
- background: Some(Background::Color(SURFACE)),
+ background: SURFACE.into(),
border_radius: 2,
border_width: 0,
border_color: Color::TRANSPARENT,
@@ -424,10 +420,7 @@ mod style {
let active = self.active();
scrollable::Scrollbar {
- background: Some(Background::Color(Color {
- a: 0.5,
- ..SURFACE
- })),
+ background: Color { a: 0.5, ..SURFACE }.into(),
scroller: scrollable::Scroller {
color: HOVERED,
..active.scroller
@@ -494,8 +487,8 @@ mod style {
impl progress_bar::StyleSheet for ProgressBar {
fn style(&self) -> progress_bar::Style {
progress_bar::Style {
- background: Background::Color(SURFACE),
- bar: Background::Color(ACTIVE),
+ background: SURFACE.into(),
+ bar: ACTIVE.into(),
border_radius: 10,
}
}
@@ -506,11 +499,8 @@ mod style {
impl checkbox::StyleSheet for Checkbox {
fn active(&self, is_checked: bool) -> checkbox::Style {
checkbox::Style {
- background: Background::Color(if is_checked {
- ACTIVE
- } else {
- SURFACE
- }),
+ background: if is_checked { ACTIVE } else { SURFACE }
+ .into(),
checkmark_color: Color::WHITE,
border_radius: 2,
border_width: 1,
@@ -520,10 +510,11 @@ mod style {
fn hovered(&self, is_checked: bool) -> checkbox::Style {
checkbox::Style {
- background: Background::Color(Color {
+ background: Color {
a: 0.8,
..if is_checked { ACTIVE } else { SURFACE }
- }),
+ }
+ .into(),
..self.active(is_checked)
}
}


message: Add trait for embedded web applications

diff:
@@ -260,3 +260,168 @@ where
.finish()
}
}
+
+/// An interactive embedded web application.
+///
+/// This trait is the main entrypoint of Iced. Once implemented, you can run
+/// your GUI application by simply calling [`run`](#method.run). It will either
+/// take control of the `<body>' or of an HTML element of the document specified
+/// by `container_id`.
+///
+/// An [`Embedded`](trait.Embedded.html) can execute asynchronous actions
+/// by returning a [`Command`](struct.Command.html) in some of its methods.
+pub trait Embedded {
+ /// The [`Executor`] that will run commands and subscriptions.
+ ///
+ /// The [`executor::WasmBindgen`] can be a good choice for the Web.
+ ///
+ /// [`Executor`]: trait.Executor.html
+ /// [`executor::Default`]: executor/struct.Default.html
+ type Executor: Executor;
+
+ /// The type of __messages__ your [`Embedded`] application will produce.
+ ///
+ /// [`Embedded`]: trait.Embedded.html
+ type Message: Send;
+
+ /// The data needed to initialize your [`Embedded`] application.
+ ///
+ /// [`Embedded`]: trait.Embedded.html
+ type Flags;
+
+ /// Initializes the [`Embedded`] application.
+ ///
+ /// Here is where you should return the initial state of your app.
+ ///
+ /// Additionally, you can return a [`Command`](struct.Command.html) if you
+ /// need to perform some async action in the background on startup. This is
+ /// useful if you want to load state from a file, perform an initial HTTP
+ /// request, etc.
+ ///
+ /// [`Embedded`]: trait.Embedded.html
+ fn new(flags: Self::Flags) -> (Self, Command<Self::Message>)
+ where
+ Self: Sized;
+
+ /// Handles a __message__ and updates the state of the [`Embedded`]
+ /// application.
+ ///
+ /// This is where you define your __update logic__. All the __messages__,
+ /// produced by either user interactions or commands, will be handled by
+ /// this method.
+ ///
+ /// Any [`Command`] returned will be executed immediately in the background.
+ ///
+ /// [`Embedded`]: trait.Embedded.html
+ /// [`Command`]: struct.Command.html
+ fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
+
+ /// Returns the widgets to display in the [`Embedded`] application.
+ ///
+ /// These widgets can produce __messages__ based on user interaction.
+ ///
+ /// [`Embedded`]: trait.Embedded.html
+ fn view(&mut self) -> Element<'_, Self::Message>;
+
+ /// Returns the event [`Subscription`] for the current state of the embedded
+ /// application.
+ ///
+ /// A [`Subscription`] will be kept alive as long as you keep returning it,
+ /// and the __messages__ produced will be handled by
+ /// [`update`](#tymethod.update).
+ ///
+ /// By default, this method returns an empty [`Subscription`].
+ ///
+ /// [`Subscription`]: struct.Subscription.html
+ fn subscription(&self) -> Subscription<Self::Message> {
+ Subscription::none()
+ }
+
+ /// Runs the [`Embedded`] application.
+ ///
+ /// [`Embedded`]: trait.Embedded.html
+ fn run(flags: Self::Flags, container_id: Option<String>)
+ where
+ Self: 'static + Sized,
+ {
+ use futures::stream::StreamExt;
+ use wasm_bindgen::JsCast;
+ use web_sys::HtmlElement;
+
+ let window = web_sys::window().unwrap();
+ let document = window.document().unwrap();
+ let container: HtmlElement = container_id
+ .map(|id| document.get_element_by_id(&id).unwrap())
+ .map(|container| {
+ container.dyn_ref::<HtmlElement>().unwrap().to_owned()
+ })
+ .unwrap_or_else(|| document.body().unwrap());
+
+ let (sender, receiver) =
+ iced_futures::futures::channel::mpsc::unbounded();
+
+ let mut runtime = iced_futures::Runtime::new(
+ Self::Executor::new().expect("Create executor"),
+ sender.clone(),
+ );
+
+ let (app, command) = runtime.enter(|| Self::new(flags));
+
+ runtime.spawn(command);
+
+ let application = Rc::new(RefCell::new(app));
+
+ let instance = EmbeddedInstance {
+ application: application.clone(),
+ bus: Bus::new(sender),
+ };
+
+ let vdom = dodrio::Vdom::new(&container, instance);
+
+ let event_loop = receiver.for_each(move |message| {
+ let (command, subscription) = runtime.enter(|| {
+ let command = application.borrow_mut().update(message);
+ let subscription = application.borrow().subscription();
+
+ (command, subscription)
+ });
+
+ runtime.spawn(command);
+ runtime.track(subscription);
+
+ vdom.weak().schedule_render();
+
+ futures::future::ready(())
+ });
+
+ wasm_bindgen_futures::spawn_local(event_loop);
+ }
+}
+
+struct EmbeddedInstance<A: Embedded> {
+ application: Rc<RefCell<A>>,
+ bus: Bus<A::Message>,
+}
+
+impl<'a, A> dodrio::Render<'a> for EmbeddedInstance<A>
+where
+ A: Embedded,
+{
+ fn render(
+ &self,
+ context: &mut dodrio::RenderContext<'a>,
+ ) -> dodrio::Node<'a> {
+ use dodrio::builder::*;
+
+ let mut ui = self.application.borrow_mut();
+ let element = ui.view();
+ let mut css = Css::new();
+
+ let node = element.widget.node(context.bump, &self.bus, &mut css);
+
+ div(context.bump)
+ .attr("style", "width: 100%; height: 100%")
+ .children(vec![css.node(context.bump), node])
+ .finish()
+ }
+}

original_message: Add trait for embedded web applications


message: Implement `Toggler` widget for iced_native

diff:
@@ -20,6 +20,7 @@ pub mod scrollable;
pub mod slider;
pub mod svg;
pub mod text_input;
+pub mod toggler;
pub mod tooltip;
mod column;
@@ -50,6 +51,8 @@ pub use slider::Slider;
#[doc(no_inline)]
pub use text_input::TextInput;
#[doc(no_inline)]
+pub use toggler::Toggler;
+#[doc(no_inline)]
pub use tooltip::Tooltip;
pub use column::Column;

change_type: ADD

diff:
+//! Show toggle controls using togglers.
+use crate::backend::{self, Backend};
+use crate::{Primitive, Renderer};
+use iced_native::mouse;
+use iced_native::toggler;
+use iced_native::Rectangle;
+
+pub use iced_style::toggler::{Style, StyleSheet};
+
+/// Makes sure that the border radius of the toggler looks good at every size.
+const BORDER_RADIUS_RATIO: f32 = 32.0 / 13.0;
+
+/// The space ratio between the background Quad and the Toggler bounds, and
+/// between the background Quad and foreground Quad.
+const SPACE_RATIO: f32 = 0.05;
+
+/// A toggler that can be toggled.
+///
+/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
+pub type Toggler<Message, Backend> =
+ iced_native::Toggler<Message, Renderer<Backend>>;
+
+impl<B> toggler::Renderer for Renderer<B>
+where
+ B: Backend + backend::Text,
+{
+ type Style = Box<dyn StyleSheet>;
+
+ const DEFAULT_SIZE: u16 = 20;
+
+ fn draw(
+ &mut self,
+ bounds: Rectangle,
+ is_active: bool,
+ is_mouse_over: bool,
+ (label, _): Self::Output,
+ style_sheet: &Self::Style,
+ ) -> Self::Output {
+ let style = if is_mouse_over {
+ style_sheet.hovered(is_active)
+ } else {
+ style_sheet.active(is_active)
+ };
+
+ let border_radius = bounds.height as f32 / BORDER_RADIUS_RATIO;
+ let space = SPACE_RATIO * bounds.height as f32;
+
+ let toggler_background_bounds = Rectangle {
+ x: bounds.x + space,
+ y: bounds.y + space,
+ width: bounds.width - (2.0 * space),
+ height: bounds.height - (2.0 * space),
+ };
+
+ let toggler_background = Primitive::Quad {
+ bounds: toggler_background_bounds,
+ background: style.background.into(),
+ border_radius,
+ border_width: 1.0,
+ border_color: style.background_border.unwrap_or(style.background),
+ };
+
+ let toggler_foreground_bounds = Rectangle {
+ x: bounds.x
+ + if is_active {
+ bounds.width - 2.0 * space - (bounds.height - (4.0 * space))
+ } else {
+ 2.0 * space
+ },
+ y: bounds.y + (2.0 * space),
+ width: bounds.height - (4.0 * space),
+ height: bounds.height - (4.0 * space),
+ };
+
+ let toggler_foreground = Primitive::Quad {
+ bounds: toggler_foreground_bounds,
+ background: style.foreground.into(),
+ border_radius,
+ border_width: 1.0,
+ border_color: style.foreground_border.unwrap_or(style.foreground),
+ };
+
+ (
+ Primitive::Group {
+ primitives: vec![label, toggler_background, toggler_foreground],
+ },
+ if is_mouse_over {
+ mouse::Interaction::Pointer
+ } else {
+ mouse::Interaction::default()
+ },
+ )
+ }
+}

diff:
use crate::{
button, checkbox, column, container, pane_grid, progress_bar, radio, row,
- scrollable, slider, text, text_input, Color, Element, Font,
+ scrollable, slider, text, text_input, toggler, Color, Element, Font,
HorizontalAlignment, Layout, Padding, Point, Rectangle, Renderer, Size,
VerticalAlignment,
};
@@ -288,3 +288,19 @@ impl pane_grid::Renderer for Null {
) {
}
}
+
+impl toggler::Renderer for Null {
+ type Style = ();
+
+ const DEFAULT_SIZE: u16 = 20;
+
+ fn draw(
+ &mut self,
+ _bounds: Rectangle,
+ _is_checked: bool,
+ _is_mouse_over: bool,
+ _label: Self::Output,
+ _style: &Self::Style,
+ ) {
+ }
+}

diff:
@@ -36,6 +36,7 @@ pub mod space;
pub mod svg;
pub mod text;
pub mod text_input;
+pub mod toggler;
pub mod tooltip;
#[doc(no_inline)]
@@ -73,6 +74,8 @@ pub use text::Text;
#[doc(no_inline)]
pub use text_input::TextInput;
#[doc(no_inline)]
+pub use toggler::Toggler;
+#[doc(no_inline)]
pub use tooltip::Tooltip;
use crate::event::{self, Event};

diff:
+//! Show toggle controls using togglers.
+use std::hash::Hash;
+
+use crate::{
+ event, layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher,
+ HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
+ VerticalAlignment, Widget,
+};
+
+/// A toggler widget
+///
+/// # Example
+///
+/// ```
+/// # type Toggler<Message> = iced_native::Toggler<Message, iced_native::renderer::Null>;
+/// #
+/// pub enum Message {
+/// TogglerToggled(bool),
+/// }
+///
+/// let is_active = true;
+///
+/// Toggler::new(is_active, "Toggle me!", |b| Message::TogglerToggled(b))
+/// ```
+///
+#[allow(missing_debug_implementations)]
+pub struct Toggler<Message, Renderer: self::Renderer + text::Renderer> {
+ is_active: bool,
+ on_toggle: Box<dyn Fn(bool) -> Message>,
+ label: String,
+ width: Length,
+ size: u16,
+ text_size: Option<u16>,
+ font: Renderer::Font,
+ style: Renderer::Style,
+}
+
+impl<Message, Renderer: self::Renderer + text::Renderer>
+ Toggler<Message, Renderer>
+{
+ /// Creates a new [`Toggler`].
+ ///
+ /// It expects:
+ /// * a boolean describing whether the [`Toggler`] is checked or not
+ /// * the label of the [`Toggler`]
+ /// * a function that will be called when the [`Toggler`] is toggled. It
+ /// will receive the new state of the [`Toggler`] and must produce a
+ /// `Message`.
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ pub fn new<F>(is_active: bool, label: impl Into<String>, f: F) -> Self
+ where
+ F: 'static + Fn(bool) -> Message,
+ {
+ Toggler {
+ is_active,
+ on_toggle: Box::new(f),
+ label: label.into(),
+ width: Length::Fill,
+ size: <Renderer as self::Renderer>::DEFAULT_SIZE,
+ text_size: None,
+ font: Renderer::Font::default(),
+ style: Renderer::Style::default(),
+ }
+ }
+
+ /// Sets the size of the [`Toggler`].
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ pub fn size(mut self, size: u16) -> Self {
+ self.size = size;
+ self
+ }
+
+ /// Sets the width of the [`Toggler`].
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ pub fn width(mut self, width: Length) -> Self {
+ self.width = width;
+ self
+ }
+
+ /// Sets the text size o the [`Toggler`].
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ pub fn text_size(mut self, text_size: u16) -> Self {
+ self.text_size = Some(text_size);
+ self
+ }
+
+ /// Sets the [`Font`] of the text of the [`Toggler`]
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ /// [`Font`]: ../../struct.Font.html
+ pub fn font(mut self, font: Renderer::Font) -> Self {
+ self.font = font;
+ self
+ }
+
+ /// Sets the style of the [`Toggler`].
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
+ self.style = style.into();
+ self
+ }
+}
+
+impl<Message, Renderer> Widget<Message, Renderer> for Toggler<Message, Renderer>
+where
+ Renderer: self::Renderer + text::Renderer + row::Renderer,
+{
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ Row::<(), Renderer>::new()
+ .width(self.width)
+ .align_items(Align::Center)
+ .push(
+ Text::new(&self.label)
+ .font(self.font)
+ .width(self.width)
+ .size(self.text_size.unwrap_or(renderer.default_size())),
+ )
+ .push(
+ Row::new()
+ .width(Length::Units(2 * self.size))
+ .height(Length::Units(self.size)),
+ )
+ .layout(renderer, limits)
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _renderer: &Renderer,
+ _clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ let mouse_over = layout.bounds().contains(cursor_position);
+
+ if mouse_over {
+ messages.push((self.on_toggle)(!self.is_active));
+
+ event::Status::Captured
+ } else {
+ event::Status::Ignored
+ }
+ }
+ _ => event::Status::Ignored,
+ }
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> Renderer::Output {
+ let bounds = layout.bounds();
+ let mut children = layout.children();
+
+ let label_layout = children.next().unwrap();
+ let toggler_layout = children.next().unwrap();
+ let toggler_bounds = toggler_layout.bounds();
+
+ let label = text::Renderer::draw(
+ renderer,
+ defaults,
+ label_layout.bounds(),
+ &self.label,
+ self.text_size.unwrap_or(renderer.default_size()),
+ self.font,
+ None,
+ HorizontalAlignment::Left,
+ VerticalAlignment::Center,
+ );
+
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ self::Renderer::draw(
+ renderer,
+ toggler_bounds,
+ self.is_active,
+ is_mouse_over,
+ label,
+ &self.style,
+ )
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
+
+ self.label.hash(state)
+ }
+}
+
+/// The renderer of a [`Toggler`].
+///
+/// Your [renderer] will need to implement this trait before being
+/// able to use a [`Toggler`] in your user interface.
+///
+/// [`Toggler`]: struct.Toggler.html
+/// [renderer]: ../../renderer/index.html
+pub trait Renderer: crate::Renderer {
+ /// The style supported by this renderer.
+ type Style: Default;
+
+ /// The default size of a [`Toggler`].
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ const DEFAULT_SIZE: u16;
+
+ /// Draws a [`Toggler`].
+ ///
+ /// It receives:
+ /// * the bounds of the [`Toggler`]
+ /// * whether the [`Toggler`] is activated or not
+ /// * whether the mouse is over the [`Toggler`] or not
+ /// * the drawn label of the [`Toggler`]
+ /// * the style of the [`Toggler`]
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ fn draw(
+ &mut self,
+ bounds: Rectangle,
+ is_active: bool,
+ is_mouse_over: bool,
+ label: Self::Output,
+ style: &Self::Style,
+ ) -> Self::Output;
+}
+
+impl<'a, Message, Renderer> From<Toggler<Message, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ Renderer: 'a + self::Renderer + text::Renderer + row::Renderer,
+ Message: 'a,
+{
+ fn from(
+ toggler: Toggler<Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
+ Element::new(toggler)
+ }
+}

diff:
mod platform {
pub use crate::renderer::widget::{
button, checkbox, container, pane_grid, pick_list, progress_bar, radio,
- rule, scrollable, slider, text_input, tooltip, Column, Row, Space,
- Text,
+ rule, scrollable, slider, text_input, toggler, tooltip, Column, Row,
+ Space, Text,
};
#[cfg(any(feature = "canvas", feature = "glow_canvas"))]
@@ -53,7 +53,7 @@ mod platform {
button::Button, checkbox::Checkbox, container::Container, image::Image,
pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar,
radio::Radio, rule::Rule, scrollable::Scrollable, slider::Slider,
- svg::Svg, text_input::TextInput, tooltip::Tooltip,
+ svg::Svg, text_input::TextInput, toggler::Toggler, tooltip::Tooltip,
};
#[cfg(any(feature = "canvas", feature = "glow_canvas"))]

diff:
@@ -18,3 +18,4 @@ pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod text_input;
+pub mod toggler;

diff:
+//! Show toggle controls using togglers.
+use iced_core::Color;
+
+/// The appearance of a toggler.
+#[derive(Debug)]
+pub struct Style {
+ pub background: Color,
+ pub background_border: Option<Color>,
+ pub foreground: Color,
+ pub foreground_border: Option<Color>,
+}
+
+/// A set of rules that dictate the style of a toggler.
+pub trait StyleSheet {
+ fn active(&self, is_active: bool) -> Style;
+
+ fn hovered(&self, is_active: bool) -> Style;
+}
+
+struct Default;
+
+impl StyleSheet for Default {
+ fn active(&self, is_active: bool) -> Style {
+ Style {
+ background: if is_active {
+ Color::from_rgb(0.0, 1.0, 0.0)
+ } else {
+ Color::from_rgb(0.7, 0.7, 0.7)
+ },
+ background_border: None,
+ foreground: Color::WHITE,
+ foreground_border: None,
+ }
+ }
+
+ fn hovered(&self, is_active: bool) -> Style {
+ Style {
+ foreground: Color::from_rgb(0.95, 0.95, 0.95),
+ ..self.active(is_active)
+ }
+ }
+}
+
+impl std::default::Default for Box<dyn StyleSheet> {
+ fn default() -> Self {
+ Box::new(Default)
+ }
+}
+
+impl<T> From<T> for Box<dyn StyleSheet>
+where
+ T: 'static + StyleSheet,
+{
+ fn from(style: T) -> Self {
+ Box::new(style)
+ }
+}

diff:
@@ -20,6 +20,7 @@ pub mod rule;
pub mod scrollable;
pub mod slider;
pub mod text_input;
+pub mod toggler;
pub mod tooltip;
#[doc(no_inline)]
@@ -45,6 +46,8 @@ pub use slider::Slider;
#[doc(no_inline)]
pub use text_input::TextInput;
#[doc(no_inline)]
+pub use toggler::Toggler;
+#[doc(no_inline)]
pub use tooltip::Tooltip;
#[cfg(feature = "canvas")]

diff:
+//! Show toggle controls using togglers.
+use crate::Renderer;
+
+pub use iced_graphics::toggler::{Style, StyleSheet};
+
+/// A toggler that can be toggled
+///
+/// This is an alias of an `iced_native` toggler with an `iced_wgpu::Renderer`.
+pub type Toggler<Message> = iced_native::Toggler<Message, Renderer>;


message: fix missing semicolon in doc test

diff:
@@ -20,7 +20,7 @@ use crate::{
///
/// let is_active = true;
///
-/// Toggler::new(is_active, "Toggle me!", |b| Message::TogglerToggled(b))
+/// Toggler::new(is_active, "Toggle me!", |b| Message::TogglerToggled(b));
/// ```
///
#[allow(missing_debug_implementations)]

original_message: fix missing semicolon in doc test


message: add missing glow support

diff:
+//! Show toggle controls using togglers.
+use crate::Renderer;
+
+pub use iced_graphics::toggler::{Style, StyleSheet};
+
+/// A toggler that can be toggled.
+///
+/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`.
+pub type Toggler<Message> = iced_native::Toggler<Message, Renderer>;
\ No newline at end of file

original_message: add missing glow support


message: Add alignment of `Toggler` label.

diff:
@@ -31,6 +31,8 @@ pub struct Toggler<Message, Renderer: self::Renderer + text::Renderer> {
width: Length,
size: u16,
text_size: Option<u16>,
+ text_align: Option<HorizontalAlignment>,
+ spacing: u16,
font: Renderer::Font,
style: Renderer::Style,
}
@@ -59,6 +61,8 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
width: Length::Fill,
size: <Renderer as self::Renderer>::DEFAULT_SIZE,
text_size: None,
+ text_align: None,
+ spacing: 0,
font: Renderer::Font::default(),
style: Renderer::Style::default(),
}
@@ -88,6 +92,22 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
self
}
+ /// Sets the alignment of the text of the [`Toggler`]
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ pub fn text_align(mut self, align: HorizontalAlignment) -> Self {
+ self.text_align = Some(align);
+ self
+ }
+
+ /// Sets the spacing between the [`Toggler`] and the text.
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ pub fn spacing(mut self, spacing: u16) -> Self {
+ self.spacing = spacing;
+ self
+ }
+
/// Sets the [`Font`] of the text of the [`Toggler`]
///
/// [`Toggler`]: struct.Toggler.html
@@ -125,9 +145,13 @@ where
) -> layout::Node {
Row::<(), Renderer>::new()
.width(self.width)
+ .spacing(self.spacing)
.align_items(Align::Center)
.push(
Text::new(&self.label)
+ .horizontal_alignment(
+ self.text_align.unwrap_or(HorizontalAlignment::Left),
+ )
.font(self.font)
.width(self.width)
.size(self.text_size.unwrap_or(renderer.default_size())),
@@ -188,7 +212,7 @@ where
self.text_size.unwrap_or(renderer.default_size()),
self.font,
None,
- HorizontalAlignment::Left,
+ self.text_align.unwrap_or(HorizontalAlignment::Left),
VerticalAlignment::Center,
);

original_message: Add alignment of `Toggler` label.


message: Change label of `Toggler` to optional

diff:
@@ -33,7 +33,7 @@ where
bounds: Rectangle,
is_active: bool,
is_mouse_over: bool,
- (label, _): Self::Output,
+ label: Option<Self::Output>,
style_sheet: &Self::Style,
) -> Self::Output {
let style = if is_mouse_over {
@@ -82,7 +82,12 @@ where
(
Primitive::Group {
- primitives: vec![label, toggler_background, toggler_foreground],
+ primitives: match label {
+ Some((l, _)) => {
+ vec![l, toggler_background, toggler_foreground]
+ }
+ None => vec![toggler_background, toggler_foreground],
+ },
},
if is_mouse_over {
mouse::Interaction::Pointer

diff:
@@ -299,7 +299,7 @@ impl toggler::Renderer for Null {
_bounds: Rectangle,
_is_checked: bool,
_is_mouse_over: bool,
- _label: Self::Output,
+ _label: Option<Self::Output>,
_style: &Self::Style,
) {
}

diff:
@@ -20,14 +20,14 @@ use crate::{
///
/// let is_active = true;
///
-/// Toggler::new(is_active, "Toggle me!", |b| Message::TogglerToggled(b));
+/// Toggler::new(is_active, String::from("Toggle me!"), |b| Message::TogglerToggled(b));
/// ```
///
#[allow(missing_debug_implementations)]
pub struct Toggler<Message, Renderer: self::Renderer + text::Renderer> {
is_active: bool,
on_toggle: Box<dyn Fn(bool) -> Message>,
- label: String,
+ label: Option<String>,
width: Length,
size: u16,
text_size: Option<u16>,
@@ -44,13 +44,17 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
///
/// It expects:
/// * a boolean describing whether the [`Toggler`] is checked or not
- /// * the label of the [`Toggler`]
+ /// * An optional label for the [`Toggler`]
/// * a function that will be called when the [`Toggler`] is toggled. It
/// will receive the new state of the [`Toggler`] and must produce a
/// `Message`.
///
/// [`Toggler`]: struct.Toggler.html
- pub fn new<F>(is_active: bool, label: impl Into<String>, f: F) -> Self
+ pub fn new<F>(
+ is_active: bool,
+ label: impl Into<Option<String>>,
+ f: F,
+ ) -> Self
where
F: 'static + Fn(bool) -> Message,
{
@@ -143,25 +147,30 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- Row::<(), Renderer>::new()
+ let mut row = Row::<(), Renderer>::new()
.width(self.width)
.spacing(self.spacing)
- .align_items(Align::Center)
- .push(
- Text::new(&self.label)
+ .align_items(Align::Center);
+
+ if let Some(label) = &self.label {
+ row = row.push(
+ Text::new(label)
.horizontal_alignment(
self.text_align.unwrap_or(HorizontalAlignment::Left),
)
.font(self.font)
.width(self.width)
.size(self.text_size.unwrap_or(renderer.default_size())),
- )
- .push(
+ );
+ }
+
+ row = row.push(
Row::new()
.width(Length::Units(2 * self.size))
.height(Length::Units(self.size)),
- )
- .layout(renderer, limits)
+ );
+
+ row.layout(renderer, limits)
}
fn on_event(
@@ -200,21 +209,28 @@ where
let bounds = layout.bounds();
let mut children = layout.children();
+ let label = match &self.label {
+ Some(label) => {
let label_layout = children.next().unwrap();
- let toggler_layout = children.next().unwrap();
- let toggler_bounds = toggler_layout.bounds();
- let label = text::Renderer::draw(
+ Some(text::Renderer::draw(
renderer,
defaults,
label_layout.bounds(),
- &self.label,
+ &label,
self.text_size.unwrap_or(renderer.default_size()),
self.font,
None,
self.text_align.unwrap_or(HorizontalAlignment::Left),
VerticalAlignment::Center,
- );
+ ))
+ }
+
+ None => None,
+ };
+
+ let toggler_layout = children.next().unwrap();
+ let toggler_bounds = toggler_layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
@@ -267,7 +283,7 @@ pub trait Renderer: crate::Renderer {
bounds: Rectangle,
is_active: bool,
is_mouse_over: bool,
- label: Self::Output,
+ label: Option<Self::Output>,
style: &Self::Style,
) -> Self::Output;
}

original_message: Change label of `Toggler` to optional


message: Add `Toggler` to styling example

diff:
use iced::{
button, scrollable, slider, text_input, Align, Button, Checkbox, Column,
Container, Element, Length, ProgressBar, Radio, Row, Rule, Sandbox,
- Scrollable, Settings, Slider, Space, Text, TextInput,
+ Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
};
pub fn main() -> iced::Result {
@@ -17,7 +17,8 @@ struct Styling {
button: button::State,
slider: slider::State,
slider_value: f32,
- toggle_value: bool,
+ checkbox_value: bool,
+ toggler_value: bool,
}
#[derive(Debug, Clone)]
@@ -27,6 +28,7 @@ enum Message {
ButtonPressed,
SliderChanged(f32),
CheckboxToggled(bool),
+ TogglerToggled(bool),
}
impl Sandbox for Styling {
@@ -46,7 +48,8 @@ impl Sandbox for Styling {
Message::InputChanged(value) => self.input_value = value,
Message::ButtonPressed => {}
Message::SliderChanged(value) => self.slider_value = value,
- Message::CheckboxToggled(value) => self.toggle_value = value,
+ Message::CheckboxToggled(value) => self.checkbox_value = value,
+ Message::TogglerToggled(value) => self.toggler_value = value,
}
}
@@ -101,11 +104,19 @@ impl Sandbox for Styling {
.push(Text::new("You did it!"));
let checkbox = Checkbox::new(
- self.toggle_value,
- "Toggle me!",
+ self.checkbox_value,
+ "Check me!",
Message::CheckboxToggled,
)
- .width(Length::Fill)
+ .style(self.theme);
+
+ let toggler = Toggler::new(
+ self.toggler_value,
+ String::from("Toggle me!"),
+ Message::TogglerToggled,
+ )
+ .width(Length::Shrink)
+ .spacing(10)
.style(self.theme);
let content = Column::new()
@@ -124,7 +135,13 @@ impl Sandbox for Styling {
.align_items(Align::Center)
.push(scrollable)
.push(Rule::vertical(38).style(self.theme))
- .push(checkbox),
+ .push(
+ Column::new()
+ .width(Length::Shrink)
+ .spacing(20)
+ .push(checkbox)
+ .push(toggler),
+ ),
);
Container::new(content)
@@ -140,7 +157,7 @@ impl Sandbox for Styling {
mod style {
use iced::{
button, checkbox, container, progress_bar, radio, rule, scrollable,
- slider, text_input,
+ slider, text_input, toggler,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -231,6 +248,15 @@ mod style {
}
}
+ impl From<Theme> for Box<dyn toggler::StyleSheet> {
+ fn from(theme: Theme) -> Self {
+ match theme {
+ Theme::Light => Default::default(),
+ Theme::Dark => dark::Toggler.into(),
+ }
+ }
+ }
+
impl From<Theme> for Box<dyn rule::StyleSheet> {
fn from(theme: Theme) -> Self {
match theme {
@@ -269,7 +295,7 @@ mod style {
mod dark {
use iced::{
button, checkbox, container, progress_bar, radio, rule, scrollable,
- slider, text_input, Color,
+ slider, text_input, toggler, Color,
};
const SURFACE: Color = Color::from_rgb(
@@ -520,6 +546,35 @@ mod style {
}
}
+ pub struct Toggler;
+
+ impl toggler::StyleSheet for Toggler {
+ fn active(&self, is_active: bool) -> toggler::Style {
+ toggler::Style {
+ background: if is_active { ACTIVE } else { SURFACE },
+ background_border: None,
+ foreground: if is_active { Color::WHITE } else { ACTIVE },
+ foreground_border: None,
+ }
+ }
+
+ fn hovered(&self, is_active: bool) -> toggler::Style {
+ toggler::Style {
+ background: if is_active { ACTIVE } else { SURFACE },
+ background_border: None,
+ foreground: if is_active {
+ Color {
+ a: 0.5,
+ ..Color::WHITE
+ }
+ } else {
+ Color { a: 0.5, ..ACTIVE }
+ },
+ foreground_border: None,
+ }
+ }
+ }
+
pub struct Rule;
impl rule::StyleSheet for Rule {

original_message: Add `Toggler` to styling example


message: Add `Toggler` to tour example

diff:
use iced::{
button, scrollable, slider, text_input, Button, Checkbox, Color, Column,
Container, Element, HorizontalAlignment, Image, Length, Radio, Row,
- Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput,
+ Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput, Toggler,
};
pub fn main() -> iced::Result {
@@ -135,6 +135,9 @@ impl Steps {
color: Color::BLACK,
},
Step::Radio { selection: None },
+ Step::Toggler {
+ can_continue: false,
+ },
Step::Image {
width: 300,
slider: slider::State::new(),
@@ -206,6 +209,9 @@ enum Step {
Radio {
selection: Option<Language>,
},
+ Toggler {
+ can_continue: bool,
+ },
Image {
width: u16,
slider: slider::State,
@@ -232,6 +238,7 @@ pub enum StepMessage {
InputChanged(String),
ToggleSecureInput(bool),
DebugToggled(bool),
+ TogglerChanged(bool),
}
impl<'a> Step {
@@ -287,6 +294,11 @@ impl<'a> Step {
*is_secure = toggle;
}
}
+ StepMessage::TogglerChanged(value) => {
+ if let Step::Toggler { can_continue, .. } = self {
+ *can_continue = value;
+ }
+ }
};
}
@@ -294,6 +306,7 @@ impl<'a> Step {
match self {
Step::Welcome => "Welcome",
Step::Radio { .. } => "Radio button",
+ Step::Toggler { .. } => "Toggler",
Step::Slider { .. } => "Slider",
Step::Text { .. } => "Text",
Step::Image { .. } => "Image",
@@ -309,6 +322,7 @@ impl<'a> Step {
match self {
Step::Welcome => true,
Step::Radio { selection } => *selection == Some(Language::Rust),
+ Step::Toggler { can_continue } => *can_continue,
Step::Slider { .. } => true,
Step::Text { .. } => true,
Step::Image { .. } => true,
@@ -324,6 +338,7 @@ impl<'a> Step {
match self {
Step::Welcome => Self::welcome(),
Step::Radio { selection } => Self::radio(*selection),
+ Step::Toggler { can_continue } => Self::toggler(*can_continue),
Step::Slider { state, value } => Self::slider(state, *value),
Step::Text {
size_slider,
@@ -545,6 +560,18 @@ impl<'a> Step {
))
}
+ fn toggler(can_continue: bool) -> Column<'a, StepMessage> {
+ Self::container("Toggler")
+ .push(Text::new(
+ "A toggler is mostly used to enable or disable something.",
+ ))
+ .push(Toggler::new(
+ can_continue,
+ String::from("Toggle me to continue..."),
+ StepMessage::TogglerChanged,
+ ))
+ }
+
fn image(
width: u16,
slider: &'a mut slider::State,

original_message: Add `Toggler` to tour example


message: Update documentation of `Toggler`

diff:
@@ -96,7 +96,7 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
self
}
- /// Sets the alignment of the text of the [`Toggler`]
+ /// Sets the horizontal alignment of the text of the [`Toggler`]
///
/// [`Toggler`]: struct.Toggler.html
pub fn text_align(mut self, align: HorizontalAlignment) -> Self {

original_message: Update documentation of `Toggler`


message: Add `Toggler` widget to `iced_web`

diff:
@@ -14,6 +14,9 @@ pub enum Rule {
/// Spacing between elements
Spacing(u16),
+
+ /// Toggler input for a specific size
+ Toggler(u16),
}
impl Rule {
@@ -23,6 +26,7 @@ impl Rule {
Rule::Column => String::from("c"),
Rule::Row => String::from("r"),
Rule::Spacing(spacing) => format!("s-{}", spacing),
+ Rule::Toggler(size) => format!("toggler-{}", size),
}
}
@@ -55,6 +59,46 @@ impl Rule {
class
)
.into_bump_str(),
+ Rule::Toggler(size) => bumpalo::format!(
+ in bump,
+ ".toggler-{} {{ display: flex; cursor: pointer; justify-content: space-between; }} \
+ .toggler-{} input {{ display:none; }} \
+ .toggler-{} span {{ background-color: #b1b1b1; position: relative; display: inline-flex; width:{}px; height: {}px; border-radius: {}px;}} \
+ .toggler-{} span > span {{ background-color: #FFFFFF; width: {}px; height: {}px; border-radius: 50%; top: 1px; left: 1px;}} \
+ .toggler-{}:hover span > span {{ background-color: #f1f1f1 !important; }} \
+ .toggler-{} input:checked + span {{ background-color: #00FF00; }} \
+ .toggler-{} input:checked + span > span {{ -webkit-transform: translateX({}px); -ms-transform:translateX({}px); transform: translateX({}px); }}
+ ",
+ // toggler
+ size,
+
+ // toggler input
+ size,
+
+ // toggler span
+ size,
+ size*2,
+ size,
+ size,
+
+ // toggler span > span
+ size,
+ size-2,
+ size-2,
+
+ // toggler: hover + span > span
+ size,
+
+ // toggler input:checked + span
+ size,
+
+ // toggler input:checked + span > span
+ size,
+ size,
+ size,
+ size
+ )
+ .into_bump_str(),
}
}
}

diff:
@@ -24,6 +24,7 @@ pub mod radio;
pub mod scrollable;
pub mod slider;
pub mod text_input;
+pub mod toggler;
mod column;
mod row;
@@ -40,6 +41,8 @@ pub use slider::Slider;
pub use text::Text;
#[doc(no_inline)]
pub use text_input::TextInput;
+#[doc(no_inline)]
+pub use toggler::Toggler;
pub use checkbox::Checkbox;
pub use column::Column;

diff:
+//! Show toggle controls using togglers.
+use crate::{css, Bus, Css, Element, Length, Widget};
+
+pub use iced_style::toggler::{Style, StyleSheet};
+
+use dodrio::bumpalo;
+use std::rc::Rc;
+
+/// A toggler that can be toggled.
+///
+/// # Example
+///
+/// ```
+/// # use iced_web::Toggler;
+///
+/// pub enum Message {
+/// TogglerToggled(bool),
+/// }
+///
+/// let is_active = true;
+///
+/// Toggler::new(is_active, String::from("Toggle me!"), Message::TogglerToggled);
+/// ```
+///
+#[allow(missing_debug_implementations)]
+pub struct Toggler<Message> {
+ is_active: bool,
+ on_toggle: Rc<dyn Fn(bool) -> Message>,
+ label: Option<String>,
+ id: Option<String>,
+ width: Length,
+ style: Box<dyn StyleSheet>,
+}
+
+impl<Message> Toggler<Message> {
+ /// Creates a new [`Toggler`].
+ ///
+ /// It expects:
+ /// * a boolean describing whether the [`Toggler`] is active or not
+ /// * An optional label for the [`Toggler`]
+ /// * a function that will be called when the [`Toggler`] is toggled. It
+ /// will receive the new state of the [`Toggler`] and must produce a
+ /// `Message`.
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ pub fn new<F>(is_active: bool, label: impl Into<Option<String>>, f: F) -> Self
+ where
+ F: 'static + Fn(bool) -> Message,
+ {
+ Toggler {
+ is_active,
+ on_toggle: Rc::new(f),
+ label: label.into(),
+ id: None,
+ width: Length::Shrink,
+ style: Default::default(),
+ }
+ }
+
+ /// Sets the width of the [`Toggler`].
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ pub fn width(mut self, width: Length) -> Self {
+ self.width = width;
+ self
+ }
+
+ /// Sets the style of the [`Toggler`].
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self {
+ self.style = style.into();
+ self
+ }
+
+ /// Sets the id of the [`Toggler`].
+ ///
+ /// [`Toggler`]: struct.Toggler.html
+ pub fn id(mut self, id: impl Into<String>) -> Self {
+ self.id = Some(id.into());
+ self
+ }
+}
+
+impl<Message> Widget<Message> for Toggler<Message>
+where
+ Message: 'static,
+{
+ fn node<'b>(
+ &self,
+ bump: &'b bumpalo::Bump,
+ bus: &Bus<Message>,
+ style_sheet: &mut Css<'b>,
+ ) -> dodrio::Node<'b> {
+ use dodrio::builder::*;
+ use dodrio::bumpalo::collections::String;
+
+ let toggler_label = &self.label.as_ref().map(|label| {
+ String::from_str_in(&label, bump).into_bump_str()
+ });
+
+ let event_bus = bus.clone();
+ let on_toggle = self.on_toggle.clone();
+ let is_active = self.is_active;
+
+ let row_class = style_sheet.insert(bump, css::Rule::Row);
+ let toggler_class = style_sheet.insert(bump, css::Rule::Toggler(16));
+
+ let (label, input) = if let Some(id) = &self.id {
+ let id = String::from_str_in(id, bump).into_bump_str();
+
+ (label(bump).attr("for", id), input(bump).attr("id", id))
+ } else {
+ (label(bump), input(bump))
+ };
+
+ let checkbox = input
+ .attr("type", "checkbox")
+ .bool_attr("checked", self.is_active)
+ .on("click", move |_root, vdom, _event| {
+ let msg = on_toggle(!is_active);
+ event_bus.publish(msg);
+
+ vdom.schedule_render();
+ })
+ .finish();
+
+ let toggler = span(bump)
+ .children(vec![span(bump).finish()])
+ .finish();
+
+ label
+ .attr(
+ "class",
+ bumpalo::format!(in bump, "{} {}", row_class, toggler_class)
+ .into_bump_str(),
+ )
+ .attr(
+ "style",
+ bumpalo::format!(in bump, "width: {}; align-items: center", css::length(self.width))
+ .into_bump_str()
+ )
+ .children(
+ if let Some(label) = toggler_label {
+ vec![
+ text(label),
+ checkbox,
+ toggler,
+ ]
+ } else {
+ vec![
+ checkbox,
+ toggler,
+ ]
+ }
+ )
+ .finish()
+ }
+}
+
+impl<'a, Message> From<Toggler<Message>> for Element<'a, Message>
+where
+ Message: 'static,
+{
+ fn from(toggler: Toggler<Message>) -> Element<'a, Message> {
+ Element::new(toggler)
+ }
+}
\ No newline at end of file

original_message: Add `Toggler` widget to `iced_web`


message: major bug fix

diff:
@@ -31,7 +31,7 @@ asset::ICPUMeshBuffer* COverdrawMeshOptimizer::createOptimized(asset::ICPUMeshBu
return outbuffer;
}
- const size_t idxCount = outbuffer->getIndexType();
+ const size_t idxCount = outbuffer->getIndexCount();
void* indicesCopy = _IRR_ALIGNED_MALLOC(indexSize*idxCount,_IRR_SIMD_ALIGNMENT);
memcpy(indicesCopy, indices, indexSize*idxCount);
const size_t vertexCount = outbuffer->calcVertexCount();

license: Apache License 2.0

repo: buildaworldnet/irrlichtbaw

original_message: major bug fix


message: smooth normal calculation first setup

diff:
@@ -74,6 +74,9 @@ namespace asset
IReferenceCounted::drop() for more information. */
virtual asset::ICPUMeshBuffer* createMeshBufferUniquePrimitives(asset::ICPUMeshBuffer* inbuffer) const = 0;
+ //
+ virtual asset::ICPUMeshBuffer* calculateSmoothNormals(asset::ICPUMeshBuffer* inbuffer, const float creaseAngle) const = 0;
+
//! Creates a copy of a mesh with vertices welded
/** \param mesh Input mesh
\param errMetrics Array of size EVAI_COUNT. Describes error metric for each vertex attribute (used if attribute is of floating point or normalized type).

diff:
@@ -718,6 +718,11 @@ asset::ICPUMeshBuffer* CMeshManipulator::createMeshBufferUniquePrimitives(asset:
return clone;
}
+asset::ICPUMeshBuffer* CMeshManipulator::calculateSmoothNormals(asset::ICPUMeshBuffer* inbuffer, const float creaseAngle) const
+{
+ return nullptr;
+}
+
// Used by createMeshBufferWelded only
static bool cmpVertices(asset::ICPUMeshBuffer* _inbuf, const void* _va, const void* _vb, size_t _vsize, const IMeshManipulator::SErrorMetric* _errMetrics, const IMeshManipulator* _meshManip)
{

diff:
@@ -65,6 +65,9 @@ public:
//! Creates a copy of the mesh, which will only consist of unique triangles, i.e. no vertices are shared.
virtual asset::ICPUMeshBuffer* createMeshBufferUniquePrimitives(asset::ICPUMeshBuffer* inbuffer) const;
+ //
+ virtual asset::ICPUMeshBuffer* calculateSmoothNormals(asset::ICPUMeshBuffer* inbuffer, const float creaseAngle) const override;
+
//! Creates a copy of the mesh, which will have all duplicated vertices removed, i.e. maximal amount of vertices are shared via indexing.
virtual asset::ICPUMeshBuffer* createMeshBufferWelded(asset::ICPUMeshBuffer *inbuffer, const SErrorMetric* _errMetrics, const bool& optimIndexType = true, const bool& makeNewMesh=false) const;

original_message: smooth normal calculation first setup