From d4743183d40c6044ce6fa39e2a52919a32912cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 19 May 2020 14:23:28 +0200 Subject: [PATCH 01/40] Draft first working version of `iced_glow` :tada: --- Cargo.toml | 1 + examples/tour/Cargo.toml | 3 +- examples/tour/src/main.rs | 66 ++-- glow/Cargo.toml | 34 ++ glow/src/defaults.rs | 32 ++ glow/src/lib.rs | 37 ++ glow/src/primitive.rs | 107 ++++++ glow/src/quad.rs | 254 +++++++++++++ glow/src/renderer.rs | 455 +++++++++++++++++++++++ glow/src/renderer/widget.rs | 19 + glow/src/renderer/widget/button.rs | 93 +++++ glow/src/renderer/widget/checkbox.rs | 63 ++++ glow/src/renderer/widget/column.rs | 34 ++ glow/src/renderer/widget/container.rs | 48 +++ glow/src/renderer/widget/image.rs | 22 ++ glow/src/renderer/widget/pane_grid.rs | 93 +++++ glow/src/renderer/widget/progress_bar.rs | 54 +++ glow/src/renderer/widget/radio.rs | 63 ++++ glow/src/renderer/widget/row.rs | 34 ++ glow/src/renderer/widget/scrollable.rs | 125 +++++++ glow/src/renderer/widget/slider.rs | 106 ++++++ glow/src/renderer/widget/space.rs | 8 + glow/src/renderer/widget/svg.rs | 22 ++ glow/src/renderer/widget/text.rs | 61 +++ glow/src/renderer/widget/text_input.rs | 261 +++++++++++++ glow/src/settings.rs | 50 +++ glow/src/shader/quad.frag | 67 ++++ glow/src/shader/quad.vert | 47 +++ glow/src/text.rs | 180 +++++++++ glow/src/text/font.rs | 37 ++ glow/src/text/icons.ttf | Bin 0 -> 4912 bytes glow/src/transformation.rs | 54 +++ glow/src/triangle.rs | 84 +++++ glow/src/viewport.rs | 33 ++ glow/src/widget.rs | 44 +++ glow/src/widget/button.rs | 15 + glow/src/widget/canvas.rs | 201 ++++++++++ glow/src/widget/checkbox.rs | 9 + glow/src/widget/container.rs | 10 + glow/src/widget/pane_grid.rs | 24 ++ glow/src/widget/progress_bar.rs | 15 + glow/src/widget/radio.rs | 10 + glow/src/widget/scrollable.rs | 13 + glow/src/widget/slider.rs | 16 + glow/src/widget/text_input.rs | 15 + glow/src/window.rs | 6 + glow/src/window/backend.rs | 183 +++++++++ glow/src/window/swap_chain.rs | 5 + native/src/window/backend.rs | 11 +- wgpu/src/window/backend.rs | 21 +- winit/src/application.rs | 9 +- 51 files changed, 3212 insertions(+), 42 deletions(-) create mode 100644 glow/Cargo.toml create mode 100644 glow/src/defaults.rs create mode 100644 glow/src/lib.rs create mode 100644 glow/src/primitive.rs create mode 100644 glow/src/quad.rs create mode 100644 glow/src/renderer.rs create mode 100644 glow/src/renderer/widget.rs create mode 100644 glow/src/renderer/widget/button.rs create mode 100644 glow/src/renderer/widget/checkbox.rs create mode 100644 glow/src/renderer/widget/column.rs create mode 100644 glow/src/renderer/widget/container.rs create mode 100644 glow/src/renderer/widget/image.rs create mode 100644 glow/src/renderer/widget/pane_grid.rs create mode 100644 glow/src/renderer/widget/progress_bar.rs create mode 100644 glow/src/renderer/widget/radio.rs create mode 100644 glow/src/renderer/widget/row.rs create mode 100644 glow/src/renderer/widget/scrollable.rs create mode 100644 glow/src/renderer/widget/slider.rs create mode 100644 glow/src/renderer/widget/space.rs create mode 100644 glow/src/renderer/widget/svg.rs create mode 100644 glow/src/renderer/widget/text.rs create mode 100644 glow/src/renderer/widget/text_input.rs create mode 100644 glow/src/settings.rs create mode 100644 glow/src/shader/quad.frag create mode 100644 glow/src/shader/quad.vert create mode 100644 glow/src/text.rs create mode 100644 glow/src/text/font.rs create mode 100644 glow/src/text/icons.ttf create mode 100644 glow/src/transformation.rs create mode 100644 glow/src/triangle.rs create mode 100644 glow/src/viewport.rs create mode 100644 glow/src/widget.rs create mode 100644 glow/src/widget/button.rs create mode 100644 glow/src/widget/canvas.rs create mode 100644 glow/src/widget/checkbox.rs create mode 100644 glow/src/widget/container.rs create mode 100644 glow/src/widget/pane_grid.rs create mode 100644 glow/src/widget/progress_bar.rs create mode 100644 glow/src/widget/radio.rs create mode 100644 glow/src/widget/scrollable.rs create mode 100644 glow/src/widget/slider.rs create mode 100644 glow/src/widget/text_input.rs create mode 100644 glow/src/window.rs create mode 100644 glow/src/window/backend.rs create mode 100644 glow/src/window/swap_chain.rs diff --git a/Cargo.toml b/Cargo.toml index 6dd30b42..3d8c1757 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ maintenance = { status = "actively-developed" } members = [ "core", "futures", + "glow", "native", "style", "web", diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml index 96749e90..b34c5feb 100644 --- a/examples/tour/Cargo.toml +++ b/examples/tour/Cargo.toml @@ -6,5 +6,6 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../..", features = ["image", "debug"] } +iced_winit = { path = "../../winit", features = ["debug"] } +iced_glow = { path = "../../glow" } env_logger = "0.7" diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index c9678b9d..729ae8fb 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -1,13 +1,20 @@ -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, +use iced_glow::{ + button, scrollable, slider, text_input, window, Button, Checkbox, Color, + Column, Command, Container, Element, HorizontalAlignment, Image, Length, + Radio, Row, Scrollable, Slider, Space, Text, TextInput, }; +use iced_winit::{executor, Application, Settings}; pub fn main() { env_logger::init(); - Tour::run(Settings::default()) + Tour::run( + Settings::default(), + iced_glow::Settings { + default_font: None, + antialiasing: None, + }, + ) } pub struct Tour { @@ -18,24 +25,30 @@ pub struct Tour { debug: bool, } -impl Sandbox for Tour { +impl Application for Tour { + type Backend = window::Backend; + type Executor = executor::Null; type Message = Message; + type Flags = (); - fn new() -> Tour { - Tour { - steps: Steps::new(), - scroll: scrollable::State::new(), - back_button: button::State::new(), - next_button: button::State::new(), - debug: false, - } + fn new(_flags: ()) -> (Tour, Command) { + ( + Tour { + steps: Steps::new(), + scroll: scrollable::State::new(), + back_button: button::State::new(), + next_button: button::State::new(), + debug: false, + }, + Command::none(), + ) } fn title(&self) -> String { format!("{} - Iced", self.steps.title()) } - fn update(&mut self, event: Message) { + fn update(&mut self, event: Message) -> Command { match event { Message::BackPressed => { self.steps.go_back(); @@ -47,6 +60,8 @@ impl Sandbox for Tour { self.steps.update(step_msg, &mut self.debug); } } + + Command::none() } fn view(&mut self) -> Element { @@ -678,17 +693,18 @@ impl<'a> Step { fn ferris<'a>(width: u16) -> Container<'a, StepMessage> { Container::new( + Text::new("Not supported yet!") // This should go away once we unify resource loading on native // platforms - if cfg!(target_arch = "wasm32") { - Image::new("images/ferris.png") - } else { - Image::new(format!( - "{}/images/ferris.png", - env!("CARGO_MANIFEST_DIR") - )) - } - .width(Length::Units(width)), + //if cfg!(target_arch = "wasm32") { + // Image::new("images/ferris.png") + //} else { + // Image::new(format!( + // "{}/images/ferris.png", + // env!("CARGO_MANIFEST_DIR") + // )) + //} + //.width(Length::Units(width)), ) .width(Length::Fill) .center_x() @@ -749,7 +765,7 @@ pub enum Layout { } mod style { - use iced::{button, Background, Color, Vector}; + use iced_glow::{button, Background, Color, Vector}; pub enum Button { Primary, diff --git a/glow/Cargo.toml b/glow/Cargo.toml new file mode 100644 index 00000000..e130d563 --- /dev/null +++ b/glow/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "iced_glow" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +description = "A glow renderer for iced" +license = "MIT AND OFL-1.1" +repository = "https://github.com/hecrj/iced" + +[dependencies] +raw-window-handle = "0.3" +euclid = "0.20" +glow = "0.4" +bytemuck = "1.2" +glam = "0.8" +font-kit = "0.6" +log = "0.4" +glyph_brush = "0.6" + +[dependencies.iced_native] +version = "0.2" +path = "../native" + +[dependencies.iced_style] +version = "0.1" +path = "../style" + +[dependencies.surfman] +path = "../../surfman/surfman" +default-features = false +features = ["sm-raw-window-handle", "sm-x11"] + +[dependencies.glow_glyph] +path = "../../glow_glyph" diff --git a/glow/src/defaults.rs b/glow/src/defaults.rs new file mode 100644 index 00000000..11718a87 --- /dev/null +++ b/glow/src/defaults.rs @@ -0,0 +1,32 @@ +//! Use default styling attributes to inherit styles. +use iced_native::Color; + +/// Some default styling attributes. +#[derive(Debug, Clone, Copy)] +pub struct Defaults { + /// Text styling + pub text: Text, +} + +impl Default for Defaults { + fn default() -> Defaults { + Defaults { + text: Text::default(), + } + } +} + +/// Some default text styling attributes. +#[derive(Debug, Clone, Copy)] +pub struct Text { + /// The default color of text + pub color: Color, +} + +impl Default for Text { + fn default() -> Text { + Text { + color: Color::BLACK, + } + } +} diff --git a/glow/src/lib.rs b/glow/src/lib.rs new file mode 100644 index 00000000..ce447192 --- /dev/null +++ b/glow/src/lib.rs @@ -0,0 +1,37 @@ +//#![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![deny(unused_results)] +//#![forbid(unsafe_code)] +#![forbid(rust_2018_idioms)] + +mod defaults; +mod primitive; +mod quad; +mod renderer; +mod text; +mod transformation; +mod triangle; +mod viewport; + +pub mod settings; +pub mod widget; +pub mod window; + +pub use defaults::Defaults; +pub use primitive::Primitive; +pub use renderer::Renderer; +pub use settings::Settings; +pub use viewport::Viewport; + +pub(crate) use quad::Quad; +pub(crate) use transformation::Transformation; + +#[doc(no_inline)] +pub use widget::*; + +pub use iced_native::{ + Background, Color, Command, HorizontalAlignment, Length, Vector, + VerticalAlignment, +}; + +pub type Element<'a, Message> = iced_native::Element<'a, Message, Renderer>; diff --git a/glow/src/primitive.rs b/glow/src/primitive.rs new file mode 100644 index 00000000..e73227ef --- /dev/null +++ b/glow/src/primitive.rs @@ -0,0 +1,107 @@ +use iced_native::{ + image, svg, Background, Color, Font, HorizontalAlignment, Rectangle, Size, + Vector, VerticalAlignment, +}; + +use crate::triangle; +use std::sync::Arc; + +/// A rendering primitive. +#[derive(Debug, Clone)] +pub enum Primitive { + /// An empty primitive + None, + /// A group of primitives + Group { + /// The primitives of the group + primitives: Vec, + }, + /// A text primitive + Text { + /// The contents of the text + content: String, + /// The bounds of the text + bounds: Rectangle, + /// The color of the text + color: Color, + /// The size of the text + size: f32, + /// The font of the text + font: Font, + /// The horizontal alignment of the text + horizontal_alignment: HorizontalAlignment, + /// The vertical alignment of the text + vertical_alignment: VerticalAlignment, + }, + /// A quad primitive + Quad { + /// The bounds of the quad + bounds: Rectangle, + /// The background of the quad + background: Background, + /// The border radius of the quad + border_radius: u16, + /// The border width of the quad + border_width: u16, + /// The border color of the quad + border_color: Color, + }, + /// An image primitive + Image { + /// The handle of the image + handle: image::Handle, + /// The bounds of the image + bounds: Rectangle, + }, + /// An SVG primitive + Svg { + /// The path of the SVG file + handle: svg::Handle, + + /// The bounds of the viewport + bounds: Rectangle, + }, + /// A clip primitive + Clip { + /// The bounds of the clip + bounds: Rectangle, + /// The offset transformation of the clip + offset: Vector, + /// The content of the clip + content: Box, + }, + /// A primitive that applies a translation + Translate { + /// The translation vector + translation: Vector, + + /// The primitive to translate + content: Box, + }, + /// A low-level primitive to render a mesh of triangles. + /// + /// It can be used to render many kinds of geometry freely. + Mesh2D { + /// The size of the drawable region of the mesh. + /// + /// Any geometry that falls out of this region will be clipped. + size: Size, + + /// The vertex and index buffers of the mesh + buffers: triangle::Mesh2D, + }, + /// A cached primitive. + /// + /// This can be useful if you are implementing a widget where primitive + /// generation is expensive. + Cached { + /// The cached primitive + cache: Arc, + }, +} + +impl Default for Primitive { + fn default() -> Primitive { + Primitive::None + } +} diff --git a/glow/src/quad.rs b/glow/src/quad.rs new file mode 100644 index 00000000..744597d2 --- /dev/null +++ b/glow/src/quad.rs @@ -0,0 +1,254 @@ +use crate::{Transformation, Viewport}; +use glow::HasContext; +use iced_native::Rectangle; + +#[derive(Debug)] +pub struct Pipeline { + program: ::Program, + vertex_array: ::VertexArray, + instances: ::Buffer, + current_transform: Transformation, + current_scale: f32, +} + +impl Pipeline { + pub fn new(gl: &glow::Context) -> Pipeline { + let program = unsafe { + create_program( + gl, + &[ + (glow::VERTEX_SHADER, include_str!("shader/quad.vert")), + (glow::FRAGMENT_SHADER, include_str!("shader/quad.frag")), + ], + ) + }; + + unsafe { + gl.use_program(Some(program)); + + gl.uniform_matrix_4_f32_slice( + Some(0), + false, + &Transformation::identity().into(), + ); + gl.uniform_1_f32(Some(1), 1.0); + + gl.use_program(None); + } + + let (vertex_array, instances) = + unsafe { create_instance_buffer(gl, Quad::MAX) }; + + Pipeline { + program, + vertex_array, + instances, + current_transform: Transformation::identity(), + current_scale: 1.0, + } + } + + pub fn draw( + &mut self, + gl: &glow::Context, + viewport: &Viewport, + instances: &[Quad], + transformation: Transformation, + scale: f32, + bounds: Rectangle, + ) { + unsafe { + gl.enable(glow::SCISSOR_TEST); + gl.scissor( + bounds.x as i32, + (viewport.height() + - (bounds.y + bounds.height).min(viewport.height())) + as i32, + bounds.width as i32, + bounds.height as i32, + ); + + gl.use_program(Some(self.program)); + gl.bind_vertex_array(Some(self.vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instances)); + } + + if transformation != self.current_transform { + unsafe { + gl.uniform_matrix_4_f32_slice( + Some(0), + false, + &transformation.into(), + ); + + self.current_transform = transformation; + } + } + + if scale != self.current_scale { + unsafe { + gl.uniform_1_f32(Some(1), scale); + } + + self.current_scale = scale; + } + + let mut i = 0; + let total = instances.len(); + + while i < total { + let end = (i + Quad::MAX).min(total); + let amount = end - i; + + unsafe { + gl.buffer_sub_data_u8_slice( + glow::ARRAY_BUFFER, + 0, + bytemuck::cast_slice(&instances[i..end]), + ); + + gl.draw_arrays_instanced( + glow::TRIANGLE_STRIP, + 0, + 4, + amount as i32, + ); + } + + i += Quad::MAX; + } + + unsafe { + gl.bind_vertex_array(None); + gl.use_program(None); + gl.disable(glow::SCISSOR_TEST); + } + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct Quad { + pub position: [f32; 2], + pub scale: [f32; 2], + pub color: [f32; 4], + pub border_color: [f32; 4], + pub border_radius: f32, + pub border_width: f32, +} + +unsafe impl bytemuck::Zeroable for Quad {} +unsafe impl bytemuck::Pod for Quad {} + +impl Quad { + const MAX: usize = 100_000; +} + +unsafe fn create_program( + gl: &glow::Context, + shader_sources: &[(u32, &str)], +) -> ::Program { + let program = gl.create_program().expect("Cannot create program"); + + let mut shaders = Vec::with_capacity(shader_sources.len()); + + for (shader_type, shader_source) in shader_sources.iter() { + let shader = gl + .create_shader(*shader_type) + .expect("Cannot create shader"); + + gl.shader_source(shader, shader_source); + gl.compile_shader(shader); + + if !gl.get_shader_compile_status(shader) { + panic!(gl.get_shader_info_log(shader)); + } + + gl.attach_shader(program, shader); + + shaders.push(shader); + } + + gl.link_program(program); + if !gl.get_program_link_status(program) { + panic!(gl.get_program_info_log(program)); + } + + for shader in shaders { + gl.detach_shader(program, shader); + gl.delete_shader(shader); + } + + program +} + +unsafe fn create_instance_buffer( + gl: &glow::Context, + size: usize, +) -> ( + ::VertexArray, + ::Buffer, +) { + let vertex_array = gl.create_vertex_array().expect("Create vertex array"); + let buffer = gl.create_buffer().expect("Create instance buffer"); + + gl.bind_vertex_array(Some(vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer)); + gl.buffer_data_size( + glow::ARRAY_BUFFER, + (size * std::mem::size_of::()) as i32, + glow::DYNAMIC_DRAW, + ); + + let stride = std::mem::size_of::() as i32; + + gl.enable_vertex_attrib_array(0); + gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0); + gl.vertex_attrib_divisor(0, 1); + + gl.enable_vertex_attrib_array(1); + gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, stride, 4 * 2); + gl.vertex_attrib_divisor(1, 1); + + gl.enable_vertex_attrib_array(2); + gl.vertex_attrib_pointer_f32(2, 4, glow::FLOAT, false, stride, 4 * (2 + 2)); + gl.vertex_attrib_divisor(2, 1); + + gl.enable_vertex_attrib_array(3); + gl.vertex_attrib_pointer_f32( + 3, + 4, + glow::FLOAT, + false, + stride, + 4 * (2 + 2 + 4), + ); + gl.vertex_attrib_divisor(3, 1); + + gl.enable_vertex_attrib_array(4); + gl.vertex_attrib_pointer_f32( + 4, + 1, + glow::FLOAT, + false, + stride, + 4 * (2 + 2 + 4 + 4), + ); + gl.vertex_attrib_divisor(4, 1); + + gl.enable_vertex_attrib_array(5); + gl.vertex_attrib_pointer_f32( + 5, + 1, + glow::FLOAT, + false, + stride, + 4 * (2 + 2 + 4 + 4 + 1), + ); + gl.vertex_attrib_divisor(5, 1); + + gl.bind_vertex_array(None); + gl.bind_buffer(glow::ARRAY_BUFFER, None); + + (vertex_array, buffer) +} diff --git a/glow/src/renderer.rs b/glow/src/renderer.rs new file mode 100644 index 00000000..40228a6b --- /dev/null +++ b/glow/src/renderer.rs @@ -0,0 +1,455 @@ +use crate::{ + quad, text, triangle, Defaults, Primitive, Quad, Settings, Transformation, + Viewport, +}; + +use iced_native::{ + layout, mouse, Background, Color, Layout, Point, Rectangle, Vector, Widget, +}; + +mod widget; + +/// A [`glow`] renderer. +/// +/// [`glow`]: https://github.com/grovesNL/glow +#[derive(Debug)] +pub struct Renderer { + quad_pipeline: quad::Pipeline, + text_pipeline: text::Pipeline, + triangle_pipeline: triangle::Pipeline, +} + +struct Layer<'a> { + bounds: Rectangle, + quads: Vec, + text: Vec>, + meshes: Vec<(Vector, Rectangle, &'a triangle::Mesh2D)>, +} + +impl<'a> Layer<'a> { + pub fn new(bounds: Rectangle) -> Self { + Self { + bounds, + quads: Vec::new(), + text: Vec::new(), + meshes: Vec::new(), + } + } + + pub fn intersection(&self, rectangle: Rectangle) -> Option> { + let layer_bounds: Rectangle = self.bounds.into(); + + layer_bounds.intersection(&rectangle).map(Into::into) + } +} + +impl Renderer { + /// Creates a new [`Renderer`]. + /// + /// [`Renderer`]: struct.Renderer.html + pub fn new(gl: &glow::Context, settings: Settings) -> Self { + let text_pipeline = text::Pipeline::new(gl, settings.default_font); + let quad_pipeline = quad::Pipeline::new(gl); + let triangle_pipeline = + triangle::Pipeline::new(gl, settings.antialiasing); + + Self { + quad_pipeline, + text_pipeline, + triangle_pipeline, + } + } + + /// Draws the provided primitives in the given [`Target`]. + /// + /// The text provided as overlay will be renderer on top of the primitives. + /// This is useful for rendering debug information. + /// + /// [`Target`]: struct.Target.html + pub fn draw>( + &mut self, + gl: &glow::Context, + viewport: &Viewport, + (primitive, mouse_interaction): &(Primitive, mouse::Interaction), + scale_factor: f64, + overlay: &[T], + ) -> mouse::Interaction { + let (width, height) = viewport.dimensions(); + let scale_factor = scale_factor as f32; + let transformation = viewport.transformation(); + + let mut layers = Vec::new(); + + layers.push(Layer::new(Rectangle { + x: 0, + y: 0, + width: (width as f32 / scale_factor).round() as u32, + height: (height as f32 / scale_factor).round() as u32, + })); + + self.draw_primitive(Vector::new(0.0, 0.0), primitive, &mut layers); + self.draw_overlay(overlay, &mut layers); + + for layer in layers { + self.flush( + gl, + viewport, + scale_factor, + transformation, + &layer, + width, + height, + ); + } + + *mouse_interaction + } + + fn draw_primitive<'a>( + &mut self, + translation: Vector, + primitive: &'a Primitive, + layers: &mut Vec>, + ) { + match primitive { + Primitive::None => {} + Primitive::Group { primitives } => { + // TODO: Inspect a bit and regroup (?) + for primitive in primitives { + self.draw_primitive(translation, primitive, layers) + } + } + Primitive::Text { + content, + bounds, + size, + color, + font, + horizontal_alignment, + vertical_alignment, + } => { + let layer = layers.last_mut().unwrap(); + + layer.text.push(glow_glyph::Section { + text: &content, + screen_position: ( + bounds.x + translation.x, + bounds.y + translation.y, + ), + bounds: (bounds.width, bounds.height), + scale: glow_glyph::Scale { x: *size, y: *size }, + color: color.into_linear(), + font_id: self.text_pipeline.find_font(*font), + layout: glow_glyph::Layout::default() + .h_align(match horizontal_alignment { + iced_native::HorizontalAlignment::Left => { + glow_glyph::HorizontalAlign::Left + } + iced_native::HorizontalAlignment::Center => { + glow_glyph::HorizontalAlign::Center + } + iced_native::HorizontalAlignment::Right => { + glow_glyph::HorizontalAlign::Right + } + }) + .v_align(match vertical_alignment { + iced_native::VerticalAlignment::Top => { + glow_glyph::VerticalAlign::Top + } + iced_native::VerticalAlignment::Center => { + glow_glyph::VerticalAlign::Center + } + iced_native::VerticalAlignment::Bottom => { + glow_glyph::VerticalAlign::Bottom + } + }), + ..Default::default() + }) + } + Primitive::Quad { + bounds, + background, + border_radius, + border_width, + border_color, + } => { + let layer = layers.last_mut().unwrap(); + + // TODO: Move some of these computations to the GPU (?) + layer.quads.push(Quad { + position: [ + bounds.x + translation.x, + bounds.y + translation.y, + ], + scale: [bounds.width, bounds.height], + color: match background { + Background::Color(color) => color.into_linear(), + }, + border_radius: *border_radius as f32, + border_width: *border_width as f32, + border_color: border_color.into_linear(), + }); + } + Primitive::Mesh2D { size, buffers } => { + let layer = layers.last_mut().unwrap(); + + // Only draw visible content + if let Some(clip_bounds) = layer.intersection(Rectangle::new( + Point::new(translation.x, translation.y), + *size, + )) { + layer.meshes.push(( + translation, + clip_bounds.into(), + buffers, + )); + } + } + Primitive::Clip { + bounds, + offset, + content, + } => { + let layer = layers.last_mut().unwrap(); + + // Only draw visible content + if let Some(clip_bounds) = + layer.intersection(*bounds + translation) + { + let clip_layer = Layer::new(clip_bounds.into()); + let new_layer = Layer::new(layer.bounds); + + layers.push(clip_layer); + self.draw_primitive( + translation + - Vector::new(offset.x as f32, offset.y as f32), + content, + layers, + ); + layers.push(new_layer); + } + } + Primitive::Translate { + translation: new_translation, + content, + } => { + self.draw_primitive( + translation + *new_translation, + &content, + layers, + ); + } + + Primitive::Cached { cache } => { + self.draw_primitive(translation, &cache, layers); + } + + #[cfg(feature = "image")] + Primitive::Image { handle, bounds } => { + let layer = layers.last_mut().unwrap(); + + layer.images.push(Image { + handle: image::Handle::Raster(handle.clone()), + position: [ + bounds.x + translation.x, + bounds.y + translation.y, + ], + size: [bounds.width, bounds.height], + }); + } + #[cfg(not(feature = "image"))] + Primitive::Image { .. } => {} + + #[cfg(feature = "svg")] + Primitive::Svg { handle, bounds } => { + let layer = layers.last_mut().unwrap(); + + layer.images.push(Image { + handle: image::Handle::Vector(handle.clone()), + position: [ + bounds.x + translation.x, + bounds.y + translation.y, + ], + size: [bounds.width, bounds.height], + }); + } + #[cfg(not(feature = "svg"))] + Primitive::Svg { .. } => {} + } + } + + fn draw_overlay<'a, T: AsRef>( + &mut self, + lines: &'a [T], + layers: &mut Vec>, + ) { + let first = layers.first().unwrap(); + let mut overlay = Layer::new(first.bounds); + + let font_id = self.text_pipeline.overlay_font(); + let scale = glow_glyph::Scale { x: 20.0, y: 20.0 }; + + for (i, line) in lines.iter().enumerate() { + overlay.text.push(glow_glyph::Section { + text: line.as_ref(), + screen_position: (11.0, 11.0 + 25.0 * i as f32), + color: [0.9, 0.9, 0.9, 1.0], + scale, + font_id, + ..glow_glyph::Section::default() + }); + + overlay.text.push(glow_glyph::Section { + text: line.as_ref(), + screen_position: (10.0, 10.0 + 25.0 * i as f32), + color: [0.0, 0.0, 0.0, 1.0], + scale, + font_id, + ..glow_glyph::Section::default() + }); + } + + layers.push(overlay); + } + + fn flush( + &mut self, + gl: &glow::Context, + viewport: &Viewport, + scale_factor: f32, + transformation: Transformation, + layer: &Layer<'_>, + target_width: u32, + target_height: u32, + ) { + let bounds = layer.bounds * scale_factor; + + if !layer.quads.is_empty() { + self.quad_pipeline.draw( + gl, + viewport, + &layer.quads, + transformation, + scale_factor, + bounds, + ); + } + + if !layer.meshes.is_empty() { + let scaled = transformation + * Transformation::scale(scale_factor, scale_factor); + + self.triangle_pipeline.draw( + gl, + target_width, + target_height, + scaled, + scale_factor, + &layer.meshes, + ); + } + + if !layer.text.is_empty() { + for text in layer.text.iter() { + // Target physical coordinates directly to avoid blurry text + let text = glow_glyph::Section { + // TODO: We `round` here to avoid rerasterizing text when + // its position changes slightly. This can make text feel a + // bit "jumpy". We may be able to do better once we improve + // our text rendering/caching pipeline. + screen_position: ( + (text.screen_position.0 * scale_factor).round(), + (text.screen_position.1 * scale_factor).round(), + ), + // TODO: Fix precision issues with some scale factors. + // + // The `ceil` here can cause some words to render on the + // same line when they should not. + // + // Ideally, `wgpu_glyph` should be able to compute layout + // using logical positions, and then apply the proper + // scaling when rendering. This would ensure that both + // measuring and rendering follow the same layout rules. + bounds: ( + (text.bounds.0 * scale_factor).ceil(), + (text.bounds.1 * scale_factor).ceil(), + ), + scale: glow_glyph::Scale { + x: text.scale.x * scale_factor, + y: text.scale.y * scale_factor, + }, + ..*text + }; + + self.text_pipeline.queue(text); + } + + self.text_pipeline.draw_queued( + gl, + transformation, + glow_glyph::Region { + x: bounds.x, + y: viewport.height() + - (bounds.y + bounds.height).min(viewport.height()), + width: bounds.width, + height: bounds.height, + }, + ); + } + } +} + +impl iced_native::Renderer for Renderer { + type Output = (Primitive, mouse::Interaction); + type Defaults = Defaults; + + fn layout<'a, Message>( + &mut self, + element: &iced_native::Element<'a, Message, Self>, + limits: &iced_native::layout::Limits, + ) -> iced_native::layout::Node { + let node = element.layout(self, limits); + + self.text_pipeline.clear_measurement_cache(); + + node + } +} + +impl layout::Debugger for Renderer { + fn explain( + &mut self, + defaults: &Defaults, + widget: &dyn Widget, + layout: Layout<'_>, + cursor_position: Point, + color: Color, + ) -> Self::Output { + let mut primitives = Vec::new(); + let (primitive, cursor) = + widget.draw(self, defaults, layout, cursor_position); + + explain_layout(layout, color, &mut primitives); + primitives.push(primitive); + + (Primitive::Group { primitives }, cursor) + } +} + +fn explain_layout( + layout: Layout<'_>, + color: Color, + primitives: &mut Vec, +) { + primitives.push(Primitive::Quad { + bounds: layout.bounds(), + background: Background::Color(Color::TRANSPARENT), + border_radius: 0, + border_width: 1, + border_color: [0.6, 0.6, 0.6, 0.5].into(), + }); + + for child in layout.children() { + explain_layout(child, color, primitives); + } +} diff --git a/glow/src/renderer/widget.rs b/glow/src/renderer/widget.rs new file mode 100644 index 00000000..37421fbe --- /dev/null +++ b/glow/src/renderer/widget.rs @@ -0,0 +1,19 @@ +mod button; +mod checkbox; +mod column; +mod container; +mod pane_grid; +mod progress_bar; +mod radio; +mod row; +mod scrollable; +mod slider; +mod space; +mod text; +mod text_input; + +#[cfg(feature = "svg")] +mod svg; + +#[cfg(feature = "image")] +mod image; diff --git a/glow/src/renderer/widget/button.rs b/glow/src/renderer/widget/button.rs new file mode 100644 index 00000000..eb225038 --- /dev/null +++ b/glow/src/renderer/widget/button.rs @@ -0,0 +1,93 @@ +use crate::{button::StyleSheet, defaults, Defaults, Primitive, Renderer}; +use iced_native::{ + mouse, Background, Color, Element, Layout, Point, Rectangle, Vector, +}; + +impl iced_native::button::Renderer for Renderer { + const DEFAULT_PADDING: u16 = 5; + + type Style = Box; + + fn draw( + &mut self, + _defaults: &Defaults, + bounds: Rectangle, + cursor_position: Point, + is_disabled: bool, + is_pressed: bool, + style: &Box, + content: &Element<'_, Message, Self>, + content_layout: Layout<'_>, + ) -> Self::Output { + let is_mouse_over = bounds.contains(cursor_position); + + let styling = if is_disabled { + style.disabled() + } else if is_mouse_over { + if is_pressed { + style.pressed() + } else { + style.hovered() + } + } else { + style.active() + }; + + let (content, _) = content.draw( + self, + &Defaults { + text: defaults::Text { + color: styling.text_color, + }, + }, + content_layout, + cursor_position, + ); + + ( + if styling.background.is_some() || styling.border_width > 0 { + let background = Primitive::Quad { + bounds, + background: styling + .background + .unwrap_or(Background::Color(Color::TRANSPARENT)), + border_radius: styling.border_radius, + border_width: styling.border_width, + border_color: styling.border_color, + }; + + if styling.shadow_offset == Vector::default() { + Primitive::Group { + primitives: vec![background, content], + } + } else { + // TODO: Implement proper shadow support + let shadow = Primitive::Quad { + bounds: Rectangle { + x: bounds.x + styling.shadow_offset.x, + y: bounds.y + styling.shadow_offset.y, + ..bounds + }, + background: Background::Color( + [0.0, 0.0, 0.0, 0.5].into(), + ), + border_radius: styling.border_radius, + border_width: 0, + border_color: Color::TRANSPARENT, + }; + + Primitive::Group { + primitives: vec![shadow, background, content], + } + } + } else { + content + }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/glow/src/renderer/widget/checkbox.rs b/glow/src/renderer/widget/checkbox.rs new file mode 100644 index 00000000..0340bf62 --- /dev/null +++ b/glow/src/renderer/widget/checkbox.rs @@ -0,0 +1,63 @@ +use crate::{checkbox::StyleSheet, Primitive, Renderer}; +use iced_native::{ + checkbox, mouse, HorizontalAlignment, Rectangle, VerticalAlignment, +}; + +impl checkbox::Renderer for Renderer { + type Style = Box; + + const DEFAULT_SIZE: u16 = 20; + const DEFAULT_SPACING: u16 = 15; + + fn draw( + &mut self, + bounds: Rectangle, + is_checked: bool, + is_mouse_over: bool, + (label, _): Self::Output, + style_sheet: &Self::Style, + ) -> Self::Output { + let style = if is_mouse_over { + style_sheet.hovered(is_checked) + } else { + style_sheet.active(is_checked) + }; + + let checkbox = Primitive::Quad { + bounds, + background: style.background, + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, + }; + + ( + Primitive::Group { + primitives: if is_checked { + let check = Primitive::Text { + content: crate::text::CHECKMARK_ICON.to_string(), + font: crate::text::BUILTIN_ICONS, + size: bounds.height * 0.7, + bounds: Rectangle { + x: bounds.center_x(), + y: bounds.center_y(), + ..bounds + }, + color: style.checkmark_color, + horizontal_alignment: HorizontalAlignment::Center, + vertical_alignment: VerticalAlignment::Center, + }; + + vec![checkbox, check, label] + } else { + vec![checkbox, label] + }, + }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/glow/src/renderer/widget/column.rs b/glow/src/renderer/widget/column.rs new file mode 100644 index 00000000..b853276d --- /dev/null +++ b/glow/src/renderer/widget/column.rs @@ -0,0 +1,34 @@ +use crate::{Primitive, Renderer}; +use iced_native::{column, mouse, Element, Layout, Point}; + +impl column::Renderer for Renderer { + fn draw( + &mut self, + defaults: &Self::Defaults, + content: &[Element<'_, Message, Self>], + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let mut mouse_interaction = mouse::Interaction::default(); + + ( + Primitive::Group { + primitives: content + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + let (primitive, new_mouse_interaction) = + child.draw(self, defaults, layout, cursor_position); + + if new_mouse_interaction > mouse_interaction { + mouse_interaction = new_mouse_interaction; + } + + primitive + }) + .collect(), + }, + mouse_interaction, + ) + } +} diff --git a/glow/src/renderer/widget/container.rs b/glow/src/renderer/widget/container.rs new file mode 100644 index 00000000..30cc3f07 --- /dev/null +++ b/glow/src/renderer/widget/container.rs @@ -0,0 +1,48 @@ +use crate::{container, defaults, Defaults, Primitive, Renderer}; +use iced_native::{Background, Color, Element, Layout, Point, Rectangle}; + +impl iced_native::container::Renderer for Renderer { + type Style = Box; + + fn draw( + &mut self, + defaults: &Defaults, + bounds: Rectangle, + cursor_position: Point, + style_sheet: &Self::Style, + content: &Element<'_, Message, Self>, + content_layout: Layout<'_>, + ) -> Self::Output { + let style = style_sheet.style(); + + let defaults = Defaults { + text: defaults::Text { + color: style.text_color.unwrap_or(defaults.text.color), + }, + }; + + let (content, mouse_interaction) = + content.draw(self, &defaults, content_layout, cursor_position); + + if style.background.is_some() || style.border_width > 0 { + let quad = Primitive::Quad { + bounds, + background: style + .background + .unwrap_or(Background::Color(Color::TRANSPARENT)), + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, + }; + + ( + Primitive::Group { + primitives: vec![quad, content], + }, + mouse_interaction, + ) + } else { + (content, mouse_interaction) + } + } +} diff --git a/glow/src/renderer/widget/image.rs b/glow/src/renderer/widget/image.rs new file mode 100644 index 00000000..c4c04984 --- /dev/null +++ b/glow/src/renderer/widget/image.rs @@ -0,0 +1,22 @@ +use crate::{Primitive, Renderer}; +use iced_native::{image, mouse, Layout}; + +impl image::Renderer for Renderer { + fn dimensions(&self, handle: &image::Handle) -> (u32, u32) { + self.image_pipeline.dimensions(handle) + } + + fn draw( + &mut self, + handle: image::Handle, + layout: Layout<'_>, + ) -> Self::Output { + ( + Primitive::Image { + handle, + bounds: layout.bounds(), + }, + mouse::Interaction::default(), + ) + } +} diff --git a/glow/src/renderer/widget/pane_grid.rs b/glow/src/renderer/widget/pane_grid.rs new file mode 100644 index 00000000..2253e4af --- /dev/null +++ b/glow/src/renderer/widget/pane_grid.rs @@ -0,0 +1,93 @@ +use crate::{Primitive, Renderer}; +use iced_native::{ + mouse, + pane_grid::{self, Axis, Pane}, + Element, Layout, Point, Rectangle, Vector, +}; + +impl pane_grid::Renderer for Renderer { + fn draw( + &mut self, + defaults: &Self::Defaults, + content: &[(Pane, Element<'_, Message, Self>)], + dragging: Option, + resizing: Option, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let pane_cursor_position = if dragging.is_some() { + // TODO: Remove once cursor availability is encoded in the type + // system + Point::new(-1.0, -1.0) + } else { + cursor_position + }; + + let mut mouse_interaction = mouse::Interaction::default(); + let mut dragged_pane = None; + + let mut panes: Vec<_> = content + .iter() + .zip(layout.children()) + .enumerate() + .map(|(i, ((id, pane), layout))| { + let (primitive, new_mouse_interaction) = + pane.draw(self, defaults, layout, pane_cursor_position); + + if new_mouse_interaction > mouse_interaction { + mouse_interaction = new_mouse_interaction; + } + + if Some(*id) == dragging { + dragged_pane = Some((i, layout)); + } + + primitive + }) + .collect(); + + let primitives = if let Some((index, layout)) = dragged_pane { + let pane = panes.remove(index); + let bounds = layout.bounds(); + + // TODO: Fix once proper layering is implemented. + // This is a pretty hacky way to achieve layering. + let clip = Primitive::Clip { + bounds: Rectangle { + x: cursor_position.x - bounds.width / 2.0, + y: cursor_position.y - bounds.height / 2.0, + width: bounds.width + 0.5, + height: bounds.height + 0.5, + }, + offset: Vector::new(0, 0), + content: Box::new(Primitive::Translate { + translation: Vector::new( + cursor_position.x - bounds.x - bounds.width / 2.0, + cursor_position.y - bounds.y - bounds.height / 2.0, + ), + content: Box::new(pane), + }), + }; + + panes.push(clip); + + panes + } else { + panes + }; + + ( + Primitive::Group { primitives }, + if dragging.is_some() { + mouse::Interaction::Grabbing + } else if let Some(axis) = resizing { + match axis { + Axis::Horizontal => mouse::Interaction::ResizingVertically, + Axis::Vertical => mouse::Interaction::ResizingHorizontally, + } + } else { + mouse_interaction + }, + ) + } +} diff --git a/glow/src/renderer/widget/progress_bar.rs b/glow/src/renderer/widget/progress_bar.rs new file mode 100644 index 00000000..2baeeb14 --- /dev/null +++ b/glow/src/renderer/widget/progress_bar.rs @@ -0,0 +1,54 @@ +use crate::{progress_bar::StyleSheet, Primitive, Renderer}; +use iced_native::{mouse, progress_bar, Color, Rectangle}; + +impl progress_bar::Renderer for Renderer { + type Style = Box; + + const DEFAULT_HEIGHT: u16 = 30; + + fn draw( + &self, + bounds: Rectangle, + range: std::ops::RangeInclusive, + value: f32, + style_sheet: &Self::Style, + ) -> Self::Output { + let style = style_sheet.style(); + + let (range_start, range_end) = range.into_inner(); + let active_progress_width = bounds.width + * ((value - range_start) / (range_end - range_start).max(1.0)); + + let background = Primitive::Group { + primitives: vec![Primitive::Quad { + bounds: Rectangle { ..bounds }, + background: style.background, + border_radius: style.border_radius, + border_width: 0, + border_color: Color::TRANSPARENT, + }], + }; + + ( + if active_progress_width > 0.0 { + let bar = Primitive::Quad { + bounds: Rectangle { + width: active_progress_width, + ..bounds + }, + background: style.bar, + border_radius: style.border_radius, + border_width: 0, + border_color: Color::TRANSPARENT, + }; + + Primitive::Group { + primitives: vec![background, bar], + } + } else { + background + }, + mouse::Interaction::default(), + ) + } +} diff --git a/glow/src/renderer/widget/radio.rs b/glow/src/renderer/widget/radio.rs new file mode 100644 index 00000000..cee0deb6 --- /dev/null +++ b/glow/src/renderer/widget/radio.rs @@ -0,0 +1,63 @@ +use crate::{radio::StyleSheet, Primitive, Renderer}; +use iced_native::{mouse, radio, Background, Color, Rectangle}; + +const SIZE: f32 = 28.0; +const DOT_SIZE: f32 = SIZE / 2.0; + +impl radio::Renderer for Renderer { + type Style = Box; + + const DEFAULT_SIZE: u16 = SIZE as u16; + const DEFAULT_SPACING: u16 = 15; + + fn draw( + &mut self, + bounds: Rectangle, + is_selected: bool, + is_mouse_over: bool, + (label, _): Self::Output, + style_sheet: &Self::Style, + ) -> Self::Output { + let style = if is_mouse_over { + style_sheet.hovered() + } else { + style_sheet.active() + }; + + let radio = Primitive::Quad { + bounds, + background: style.background, + border_radius: (SIZE / 2.0) as u16, + border_width: style.border_width, + border_color: style.border_color, + }; + + ( + Primitive::Group { + primitives: if is_selected { + let radio_circle = Primitive::Quad { + bounds: Rectangle { + x: bounds.x + DOT_SIZE / 2.0, + y: bounds.y + DOT_SIZE / 2.0, + width: bounds.width - DOT_SIZE, + height: bounds.height - DOT_SIZE, + }, + background: Background::Color(style.dot_color), + border_radius: (DOT_SIZE / 2.0) as u16, + border_width: 0, + border_color: Color::TRANSPARENT, + }; + + vec![radio, radio_circle, label] + } else { + vec![radio, label] + }, + }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/glow/src/renderer/widget/row.rs b/glow/src/renderer/widget/row.rs new file mode 100644 index 00000000..d0b7ef09 --- /dev/null +++ b/glow/src/renderer/widget/row.rs @@ -0,0 +1,34 @@ +use crate::{Primitive, Renderer}; +use iced_native::{mouse, row, Element, Layout, Point}; + +impl row::Renderer for Renderer { + fn draw( + &mut self, + defaults: &Self::Defaults, + children: &[Element<'_, Message, Self>], + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let mut mouse_interaction = mouse::Interaction::default(); + + ( + Primitive::Group { + primitives: children + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + let (primitive, new_mouse_interaction) = + child.draw(self, defaults, layout, cursor_position); + + if new_mouse_interaction > mouse_interaction { + mouse_interaction = new_mouse_interaction; + } + + primitive + }) + .collect(), + }, + mouse_interaction, + ) + } +} diff --git a/glow/src/renderer/widget/scrollable.rs b/glow/src/renderer/widget/scrollable.rs new file mode 100644 index 00000000..8a400b82 --- /dev/null +++ b/glow/src/renderer/widget/scrollable.rs @@ -0,0 +1,125 @@ +use crate::{Primitive, Renderer}; +use iced_native::{mouse, scrollable, Background, Color, Rectangle, Vector}; + +const SCROLLBAR_WIDTH: u16 = 10; +const SCROLLBAR_MARGIN: u16 = 2; + +impl scrollable::Renderer for Renderer { + type Style = Box; + + fn scrollbar( + &self, + bounds: Rectangle, + content_bounds: Rectangle, + offset: u32, + ) -> Option { + if content_bounds.height > bounds.height { + let scrollbar_bounds = Rectangle { + x: bounds.x + bounds.width + - f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), + y: bounds.y, + width: f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), + height: bounds.height, + }; + + let ratio = bounds.height / content_bounds.height; + let scrollbar_height = bounds.height * ratio; + let y_offset = offset as f32 * ratio; + + let scroller_bounds = Rectangle { + x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), + y: scrollbar_bounds.y + y_offset, + width: scrollbar_bounds.width - f32::from(2 * SCROLLBAR_MARGIN), + height: scrollbar_height, + }; + + Some(scrollable::Scrollbar { + bounds: scrollbar_bounds, + scroller: scrollable::Scroller { + bounds: scroller_bounds, + }, + }) + } else { + None + } + } + + fn draw( + &mut self, + state: &scrollable::State, + bounds: Rectangle, + _content_bounds: Rectangle, + is_mouse_over: bool, + is_mouse_over_scrollbar: bool, + scrollbar: Option, + offset: u32, + style_sheet: &Self::Style, + (content, mouse_interaction): Self::Output, + ) -> Self::Output { + ( + if let Some(scrollbar) = scrollbar { + let clip = Primitive::Clip { + bounds, + offset: Vector::new(0, offset), + content: Box::new(content), + }; + + let style = if state.is_scroller_grabbed() { + style_sheet.dragging() + } else if is_mouse_over_scrollbar { + style_sheet.hovered() + } else { + style_sheet.active() + }; + + let is_scrollbar_visible = + style.background.is_some() || style.border_width > 0; + + let scroller = if is_mouse_over + || state.is_scroller_grabbed() + || is_scrollbar_visible + { + Primitive::Quad { + bounds: scrollbar.scroller.bounds, + background: Background::Color(style.scroller.color), + border_radius: style.scroller.border_radius, + border_width: style.scroller.border_width, + border_color: style.scroller.border_color, + } + } else { + Primitive::None + }; + + let scrollbar = if is_scrollbar_visible { + Primitive::Quad { + bounds: Rectangle { + x: scrollbar.bounds.x + f32::from(SCROLLBAR_MARGIN), + width: scrollbar.bounds.width + - f32::from(2 * SCROLLBAR_MARGIN), + ..scrollbar.bounds + }, + background: style + .background + .unwrap_or(Background::Color(Color::TRANSPARENT)), + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, + } + } else { + Primitive::None + }; + + Primitive::Group { + primitives: vec![clip, scrollbar, scroller], + } + } else { + content + }, + if is_mouse_over_scrollbar || state.is_scroller_grabbed() { + mouse::Interaction::Idle + } else { + mouse_interaction + }, + ) + } +} diff --git a/glow/src/renderer/widget/slider.rs b/glow/src/renderer/widget/slider.rs new file mode 100644 index 00000000..220feace --- /dev/null +++ b/glow/src/renderer/widget/slider.rs @@ -0,0 +1,106 @@ +use crate::{ + slider::{HandleShape, StyleSheet}, + Primitive, Renderer, +}; +use iced_native::{mouse, slider, Background, Color, Point, Rectangle}; + +const HANDLE_HEIGHT: f32 = 22.0; + +impl slider::Renderer for Renderer { + type Style = Box; + + fn height(&self) -> u32 { + 30 + } + + fn draw( + &mut self, + bounds: Rectangle, + cursor_position: Point, + range: std::ops::RangeInclusive, + value: f32, + is_dragging: bool, + style_sheet: &Self::Style, + ) -> Self::Output { + let is_mouse_over = bounds.contains(cursor_position); + + let style = if is_dragging { + style_sheet.dragging() + } else if is_mouse_over { + style_sheet.hovered() + } else { + style_sheet.active() + }; + + let rail_y = bounds.y + (bounds.height / 2.0).round(); + + let (rail_top, rail_bottom) = ( + Primitive::Quad { + bounds: Rectangle { + x: bounds.x, + y: rail_y, + width: bounds.width, + height: 2.0, + }, + background: Background::Color(style.rail_colors.0), + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + Primitive::Quad { + bounds: Rectangle { + x: bounds.x, + y: rail_y + 2.0, + width: bounds.width, + height: 2.0, + }, + background: Background::Color(style.rail_colors.1), + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + ); + + let (range_start, range_end) = range.into_inner(); + + let (handle_width, handle_height, handle_border_radius) = + match style.handle.shape { + HandleShape::Circle { radius } => { + (f32::from(radius * 2), f32::from(radius * 2), radius) + } + HandleShape::Rectangle { + width, + border_radius, + } => (f32::from(width), HANDLE_HEIGHT, border_radius), + }; + + let handle_offset = (bounds.width - handle_width) + * ((value - range_start) / (range_end - range_start).max(1.0)); + + let handle = Primitive::Quad { + bounds: Rectangle { + x: bounds.x + handle_offset.round(), + y: rail_y - handle_height / 2.0, + width: handle_width, + height: handle_height, + }, + background: Background::Color(style.handle.color), + border_radius: handle_border_radius, + border_width: style.handle.border_width, + border_color: style.handle.border_color, + }; + + ( + Primitive::Group { + primitives: vec![rail_top, rail_bottom, handle], + }, + if is_dragging { + mouse::Interaction::Grabbing + } else if is_mouse_over { + mouse::Interaction::Grab + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/glow/src/renderer/widget/space.rs b/glow/src/renderer/widget/space.rs new file mode 100644 index 00000000..225f7e6c --- /dev/null +++ b/glow/src/renderer/widget/space.rs @@ -0,0 +1,8 @@ +use crate::{Primitive, Renderer}; +use iced_native::{mouse, space, Rectangle}; + +impl space::Renderer for Renderer { + fn draw(&mut self, _bounds: Rectangle) -> Self::Output { + (Primitive::None, mouse::Interaction::default()) + } +} diff --git a/glow/src/renderer/widget/svg.rs b/glow/src/renderer/widget/svg.rs new file mode 100644 index 00000000..f6d6d0ba --- /dev/null +++ b/glow/src/renderer/widget/svg.rs @@ -0,0 +1,22 @@ +use crate::{Primitive, Renderer}; +use iced_native::{mouse, svg, Layout}; + +impl svg::Renderer for Renderer { + fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) { + self.image_pipeline.viewport_dimensions(handle) + } + + fn draw( + &mut self, + handle: svg::Handle, + layout: Layout<'_>, + ) -> Self::Output { + ( + Primitive::Svg { + handle, + bounds: layout.bounds(), + }, + mouse::Interaction::default(), + ) + } +} diff --git a/glow/src/renderer/widget/text.rs b/glow/src/renderer/widget/text.rs new file mode 100644 index 00000000..4605ed06 --- /dev/null +++ b/glow/src/renderer/widget/text.rs @@ -0,0 +1,61 @@ +use crate::{Primitive, Renderer}; +use iced_native::{ + mouse, text, Color, Font, HorizontalAlignment, Rectangle, Size, + VerticalAlignment, +}; + +use std::f32; + +impl text::Renderer for Renderer { + type Font = Font; + + const DEFAULT_SIZE: u16 = 20; + + fn measure( + &self, + content: &str, + size: u16, + font: Font, + bounds: Size, + ) -> (f32, f32) { + self.text_pipeline + .measure(content, f32::from(size), font, bounds) + } + + fn draw( + &mut self, + defaults: &Self::Defaults, + bounds: Rectangle, + content: &str, + size: u16, + font: Font, + color: Option, + horizontal_alignment: HorizontalAlignment, + vertical_alignment: VerticalAlignment, + ) -> Self::Output { + let x = match horizontal_alignment { + iced_native::HorizontalAlignment::Left => bounds.x, + iced_native::HorizontalAlignment::Center => bounds.center_x(), + iced_native::HorizontalAlignment::Right => bounds.x + bounds.width, + }; + + let y = match vertical_alignment { + iced_native::VerticalAlignment::Top => bounds.y, + iced_native::VerticalAlignment::Center => bounds.center_y(), + iced_native::VerticalAlignment::Bottom => bounds.y + bounds.height, + }; + + ( + Primitive::Text { + content: content.to_string(), + size: f32::from(size), + bounds: Rectangle { x, y, ..bounds }, + color: color.unwrap_or(defaults.text.color), + font, + horizontal_alignment, + vertical_alignment, + }, + mouse::Interaction::default(), + ) + } +} diff --git a/glow/src/renderer/widget/text_input.rs b/glow/src/renderer/widget/text_input.rs new file mode 100644 index 00000000..57be6692 --- /dev/null +++ b/glow/src/renderer/widget/text_input.rs @@ -0,0 +1,261 @@ +use crate::{text_input::StyleSheet, Primitive, Renderer}; + +use iced_native::{ + mouse, + text_input::{self, cursor}, + Background, Color, Font, HorizontalAlignment, Point, Rectangle, Size, + Vector, VerticalAlignment, +}; +use std::f32; + +impl text_input::Renderer for Renderer { + type Style = Box; + + fn default_size(&self) -> u16 { + // TODO: Make this configurable + 20 + } + + fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 { + let (mut width, _) = self.text_pipeline.measure( + value, + f32::from(size), + font, + Size::INFINITY, + ); + + let spaces_around = value.len() - value.trim().len(); + + if spaces_around > 0 { + let space_width = self.text_pipeline.space_width(size as f32); + width += spaces_around as f32 * space_width; + } + + width + } + + fn offset( + &self, + text_bounds: Rectangle, + font: Font, + size: u16, + value: &text_input::Value, + state: &text_input::State, + ) -> f32 { + if state.is_focused() { + let cursor = state.cursor(); + + let focus_position = match cursor.state(value) { + cursor::State::Index(i) => i, + cursor::State::Selection { end, .. } => end, + }; + + let (_, offset) = measure_cursor_and_scroll_offset( + self, + text_bounds, + value, + size, + focus_position, + font, + ); + + offset + } else { + 0.0 + } + } + + fn draw( + &mut self, + bounds: Rectangle, + text_bounds: Rectangle, + cursor_position: Point, + font: Font, + size: u16, + placeholder: &str, + value: &text_input::Value, + state: &text_input::State, + style_sheet: &Self::Style, + ) -> Self::Output { + let is_mouse_over = bounds.contains(cursor_position); + + let style = if state.is_focused() { + style_sheet.focused() + } else if is_mouse_over { + style_sheet.hovered() + } else { + style_sheet.active() + }; + + let input = Primitive::Quad { + bounds, + background: style.background, + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, + }; + + let text = value.to_string(); + + let text_value = Primitive::Text { + content: if text.is_empty() { + placeholder.to_string() + } else { + text.clone() + }, + color: if text.is_empty() { + style_sheet.placeholder_color() + } else { + style_sheet.value_color() + }, + font, + bounds: Rectangle { + y: text_bounds.center_y(), + width: f32::INFINITY, + ..text_bounds + }, + size: f32::from(size), + horizontal_alignment: HorizontalAlignment::Left, + vertical_alignment: VerticalAlignment::Center, + }; + + let (contents_primitive, offset) = if state.is_focused() { + let cursor = state.cursor(); + + let (cursor_primitive, offset) = match cursor.state(value) { + cursor::State::Index(position) => { + let (text_value_width, offset) = + measure_cursor_and_scroll_offset( + self, + text_bounds, + value, + size, + position, + font, + ); + + ( + Primitive::Quad { + bounds: Rectangle { + x: text_bounds.x + text_value_width, + y: text_bounds.y, + width: 1.0, + height: text_bounds.height, + }, + background: Background::Color( + style_sheet.value_color(), + ), + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + offset, + ) + } + cursor::State::Selection { start, end } => { + let left = start.min(end); + let right = end.max(start); + + let (left_position, left_offset) = + measure_cursor_and_scroll_offset( + self, + text_bounds, + value, + size, + left, + font, + ); + + let (right_position, right_offset) = + measure_cursor_and_scroll_offset( + self, + text_bounds, + value, + size, + right, + font, + ); + + let width = right_position - left_position; + + ( + Primitive::Quad { + bounds: Rectangle { + x: text_bounds.x + left_position, + y: text_bounds.y, + width, + height: text_bounds.height, + }, + background: Background::Color( + style_sheet.selection_color(), + ), + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + if end == right { + right_offset + } else { + left_offset + }, + ) + } + }; + + ( + Primitive::Group { + primitives: vec![cursor_primitive, text_value], + }, + Vector::new(offset as u32, 0), + ) + } else { + (text_value, Vector::new(0, 0)) + }; + + let text_width = self.measure_value( + if text.is_empty() { placeholder } else { &text }, + size, + font, + ); + + let contents = if text_width > text_bounds.width { + Primitive::Clip { + bounds: text_bounds, + offset, + content: Box::new(contents_primitive), + } + } else { + contents_primitive + }; + + ( + Primitive::Group { + primitives: vec![input, contents], + }, + if is_mouse_over { + mouse::Interaction::Text + } else { + mouse::Interaction::default() + }, + ) + } +} + +fn measure_cursor_and_scroll_offset( + renderer: &Renderer, + text_bounds: Rectangle, + value: &text_input::Value, + size: u16, + cursor_index: usize, + font: Font, +) -> (f32, f32) { + use iced_native::text_input::Renderer; + + let text_before_cursor = value.until(cursor_index).to_string(); + + let text_value_width = + renderer.measure_value(&text_before_cursor, size, font); + let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0); + + (text_value_width, offset) +} diff --git a/glow/src/settings.rs b/glow/src/settings.rs new file mode 100644 index 00000000..bffc867e --- /dev/null +++ b/glow/src/settings.rs @@ -0,0 +1,50 @@ +//! Configure a [`Renderer`]. +//! +//! [`Renderer`]: struct.Renderer.html + +/// The settings of a [`Renderer`]. +/// +/// [`Renderer`]: ../struct.Renderer.html +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Settings { + /// The bytes of the font that will be used by default. + /// + /// If `None` is provided, a default system font will be chosen. + pub default_font: Option<&'static [u8]>, + + /// The antialiasing strategy that will be used for triangle primitives. + pub antialiasing: Option, +} + +impl Default for Settings { + fn default() -> Settings { + Settings { + default_font: None, + antialiasing: None, + } + } +} + +/// An antialiasing strategy. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Antialiasing { + /// Multisample AA with 2 samples + MSAAx2, + /// Multisample AA with 4 samples + MSAAx4, + /// Multisample AA with 8 samples + MSAAx8, + /// Multisample AA with 16 samples + MSAAx16, +} + +impl Antialiasing { + pub(crate) fn sample_count(self) -> u32 { + match self { + Antialiasing::MSAAx2 => 2, + Antialiasing::MSAAx4 => 4, + Antialiasing::MSAAx8 => 8, + Antialiasing::MSAAx16 => 16, + } + } +} diff --git a/glow/src/shader/quad.frag b/glow/src/shader/quad.frag new file mode 100644 index 00000000..d9e74664 --- /dev/null +++ b/glow/src/shader/quad.frag @@ -0,0 +1,67 @@ +#version 450 + +layout(origin_upper_left) in vec4 gl_FragCoord; +layout(location = 0) in vec4 v_Color; +layout(location = 1) in vec4 v_BorderColor; +layout(location = 2) in vec2 v_Pos; +layout(location = 3) in vec2 v_Scale; +layout(location = 4) in float v_BorderRadius; +layout(location = 5) in float v_BorderWidth; + +layout(location = 0) out vec4 o_Color; + +float distance(in vec2 frag_coord, in vec2 position, in vec2 size, float radius) +{ + // TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN + vec2 inner_size = size - vec2(radius, radius) * 2.0; + vec2 top_left = position + vec2(radius, radius); + vec2 bottom_right = top_left + inner_size; + + vec2 top_left_distance = top_left - frag_coord; + vec2 bottom_right_distance = frag_coord - bottom_right; + + vec2 distance = vec2( + max(max(top_left_distance.x, bottom_right_distance.x), 0), + max(max(top_left_distance.y, bottom_right_distance.y), 0) + ); + + return sqrt(distance.x * distance.x + distance.y * distance.y); +} + +void main() { + vec4 mixed_color; + + // TODO: Remove branching (?) + if(v_BorderWidth > 0) { + float internal_border = max(v_BorderRadius - v_BorderWidth, 0); + + float internal_distance = distance( + gl_FragCoord.xy, + v_Pos + vec2(v_BorderWidth), + v_Scale - vec2(v_BorderWidth * 2.0), + internal_border + ); + + float border_mix = smoothstep( + max(internal_border - 0.5, 0.0), + internal_border + 0.5, + internal_distance + ); + + mixed_color = mix(v_Color, v_BorderColor, border_mix); + } else { + mixed_color = v_Color; + } + + float d = distance( + gl_FragCoord.xy, + v_Pos, + v_Scale, + v_BorderRadius + ); + + float radius_alpha = + 1.0 - smoothstep(max(v_BorderRadius - 0.5, 0), v_BorderRadius + 0.5, d); + + o_Color = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); +} diff --git a/glow/src/shader/quad.vert b/glow/src/shader/quad.vert new file mode 100644 index 00000000..2d2ebc3d --- /dev/null +++ b/glow/src/shader/quad.vert @@ -0,0 +1,47 @@ +#version 450 + +layout(location = 0) uniform mat4 u_Transform; +layout(location = 1) uniform float u_Scale; + +layout(location = 0) in vec2 i_Pos; +layout(location = 1) in vec2 i_Scale; +layout(location = 2) in vec4 i_Color; +layout(location = 3) in vec4 i_BorderColor; +layout(location = 4) in float i_BorderRadius; +layout(location = 5) in float i_BorderWidth; + +layout(location = 0) out vec4 o_Color; +layout(location = 1) out vec4 o_BorderColor; +layout(location = 2) out vec2 o_Pos; +layout(location = 3) out vec2 o_Scale; +layout(location = 4) out float o_BorderRadius; +layout(location = 5) out float o_BorderWidth; + +const vec2 positions[4] = vec2[]( + vec2(0.0, 0.0), + vec2(0.0, 1.0), + vec2(1.0, 0.0), + vec2(1.0, 1.0) +); + +void main() { + vec2 v_Pos = positions[gl_VertexID]; + vec2 p_Pos = i_Pos * u_Scale; + vec2 p_Scale = i_Scale * u_Scale; + + mat4 i_Transform = mat4( + vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), + vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0) + ); + + o_Color = i_Color; + o_BorderColor = i_BorderColor; + o_Pos = p_Pos; + o_Scale = p_Scale; + o_BorderRadius = i_BorderRadius * u_Scale; + o_BorderWidth = i_BorderWidth * u_Scale; + + gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0); +} diff --git a/glow/src/text.rs b/glow/src/text.rs new file mode 100644 index 00000000..bda619fc --- /dev/null +++ b/glow/src/text.rs @@ -0,0 +1,180 @@ +mod font; + +use crate::Transformation; + +use std::{cell::RefCell, collections::HashMap}; + +pub const BUILTIN_ICONS: iced_native::Font = iced_native::Font::External { + name: "iced_glow icons", + bytes: include_bytes!("text/icons.ttf"), +}; + +pub const CHECKMARK_ICON: char = '\u{F00C}'; + +const FALLBACK_FONT: &[u8] = + include_bytes!("../../wgpu/fonts/Lato-Regular.ttf"); + +#[derive(Debug)] +pub struct Pipeline { + draw_brush: RefCell>, + draw_font_map: RefCell>, + + measure_brush: RefCell>, +} + +impl Pipeline { + pub fn new(gl: &glow::Context, default_font: Option<&[u8]>) -> Self { + // TODO: Font customization + let font_source = font::Source::new(); + + let default_font = + default_font.map(|slice| slice.to_vec()).unwrap_or_else(|| { + font_source + .load(&[font::Family::SansSerif, font::Family::Serif]) + .unwrap_or_else(|_| FALLBACK_FONT.to_vec()) + }); + + let load_glyph_brush = |font: Vec| { + let builder = + glow_glyph::GlyphBrushBuilder::using_fonts_bytes(vec![ + font.clone() + ])?; + + Ok(( + builder, + glyph_brush::GlyphBrushBuilder::using_font_bytes(font).build(), + )) + }; + + let (brush_builder, measure_brush) = load_glyph_brush(default_font) + .unwrap_or_else(|_: glow_glyph::rusttype::Error| { + log::warn!("System font failed to load. Falling back to embedded font..."); + + load_glyph_brush(FALLBACK_FONT.to_vec()).expect("Load fallback font") + }); + + let draw_brush = + brush_builder.initial_cache_size((2048, 2048)).build(gl); + + Pipeline { + draw_brush: RefCell::new(draw_brush), + draw_font_map: RefCell::new(HashMap::new()), + + measure_brush: RefCell::new(measure_brush), + } + } + + pub fn overlay_font(&self) -> glow_glyph::FontId { + glow_glyph::FontId(0) + } + + pub fn queue(&mut self, section: glow_glyph::Section<'_>) { + self.draw_brush.borrow_mut().queue(section); + } + + pub fn draw_queued( + &mut self, + gl: &glow::Context, + transformation: Transformation, + region: glow_glyph::Region, + ) { + self.draw_brush + .borrow_mut() + .draw_queued_with_transform_and_scissoring( + gl, + transformation.into(), + region, + ) + .expect("Draw text"); + } + + pub fn measure( + &self, + content: &str, + size: f32, + font: iced_native::Font, + bounds: iced_native::Size, + ) -> (f32, f32) { + use glow_glyph::GlyphCruncher; + + let glow_glyph::FontId(font_id) = self.find_font(font); + + let section = glow_glyph::Section { + text: content, + scale: glow_glyph::Scale { x: size, y: size }, + bounds: (bounds.width, bounds.height), + font_id: glow_glyph::FontId(font_id), + ..Default::default() + }; + + if let Some(bounds) = + self.measure_brush.borrow_mut().glyph_bounds(section) + { + (bounds.width().ceil(), bounds.height().ceil()) + } else { + (0.0, 0.0) + } + } + + pub fn space_width(&self, size: f32) -> f32 { + use glow_glyph::GlyphCruncher; + + let glyph_brush = self.measure_brush.borrow(); + + // TODO: Select appropriate font + let font = &glyph_brush.fonts()[0]; + + font.glyph(' ') + .scaled(glow_glyph::Scale { x: size, y: size }) + .h_metrics() + .advance_width + } + + pub fn clear_measurement_cache(&mut self) { + // TODO: We should probably use a `GlyphCalculator` for this. However, + // it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop. + // This makes stuff quite inconvenient. A manual method for trimming the + // cache would make our lives easier. + loop { + let action = self + .measure_brush + .borrow_mut() + .process_queued(|_, _| {}, |_| {}); + + match action { + Ok(_) => break, + Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => { + let (width, height) = suggested; + + self.measure_brush + .borrow_mut() + .resize_texture(width, height); + } + } + } + } + + pub fn find_font(&self, font: iced_native::Font) -> glow_glyph::FontId { + match font { + iced_native::Font::Default => glow_glyph::FontId(0), + iced_native::Font::External { name, bytes } => { + if let Some(font_id) = self.draw_font_map.borrow().get(name) { + return *font_id; + } + + // TODO: Find a way to share font data + let _ = self.measure_brush.borrow_mut().add_font_bytes(bytes); + + let font_id = + self.draw_brush.borrow_mut().add_font_bytes(bytes); + + let _ = self + .draw_font_map + .borrow_mut() + .insert(String::from(name), font_id); + + font_id + } + } + } +} diff --git a/glow/src/text/font.rs b/glow/src/text/font.rs new file mode 100644 index 00000000..7346ccdb --- /dev/null +++ b/glow/src/text/font.rs @@ -0,0 +1,37 @@ +pub use font_kit::{ + error::SelectionError as LoadError, family_name::FamilyName as Family, +}; + +pub struct Source { + raw: font_kit::source::SystemSource, +} + +impl Source { + pub fn new() -> Self { + Source { + raw: font_kit::source::SystemSource::new(), + } + } + + pub fn load(&self, families: &[Family]) -> Result, LoadError> { + let font = self.raw.select_best_match( + families, + &font_kit::properties::Properties::default(), + )?; + + match font { + font_kit::handle::Handle::Path { path, .. } => { + use std::io::Read; + + let mut buf = Vec::new(); + let mut reader = std::fs::File::open(path).expect("Read font"); + let _ = reader.read_to_end(&mut buf); + + Ok(buf) + } + font_kit::handle::Handle::Memory { bytes, .. } => { + Ok(bytes.as_ref().clone()) + } + } + } +} diff --git a/glow/src/text/icons.ttf b/glow/src/text/icons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1c832f86576e51451b729a7fe513616dea38d21a GIT binary patch literal 4912 zcmd^CU2I!P6+UyX?W9Q>nA@MRd1*udB^${dM2*d*~2nmo@z{9eFH`wx>@wMBu zDJ$(OH}#zFob%1hnKNhR-jX1qFug-2)pHjvrsbDM-iGDh_$|DC;qb()J@*A7k%xY% zwC2|TarC#pg8qH{>ZNN<8T#G*08!6V@XKm_X|45*cOB@HME&(8ccTt_1Ui1yyR>?v z`kN1a+Dl|IzrI{?%hA6@{sjFd^yAAg^h}u-q2GgkV0o>1z310JWd0dV?`o~&5=Msp zcQm0j_j;Xv(EnZN|Aa2r-L=XuzOye*6c{Elep#<=H0z%ih%dzb?Ruk9|I3SspAhXr z{PZQ_wfE6Dogrgp_WS}3uDZ>2+Dqu6AND_t#p=qEONd|h6!5WEz#=Hrv6}CRcPWVZ z-!?E4eWfkMAL)oNV5Oi5>o!lmnl01x`vdO}nDh6q(q(g=z6Bb`(0(u`b_wgi5A%uz z-}f4<%(5Z-v^_xkH7CIK_XqCZ@3hd{5tHC}|KFbhoq(tPH0WG$rl1R*Q8I5C-=^L4 z%=F$cEFwpSFly|qfm0pcGi(O;B*bu!2%A|J|#9r^ID;qZCL z?}>G^=Oacm3P(6BqFRmI617O=d|1zi2)wVK`p=c{)W4|Te2?S${Rh?C4@>hsM1-?| zLTW$gqw9s*e6+A^c?|Zny|DB7VTM6bpJ||WqqG5As-Mp*KfCA;-EodVZ;#29eyrQ8 zhfe$0gnh=x0pRmK4pKjLsx$=q72n=R30m@TKMm10xq<^bFwa%G_qGj@przDvgbm+PXZF8t<5LmGw%) zZC1)W`o^`TiDt7ZtBu;4ob|_*^+xS#rPNFtZWA=G4 zme79{qck4#K03-Jup20z6+OB{Tq#8NC@Tlnx@NjMf->T1$G$<=Fxv#IP4wa@8#*dU zvwFk_@k-=_eOEC~3G9^4N)y(TbQph+&e>TXs;YBVa64}M;8Y8fjM|&<;&9`>3_r$u ziu50!HU25GrJ{?4tRv;QJ2W_VPK7R9E~uk1bC%#}zq> zGSil@A+DyKm{gOD zCLKq5ZIWA72VwFxsm9qq&dhuHf7v7w;*1Wm*WT>ur%X0I7Vjo|hrKoVeeF?10wH9u z;-bv9ESEPzXNqE+O(kOpvJ<5;M=W=yJw^W$x~l`|h0fijJ=|%4YsN}%_V=0DLOf>0 zow0aQ?X*3^$f~kClT?E?79gb>$h^Yq0j>tH20#Gfl-6Sr*D z6E@Hz8nTe$jG{A}LI_zZ&xUx z8j09`q-hvy8%iYAZrc+qqsTMMJ+^1Ee9HC$EI(;`L6&=MFT^rtdp#_lw!L1K&)D7$ zme1Oxb|yYXAo~!=vlu`k5SDQS!g4^auk8Ed=7!IJcvM89 zuJbV)>yH#=hI7H2=0d7NTvQ2LjV06=j*E>-&7q{WJuj9!Y4Kh4KN+Z(q)K-8Ktz>2 zro`@Xo#oWSS=s73Y|CRh&T()=_VMG2TVUI;*iO+O>N|Mmj5X;U7Q3;MBUm>A-L^{+ zRqIYB)r6heGnG`&e-srJRs#Jfwt}J~G9`0-GLgcUTdkax!%a}YosByW$Mtz3b`N9z zW4J=1sDAv@6tyFhsI*d+lv6E?b^L)_rrKku0LBDMsv;le>AAvfLz*&n+ZZ+XI%&S( zdT~`^KFny%;%H$Px1JLmYLRH)=J3JLf2BavW47oA6R5NPol;VbOHT&R*4I0q852S@!J zSW`&Nq&BNwoL(tstsGCryLCdRj2GZ%lokppIfWM=N9-Fh*n^aZMxeb6SNGN0-l)fm zt7!QR6)V*)r%^TOCidP(xfSEgZ+kCdRg;R08u)>vVAecCyRGW#Lq$a7fxQzKF{L z7j(B$JL~=EQp{~v7$2wlIB$HfOj(n$_(S_0cl;RBoQzIVGraydXP!g!*U??_43@mR zeK_$d$2lBIsUtXx*(ca%ao39BAvFO`&Q?buUgE4}kykm3cUC7)FWX#>>LsN06?>ED zMSybvg28!vQ)tVp0NU~zbJB3V&Kw4L<}jFJ4ug5-oB_DN90m)_VQ`T-3@$Ne8sJmR zVNhTW1BW>bE;AQ!5v=u+QgMN?NbV(OYkOkKCt$u70Vil){z zV(RBKVrs*_y(3_BzKqfd)mu^Kf&6uT)g%cY**}}d2Z;C@F6H)Tb={+28mp%<2DkeR z(c4CYq){k+*SH7w4p@8AcWJtfO`7ozh?{fxYP?zS%<_yk%G%xDckrLk>6;~dI)Q?Z XW9RmOAqK|oi2Gkufv@64F+=oU3sq^e literal 0 HcmV?d00001 diff --git a/glow/src/transformation.rs b/glow/src/transformation.rs new file mode 100644 index 00000000..ff3b1d00 --- /dev/null +++ b/glow/src/transformation.rs @@ -0,0 +1,54 @@ +use glam::{Mat4, Vec3, Vec4}; +use std::ops::Mul; + +/// A 2D transformation matrix. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Transformation(Mat4); + +impl Transformation { + /// Get the identity transformation. + pub fn identity() -> Transformation { + Transformation(Mat4::identity()) + } + + /// Creates an orthographic projection. + #[rustfmt::skip] + pub fn orthographic(width: u32, height: u32) -> Transformation { + Transformation(Mat4::from_cols( + Vec4::new(2.0 / width as f32, 0.0, 0.0, 0.0), + Vec4::new(0.0, -2.0 / height as f32, 0.0, 0.0), + Vec4::new(0.0, 0.0, -1.0, 0.0), + Vec4::new(-1.0, 1.0, 0.0, 1.0) + )) + } + + /// Creates a translate transformation. + pub fn translate(x: f32, y: f32) -> Transformation { + Transformation(Mat4::from_translation(Vec3::new(x, y, 0.0))) + } + + /// Creates a scale transformation. + pub fn scale(x: f32, y: f32) -> Transformation { + Transformation(Mat4::from_scale(Vec3::new(x, y, 1.0))) + } +} + +impl Mul for Transformation { + type Output = Self; + + fn mul(self, rhs: Self) -> Self { + Transformation(self.0 * rhs.0) + } +} + +impl AsRef<[f32; 16]> for Transformation { + fn as_ref(&self) -> &[f32; 16] { + self.0.as_ref() + } +} + +impl From for [f32; 16] { + fn from(t: Transformation) -> [f32; 16] { + *t.as_ref() + } +} diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs new file mode 100644 index 00000000..f71022d0 --- /dev/null +++ b/glow/src/triangle.rs @@ -0,0 +1,84 @@ +//! Draw meshes of triangles. +use crate::{settings, Transformation}; +use iced_native::{Rectangle, Vector}; +use std::mem; + +const UNIFORM_BUFFER_SIZE: usize = 100; +const VERTEX_BUFFER_SIZE: usize = 10_000; +const INDEX_BUFFER_SIZE: usize = 10_000; + +#[derive(Debug)] +pub(crate) struct Pipeline {} + +impl Pipeline { + pub fn new( + gl: &glow::Context, + antialiasing: Option, + ) -> Pipeline { + Pipeline {} + } + + pub fn draw( + &mut self, + gl: &glow::Context, + target_width: u32, + target_height: u32, + transformation: Transformation, + scale_factor: f32, + meshes: &[(Vector, Rectangle, &Mesh2D)], + ) { + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct Uniforms { + transform: [f32; 16], + // We need to align this to 256 bytes to please `wgpu`... + // TODO: Be smarter and stop wasting memory! + _padding_a: [f32; 32], + _padding_b: [f32; 16], +} + +impl Default for Uniforms { + fn default() -> Self { + Self { + transform: *Transformation::identity().as_ref(), + _padding_a: [0.0; 32], + _padding_b: [0.0; 16], + } + } +} + +impl From for Uniforms { + fn from(transformation: Transformation) -> Uniforms { + Self { + transform: transformation.into(), + _padding_a: [0.0; 32], + _padding_b: [0.0; 16], + } + } +} + +/// A two-dimensional vertex with some color in __linear__ RGBA. +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct Vertex2D { + /// The vertex position + pub position: [f32; 2], + /// The vertex color in __linear__ RGBA. + pub color: [f32; 4], +} + +/// A set of [`Vertex2D`] and indices representing a list of triangles. +/// +/// [`Vertex2D`]: struct.Vertex2D.html +#[derive(Clone, Debug)] +pub struct Mesh2D { + /// The vertices of the mesh + pub vertices: Vec, + /// The list of vertex indices that defines the triangles of the mesh. + /// + /// Therefore, this list should always have a length that is a multiple of 3. + pub indices: Vec, +} diff --git a/glow/src/viewport.rs b/glow/src/viewport.rs new file mode 100644 index 00000000..c1afee87 --- /dev/null +++ b/glow/src/viewport.rs @@ -0,0 +1,33 @@ +use crate::Transformation; + +/// A viewing region for displaying computer graphics. +#[derive(Debug)] +pub struct Viewport { + width: u32, + height: u32, + transformation: Transformation, +} + +impl Viewport { + /// Creates a new [`Viewport`] with the given dimensions. + pub fn new(width: u32, height: u32) -> Viewport { + Viewport { + width, + height, + transformation: Transformation::orthographic(width, height), + } + } + + pub fn height(&self) -> u32 { + self.height + } + + /// Returns the dimensions of the [`Viewport`]. + pub fn dimensions(&self) -> (u32, u32) { + (self.width, self.height) + } + + pub(crate) fn transformation(&self) -> Transformation { + self.transformation + } +} diff --git a/glow/src/widget.rs b/glow/src/widget.rs new file mode 100644 index 00000000..16e7ca88 --- /dev/null +++ b/glow/src/widget.rs @@ -0,0 +1,44 @@ +//! Use the widgets supported out-of-the-box. +//! +//! # Re-exports +//! For convenience, the contents of this module are available at the root +//! module. Therefore, you can directly type: +//! +//! ``` +//! use iced_glow::{button, Button}; +//! ``` +use crate::Renderer; + +pub mod button; +pub mod checkbox; +pub mod container; +pub mod pane_grid; +pub mod progress_bar; +pub mod radio; +pub mod scrollable; +pub mod slider; +pub mod text_input; + +#[doc(no_inline)] +pub use button::Button; +#[doc(no_inline)] +pub use checkbox::Checkbox; +#[doc(no_inline)] +pub use container::Container; +#[doc(no_inline)] +pub use pane_grid::PaneGrid; +#[doc(no_inline)] +pub use progress_bar::ProgressBar; +#[doc(no_inline)] +pub use radio::Radio; +#[doc(no_inline)] +pub use scrollable::Scrollable; +#[doc(no_inline)] +pub use slider::Slider; +#[doc(no_inline)] +pub use text_input::TextInput; + +pub use iced_native::{Image, Space, Text}; + +pub type Column<'a, Message> = iced_native::Column<'a, Message, Renderer>; +pub type Row<'a, Message> = iced_native::Row<'a, Message, Renderer>; diff --git a/glow/src/widget/button.rs b/glow/src/widget/button.rs new file mode 100644 index 00000000..b738c55e --- /dev/null +++ b/glow/src/widget/button.rs @@ -0,0 +1,15 @@ +//! Allow your users to perform actions by pressing a button. +//! +//! A [`Button`] has some local [`State`]. +//! +//! [`Button`]: type.Button.html +//! [`State`]: struct.State.html +use crate::Renderer; + +pub use iced_native::button::State; +pub use iced_style::button::{Style, StyleSheet}; + +/// A widget that produces a message when clicked. +/// +/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`. +pub type Button<'a, Message> = iced_native::Button<'a, Message, Renderer>; diff --git a/glow/src/widget/canvas.rs b/glow/src/widget/canvas.rs new file mode 100644 index 00000000..325f90ce --- /dev/null +++ b/glow/src/widget/canvas.rs @@ -0,0 +1,201 @@ +//! Draw 2D graphics for your users. +//! +//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a +//! [`Frame`]. It can be used for animation, data visualization, game graphics, +//! and more! +//! +//! [`Canvas`]: struct.Canvas.html +//! [`Frame`]: struct.Frame.html +use crate::{Defaults, Primitive, Renderer}; + +use iced_native::{ + layout, Element, Hasher, Layout, Length, MouseCursor, Point, Size, Widget, +}; +use std::hash::Hash; + +pub mod layer; +pub mod path; + +mod drawable; +mod fill; +mod frame; +mod stroke; +mod text; + +pub use drawable::Drawable; +pub use fill::Fill; +pub use frame::Frame; +pub use layer::Layer; +pub use path::Path; +pub use stroke::{LineCap, LineJoin, Stroke}; +pub use text::Text; + +/// A widget capable of drawing 2D graphics. +/// +/// A [`Canvas`] may contain multiple layers. A [`Layer`] is drawn using the +/// painter's algorithm. In other words, layers will be drawn on top of each +/// other in the same order they are pushed into the [`Canvas`]. +/// +/// [`Canvas`]: struct.Canvas.html +/// [`Layer`]: layer/trait.Layer.html +/// +/// # Examples +/// The repository has a couple of [examples] showcasing how to use a +/// [`Canvas`]: +/// +/// - [`clock`], an application that uses the [`Canvas`] widget to draw a clock +/// and its hands to display the current time. +/// - [`solar_system`], an animated solar system drawn using the [`Canvas`] widget +/// and showcasing how to compose different transforms. +/// +/// [examples]: https://github.com/hecrj/iced/tree/0.1/examples +/// [`clock`]: https://github.com/hecrj/iced/tree/0.1/examples/clock +/// [`solar_system`]: https://github.com/hecrj/iced/tree/0.1/examples/solar_system +/// +/// ## Drawing a simple circle +/// If you want to get a quick overview, here's how we can draw a simple circle: +/// +/// ```no_run +/// # mod iced { +/// # pub use iced_wgpu::canvas; +/// # pub use iced_native::Color; +/// # } +/// use iced::canvas::{self, layer, Canvas, Drawable, Fill, Frame, Path}; +/// use iced::Color; +/// +/// // First, we define the data we need for drawing +/// #[derive(Debug)] +/// struct Circle { +/// radius: f32, +/// } +/// +/// // Then, we implement the `Drawable` trait +/// impl Drawable for Circle { +/// fn draw(&self, frame: &mut Frame) { +/// // We create a `Path` representing a simple circle +/// let circle = Path::new(|p| p.circle(frame.center(), self.radius)); +/// +/// // And fill it with some color +/// frame.fill(&circle, Fill::Color(Color::BLACK)); +/// } +/// } +/// +/// // We can use a `Cache` to avoid unnecessary re-tessellation +/// let cache: layer::Cache = layer::Cache::new(); +/// +/// // Finally, we simply provide the data to our `Cache` and push the resulting +/// // layer into a `Canvas` +/// let canvas = Canvas::new() +/// .push(cache.with(&Circle { radius: 50.0 })); +/// ``` +#[derive(Debug)] +pub struct Canvas<'a> { + width: Length, + height: Length, + layers: Vec>, +} + +impl<'a> Canvas<'a> { + const DEFAULT_SIZE: u16 = 100; + + /// Creates a new [`Canvas`] with no layers. + /// + /// [`Canvas`]: struct.Canvas.html + pub fn new() -> Self { + Canvas { + width: Length::Units(Self::DEFAULT_SIZE), + height: Length::Units(Self::DEFAULT_SIZE), + layers: Vec::new(), + } + } + + /// Sets the width of the [`Canvas`]. + /// + /// [`Canvas`]: struct.Canvas.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Canvas`]. + /// + /// [`Canvas`]: struct.Canvas.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Adds a [`Layer`] to the [`Canvas`]. + /// + /// It will be drawn on top of previous layers. + /// + /// [`Layer`]: layer/trait.Layer.html + /// [`Canvas`]: struct.Canvas.html + pub fn push(mut self, layer: impl Layer + 'a) -> Self { + self.layers.push(Box::new(layer)); + self + } +} + +impl<'a, Message> Widget for Canvas<'a> { + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.width(self.width).height(self.height); + let size = limits.resolve(Size::ZERO); + + layout::Node::new(size) + } + + fn draw( + &self, + _renderer: &mut Renderer, + _defaults: &Defaults, + layout: Layout<'_>, + _cursor_position: Point, + ) -> (Primitive, MouseCursor) { + let bounds = layout.bounds(); + let origin = Point::new(bounds.x, bounds.y); + let size = Size::new(bounds.width, bounds.height); + + ( + Primitive::Group { + primitives: self + .layers + .iter() + .map(|layer| Primitive::Cached { + origin, + cache: layer.draw(size), + }) + .collect(), + }, + MouseCursor::Idle, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + std::any::TypeId::of::>().hash(state); + + self.width.hash(state); + self.height.hash(state); + } +} + +impl<'a, Message> From> for Element<'a, Message, Renderer> +where + Message: 'static, +{ + fn from(canvas: Canvas<'a>) -> Element<'a, Message, Renderer> { + Element::new(canvas) + } +} diff --git a/glow/src/widget/checkbox.rs b/glow/src/widget/checkbox.rs new file mode 100644 index 00000000..da0d7a84 --- /dev/null +++ b/glow/src/widget/checkbox.rs @@ -0,0 +1,9 @@ +//! Show toggle controls using checkboxes. +use crate::Renderer; + +pub use iced_style::checkbox::{Style, StyleSheet}; + +/// A box that can be checked. +/// +/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`. +pub type Checkbox = iced_native::Checkbox; diff --git a/glow/src/widget/container.rs b/glow/src/widget/container.rs new file mode 100644 index 00000000..9a93a246 --- /dev/null +++ b/glow/src/widget/container.rs @@ -0,0 +1,10 @@ +//! Decorate content and apply alignment. +use crate::Renderer; + +pub use iced_style::container::{Style, StyleSheet}; + +/// An element decorating some content. +/// +/// This is an alias of an `iced_native` container with a default +/// `Renderer`. +pub type Container<'a, Message> = iced_native::Container<'a, Message, Renderer>; diff --git a/glow/src/widget/pane_grid.rs b/glow/src/widget/pane_grid.rs new file mode 100644 index 00000000..578e8960 --- /dev/null +++ b/glow/src/widget/pane_grid.rs @@ -0,0 +1,24 @@ +//! Let your users split regions of your application and organize layout dynamically. +//! +//! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) +//! +//! # Example +//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, +//! drag and drop, and hotkey support. +//! +//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.1/examples/pane_grid +//! [`PaneGrid`]: type.PaneGrid.html +use crate::Renderer; + +pub use iced_native::pane_grid::{ + Axis, Direction, DragEvent, Focus, KeyPressEvent, Pane, ResizeEvent, Split, + State, +}; + +/// A collection of panes distributed using either vertical or horizontal splits +/// to completely fill the space available. +/// +/// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) +/// +/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`. +pub type PaneGrid<'a, Message> = iced_native::PaneGrid<'a, Message, Renderer>; diff --git a/glow/src/widget/progress_bar.rs b/glow/src/widget/progress_bar.rs new file mode 100644 index 00000000..34450b5e --- /dev/null +++ b/glow/src/widget/progress_bar.rs @@ -0,0 +1,15 @@ +//! Allow your users to perform actions by pressing a button. +//! +//! A [`Button`] has some local [`State`]. +//! +//! [`Button`]: type.Button.html +//! [`State`]: struct.State.html +use crate::Renderer; + +pub use iced_style::progress_bar::{Style, StyleSheet}; + +/// A bar that displays progress. +/// +/// This is an alias of an `iced_native` progress bar with an +/// `iced_wgpu::Renderer`. +pub type ProgressBar = iced_native::ProgressBar; diff --git a/glow/src/widget/radio.rs b/glow/src/widget/radio.rs new file mode 100644 index 00000000..6e5cf042 --- /dev/null +++ b/glow/src/widget/radio.rs @@ -0,0 +1,10 @@ +//! Create choices using radio buttons. +use crate::Renderer; + +pub use iced_style::radio::{Style, StyleSheet}; + +/// A circular button representing a choice. +/// +/// This is an alias of an `iced_native` radio button with an +/// `iced_wgpu::Renderer`. +pub type Radio = iced_native::Radio; diff --git a/glow/src/widget/scrollable.rs b/glow/src/widget/scrollable.rs new file mode 100644 index 00000000..1d236105 --- /dev/null +++ b/glow/src/widget/scrollable.rs @@ -0,0 +1,13 @@ +//! Navigate an endless amount of content with a scrollbar. +use crate::Renderer; + +pub use iced_native::scrollable::State; +pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; + +/// A widget that can vertically display an infinite amount of content +/// with a scrollbar. +/// +/// This is an alias of an `iced_native` scrollable with a default +/// `Renderer`. +pub type Scrollable<'a, Message> = + iced_native::Scrollable<'a, Message, Renderer>; diff --git a/glow/src/widget/slider.rs b/glow/src/widget/slider.rs new file mode 100644 index 00000000..4e47978f --- /dev/null +++ b/glow/src/widget/slider.rs @@ -0,0 +1,16 @@ +//! Display an interactive selector of a single value from a range of values. +//! +//! A [`Slider`] has some local [`State`]. +//! +//! [`Slider`]: struct.Slider.html +//! [`State`]: struct.State.html +use crate::Renderer; + +pub use iced_native::slider::State; +pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; + +/// An horizontal bar and a handle that selects a single value from a range of +/// values. +/// +/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`. +pub type Slider<'a, Message> = iced_native::Slider<'a, Message, Renderer>; diff --git a/glow/src/widget/text_input.rs b/glow/src/widget/text_input.rs new file mode 100644 index 00000000..260fe3a6 --- /dev/null +++ b/glow/src/widget/text_input.rs @@ -0,0 +1,15 @@ +//! Display fields that can be filled with text. +//! +//! A [`TextInput`] has some local [`State`]. +//! +//! [`TextInput`]: struct.TextInput.html +//! [`State`]: struct.State.html +use crate::Renderer; + +pub use iced_native::text_input::State; +pub use iced_style::text_input::{Style, StyleSheet}; + +/// A field that can be filled with text. +/// +/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`. +pub type TextInput<'a, Message> = iced_native::TextInput<'a, Message, Renderer>; diff --git a/glow/src/window.rs b/glow/src/window.rs new file mode 100644 index 00000000..b7adad82 --- /dev/null +++ b/glow/src/window.rs @@ -0,0 +1,6 @@ +//! Display rendering results on windows. +mod backend; +mod swap_chain; + +pub use backend::Backend; +pub use swap_chain::SwapChain; diff --git a/glow/src/window/backend.rs b/glow/src/window/backend.rs new file mode 100644 index 00000000..05f988f7 --- /dev/null +++ b/glow/src/window/backend.rs @@ -0,0 +1,183 @@ +use crate::{Renderer, Settings, Viewport}; + +use glow::HasContext; +use iced_native::mouse; +use raw_window_handle::HasRawWindowHandle; + +/// A window graphics backend for iced powered by `glow`. +#[allow(missing_debug_implementations)] +pub struct Backend { + connection: surfman::Connection, + device: surfman::Device, + gl_context: surfman::Context, + gl: Option, +} + +impl iced_native::window::Backend for Backend { + type Settings = Settings; + type Renderer = Renderer; + type Surface = (); + type SwapChain = Viewport; + + fn new(settings: Self::Settings) -> Backend { + let connection = surfman::Connection::new().expect("Create connection"); + + let adapter = connection + .create_hardware_adapter() + .expect("Create adapter"); + + let mut device = + connection.create_device(&adapter).expect("Create device"); + + let context_descriptor = device + .create_context_descriptor(&surfman::ContextAttributes { + version: surfman::GLVersion::new(3, 0), + flags: surfman::ContextAttributeFlags::empty(), + }) + .expect("Create context descriptor"); + + let gl_context = device + .create_context(&context_descriptor) + .expect("Create context"); + + Backend { + connection, + device, + gl_context, + gl: None, + } + } + + fn create_renderer(&mut self, settings: Settings) -> Renderer { + self.device + .make_context_current(&self.gl_context) + .expect("Make context current"); + + Renderer::new(self.gl.as_ref().unwrap(), settings) + } + + fn create_surface( + &mut self, + window: &W, + ) -> Self::Surface { + let native_widget = self + .connection + .create_native_widget_from_rwh(window.raw_window_handle()) + .expect("Create widget"); + + let surface = self + .device + .create_surface( + &self.gl_context, + surfman::SurfaceAccess::GPUOnly, + surfman::SurfaceType::Widget { native_widget }, + ) + .expect("Create surface"); + + let surfman::SurfaceInfo { .. } = self.device.surface_info(&surface); + + self.device + .bind_surface_to_context(&mut self.gl_context, surface) + .expect("Bind surface to context"); + + self.device + .make_context_current(&self.gl_context) + .expect("Make context current"); + + self.gl = Some(glow::Context::from_loader_function(|s| { + self.device.get_proc_address(&self.gl_context, s) + })); + + //let mut framebuffer = + // skia_safe::gpu::gl::FramebufferInfo::from_fboid(framebuffer_object); + + //framebuffer.format = gl::RGBA8; + + //framebuffer + } + + fn create_swap_chain( + &mut self, + _surface: &Self::Surface, + width: u32, + height: u32, + ) -> Self::SwapChain { + let mut surface = self + .device + .unbind_surface_from_context(&mut self.gl_context) + .expect("Unbind surface") + .expect("Active surface"); + + self.device + .resize_surface( + &self.gl_context, + &mut surface, + euclid::Size2D::new(width as i32, height as i32), + ) + .expect("Resize surface"); + + self.device + .bind_surface_to_context(&mut self.gl_context, surface) + .expect("Bind surface to context"); + + let gl = self.gl.as_ref().unwrap(); + + unsafe { + gl.viewport(0, 0, width as i32, height as i32); + gl.clear_color(1.0, 1.0, 1.0, 1.0); + + // Enable auto-conversion from/to sRGB + gl.enable(glow::FRAMEBUFFER_SRGB); + + // Enable alpha blending + gl.enable(glow::BLEND); + gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); + } + + Viewport::new(width, height) + } + + fn draw>( + &mut self, + renderer: &mut Self::Renderer, + swap_chain: &mut Self::SwapChain, + output: &::Output, + scale_factor: f64, + overlay: &[T], + ) -> mouse::Interaction { + let gl = self.gl.as_ref().unwrap(); + + unsafe { + gl.clear(glow::COLOR_BUFFER_BIT); + } + + let mouse = + renderer.draw(gl, swap_chain, output, scale_factor, overlay); + + { + let mut surface = self + .device + .unbind_surface_from_context(&mut self.gl_context) + .expect("Unbind surface") + .expect("Active surface"); + + self.device + .present_surface(&self.gl_context, &mut surface) + .expect("Present surface"); + + self.device + .bind_surface_to_context(&mut self.gl_context, surface) + .expect("Bind surface to context"); + } + + mouse + } +} + +impl Drop for Backend { + fn drop(&mut self) { + self.device + .destroy_context(&mut self.gl_context) + .expect("Destroy context"); + } +} diff --git a/glow/src/window/swap_chain.rs b/glow/src/window/swap_chain.rs new file mode 100644 index 00000000..41d19968 --- /dev/null +++ b/glow/src/window/swap_chain.rs @@ -0,0 +1,5 @@ +/// The rendering target of a window. +/// +/// It represents a series of virtual framebuffers with a scale factor. +#[derive(Debug)] +pub struct SwapChain; diff --git a/native/src/window/backend.rs b/native/src/window/backend.rs index 892d4bb9..d8726fd4 100644 --- a/native/src/window/backend.rs +++ b/native/src/window/backend.rs @@ -5,7 +5,7 @@ use raw_window_handle::HasRawWindowHandle; /// A graphics backend that can render to windows. pub trait Backend: Sized { /// The settings of the backend. - type Settings: Default; + type Settings: Default + Clone; /// The iced renderer of the backend. type Renderer: crate::Renderer; @@ -16,10 +16,10 @@ pub trait Backend: Sized { /// The swap chain of the backend. type SwapChain; - /// Creates a new [`Backend`] and an associated iced renderer. + /// Creates a new [`Backend`]. /// /// [`Backend`]: trait.Backend.html - fn new(settings: Self::Settings) -> (Self, Self::Renderer); + fn new(settings: Self::Settings) -> Self; /// Crates a new [`Surface`] for the given window. /// @@ -29,6 +29,11 @@ pub trait Backend: Sized { window: &W, ) -> Self::Surface; + /// Crates a new [`Renderer`]. + /// + /// [`Renderer`]: #associatedtype.Renderer + fn create_renderer(&mut self, settings: Self::Settings) -> Self::Renderer; + /// Crates a new [`SwapChain`] for the given [`Surface`]. /// /// [`SwapChain`]: #associatedtype.SwapChain diff --git a/wgpu/src/window/backend.rs b/wgpu/src/window/backend.rs index 2924ce5d..b467486c 100644 --- a/wgpu/src/window/backend.rs +++ b/wgpu/src/window/backend.rs @@ -17,8 +17,8 @@ impl iced_native::window::Backend for Backend { type Surface = wgpu::Surface; type SwapChain = SwapChain; - fn new(settings: Self::Settings) -> (Backend, Renderer) { - let (mut device, queue) = futures::executor::block_on(async { + fn new(settings: Self::Settings) -> Backend { + let (device, queue) = futures::executor::block_on(async { let adapter = wgpu::Adapter::request( &wgpu::RequestAdapterOptions { power_preference: if settings.antialiasing.is_none() { @@ -43,16 +43,15 @@ impl iced_native::window::Backend for Backend { .await }); - let renderer = Renderer::new(&mut device, settings); + Backend { + device, + queue, + format: settings.format, + } + } - ( - Backend { - device, - queue, - format: settings.format, - }, - renderer, - ) + fn create_renderer(&mut self, settings: Settings) -> Renderer { + Renderer::new(&mut self.device, settings) } fn create_surface( diff --git a/winit/src/application.rs b/winit/src/application.rs index f6bc8fcc..4bc36586 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -78,7 +78,11 @@ pub trait Application: Sized { /// [`update`](#tymethod.update). /// /// A `Subscription` will be kept alive as long as you keep returning it! - fn subscription(&self) -> Subscription; + /// + /// By default, it returns an empty subscription. + fn subscription(&self) -> Subscription { + Subscription::none() + } /// Returns the widgets to display in the [`Application`]. /// @@ -177,9 +181,10 @@ pub trait Application: Sized { let mut resized = false; let clipboard = Clipboard::new(&window); - let (mut backend, mut renderer) = Self::Backend::new(backend_settings); + let mut backend = Self::Backend::new(backend_settings.clone()); let surface = backend.create_surface(&window); + let mut renderer = backend.create_renderer(backend_settings); let mut swap_chain = { let physical_size = size.physical(); From 05af8d00d4c0f7b8e0ece85224fd90a92da86da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 19 May 2020 17:15:44 +0200 Subject: [PATCH 02/40] Draft new `iced_graphics` crate :tada: --- Cargo.toml | 1 + examples/integration/src/main.rs | 7 +- glow/Cargo.toml | 4 +- glow/src/{renderer.rs => backend.rs} | 86 ++---- glow/src/lib.rs | 10 +- glow/src/renderer/widget.rs | 19 -- glow/src/renderer/widget/button.rs | 93 ------- glow/src/renderer/widget/checkbox.rs | 63 ----- glow/src/renderer/widget/image.rs | 22 -- glow/src/renderer/widget/row.rs | 34 --- glow/src/renderer/widget/slider.rs | 106 ------- glow/src/renderer/widget/space.rs | 8 - glow/src/renderer/widget/svg.rs | 22 -- glow/src/renderer/widget/text_input.rs | 261 ------------------ glow/src/text.rs | 2 +- glow/src/triangle.rs | 25 +- glow/src/widget/button.rs | 2 +- glow/src/widget/checkbox.rs | 2 +- glow/src/widget/container.rs | 2 +- glow/src/widget/progress_bar.rs | 2 +- glow/src/widget/radio.rs | 2 +- glow/src/widget/scrollable.rs | 2 +- glow/src/widget/slider.rs | 2 +- glow/src/widget/text_input.rs | 2 +- glow/src/window.rs | 2 - glow/src/window/backend.rs | 11 +- glow/src/window/swap_chain.rs | 5 - graphics/Cargo.toml | 23 ++ graphics/src/backend.rs | 30 ++ {glow => graphics}/src/defaults.rs | 0 graphics/src/lib.rs | 15 + {glow => graphics}/src/primitive.rs | 0 graphics/src/renderer.rs | 86 ++++++ {wgpu => graphics}/src/renderer/widget.rs | 8 +- .../src/renderer/widget/button.rs | 9 +- .../src/renderer/widget/checkbox.rs | 13 +- .../src/renderer/widget/column.rs | 11 +- .../src/renderer/widget/container.rs | 9 +- .../src/renderer/widget/image.rs | 12 +- .../src/renderer/widget/pane_grid.rs | 15 +- .../src/renderer/widget/progress_bar.rs | 8 +- .../src/renderer/widget/radio.rs | 7 +- {wgpu => graphics}/src/renderer/widget/row.rs | 11 +- .../src/renderer/widget/scrollable.rs | 11 +- .../src/renderer/widget/slider.rs | 15 +- graphics/src/renderer/widget/space.rs | 13 + {wgpu => graphics}/src/renderer/widget/svg.rs | 8 +- .../src/renderer/widget/text.rs | 8 +- .../src/renderer/widget/text_input.rs | 30 +- graphics/src/triangle.rs | 26 ++ graphics/src/widget.rs | 49 ++++ graphics/src/widget/button.rs | 16 ++ graphics/src/widget/canvas.rs | 236 ++++++++++++++++ {wgpu => graphics}/src/widget/canvas/cache.rs | 0 .../src/widget/canvas/cursor.rs | 0 {wgpu => graphics}/src/widget/canvas/event.rs | 0 {wgpu => graphics}/src/widget/canvas/fill.rs | 0 {wgpu => graphics}/src/widget/canvas/frame.rs | 0 .../src/widget/canvas/geometry.rs | 0 {wgpu => graphics}/src/widget/canvas/path.rs | 0 .../src/widget/canvas/path/arc.rs | 0 .../src/widget/canvas/path/builder.rs | 0 .../src/widget/canvas/program.rs | 0 .../src/widget/canvas/stroke.rs | 0 {wgpu => graphics}/src/widget/canvas/text.rs | 0 graphics/src/widget/checkbox.rs | 10 + graphics/src/widget/container.rs | 11 + graphics/src/widget/pane_grid.rs | 25 ++ graphics/src/widget/progress_bar.rs | 15 + graphics/src/widget/radio.rs | 11 + graphics/src/widget/scrollable.rs | 13 + graphics/src/widget/slider.rs | 17 ++ graphics/src/widget/text.rs | 7 + graphics/src/widget/text_input.rs | 16 ++ native/src/user_interface.rs | 4 +- wgpu/Cargo.toml | 11 +- wgpu/src/{renderer.rs => backend.rs} | 97 +++---- wgpu/src/defaults.rs | 32 --- wgpu/src/lib.rs | 13 +- wgpu/src/primitive.rs | 107 ------- wgpu/src/renderer/widget/column.rs | 34 --- wgpu/src/renderer/widget/container.rs | 48 ---- wgpu/src/renderer/widget/pane_grid.rs | 93 ------- wgpu/src/renderer/widget/progress_bar.rs | 54 ---- wgpu/src/renderer/widget/radio.rs | 63 ----- wgpu/src/renderer/widget/scrollable.rs | 125 --------- wgpu/src/renderer/widget/space.rs | 8 - wgpu/src/renderer/widget/text.rs | 61 ---- wgpu/src/text.rs | 2 +- wgpu/src/triangle.rs | 27 +- wgpu/src/widget/button.rs | 2 +- wgpu/src/widget/canvas.rs | 226 +-------------- wgpu/src/widget/checkbox.rs | 2 +- wgpu/src/widget/container.rs | 2 +- wgpu/src/widget/progress_bar.rs | 2 +- wgpu/src/widget/radio.rs | 2 +- wgpu/src/widget/scrollable.rs | 2 +- wgpu/src/widget/slider.rs | 2 +- wgpu/src/widget/text_input.rs | 2 +- wgpu/src/window/backend.rs | 4 +- 100 files changed, 861 insertions(+), 1755 deletions(-) rename glow/src/{renderer.rs => backend.rs} (88%) delete mode 100644 glow/src/renderer/widget.rs delete mode 100644 glow/src/renderer/widget/button.rs delete mode 100644 glow/src/renderer/widget/checkbox.rs delete mode 100644 glow/src/renderer/widget/image.rs delete mode 100644 glow/src/renderer/widget/row.rs delete mode 100644 glow/src/renderer/widget/slider.rs delete mode 100644 glow/src/renderer/widget/space.rs delete mode 100644 glow/src/renderer/widget/svg.rs delete mode 100644 glow/src/renderer/widget/text_input.rs delete mode 100644 glow/src/window/swap_chain.rs create mode 100644 graphics/Cargo.toml create mode 100644 graphics/src/backend.rs rename {glow => graphics}/src/defaults.rs (100%) create mode 100644 graphics/src/lib.rs rename {glow => graphics}/src/primitive.rs (100%) create mode 100644 graphics/src/renderer.rs rename {wgpu => graphics}/src/renderer/widget.rs (78%) rename {wgpu => graphics}/src/renderer/widget/button.rs (94%) rename {wgpu => graphics}/src/renderer/widget/checkbox.rs (85%) rename {glow => graphics}/src/renderer/widget/column.rs (82%) rename {glow => graphics}/src/renderer/widget/container.rs (88%) rename {wgpu => graphics}/src/renderer/widget/image.rs (63%) rename {glow => graphics}/src/renderer/widget/pane_grid.rs (92%) rename {glow => graphics}/src/renderer/widget/progress_bar.rs (91%) rename {glow => graphics}/src/renderer/widget/radio.rs (93%) rename {wgpu => graphics}/src/renderer/widget/row.rs (82%) rename {glow => graphics}/src/renderer/widget/scrollable.rs (95%) rename {wgpu => graphics}/src/renderer/widget/slider.rs (92%) create mode 100644 graphics/src/renderer/widget/space.rs rename {wgpu => graphics}/src/renderer/widget/svg.rs (72%) rename {glow => graphics}/src/renderer/widget/text.rs (92%) rename {wgpu => graphics}/src/renderer/widget/text_input.rs (93%) create mode 100644 graphics/src/triangle.rs create mode 100644 graphics/src/widget.rs create mode 100644 graphics/src/widget/button.rs create mode 100644 graphics/src/widget/canvas.rs rename {wgpu => graphics}/src/widget/canvas/cache.rs (100%) rename {wgpu => graphics}/src/widget/canvas/cursor.rs (100%) rename {wgpu => graphics}/src/widget/canvas/event.rs (100%) rename {wgpu => graphics}/src/widget/canvas/fill.rs (100%) rename {wgpu => graphics}/src/widget/canvas/frame.rs (100%) rename {wgpu => graphics}/src/widget/canvas/geometry.rs (100%) rename {wgpu => graphics}/src/widget/canvas/path.rs (100%) rename {wgpu => graphics}/src/widget/canvas/path/arc.rs (100%) rename {wgpu => graphics}/src/widget/canvas/path/builder.rs (100%) rename {wgpu => graphics}/src/widget/canvas/program.rs (100%) rename {wgpu => graphics}/src/widget/canvas/stroke.rs (100%) rename {wgpu => graphics}/src/widget/canvas/text.rs (100%) create mode 100644 graphics/src/widget/checkbox.rs create mode 100644 graphics/src/widget/container.rs create mode 100644 graphics/src/widget/pane_grid.rs create mode 100644 graphics/src/widget/progress_bar.rs create mode 100644 graphics/src/widget/radio.rs create mode 100644 graphics/src/widget/scrollable.rs create mode 100644 graphics/src/widget/slider.rs create mode 100644 graphics/src/widget/text.rs create mode 100644 graphics/src/widget/text_input.rs rename wgpu/src/{renderer.rs => backend.rs} (89%) delete mode 100644 wgpu/src/defaults.rs delete mode 100644 wgpu/src/primitive.rs delete mode 100644 wgpu/src/renderer/widget/column.rs delete mode 100644 wgpu/src/renderer/widget/container.rs delete mode 100644 wgpu/src/renderer/widget/pane_grid.rs delete mode 100644 wgpu/src/renderer/widget/progress_bar.rs delete mode 100644 wgpu/src/renderer/widget/radio.rs delete mode 100644 wgpu/src/renderer/widget/scrollable.rs delete mode 100644 wgpu/src/renderer/widget/space.rs delete mode 100644 wgpu/src/renderer/widget/text.rs diff --git a/Cargo.toml b/Cargo.toml index 3d8c1757..aa6216cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ maintenance = { status = "actively-developed" } members = [ "core", "futures", + "graphics", "glow", "native", "style", diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 92d2fa8d..0ade0458 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -5,7 +5,7 @@ use controls::Controls; use scene::Scene; use iced_wgpu::{ - wgpu, window::SwapChain, Primitive, Renderer, Settings, Target, + wgpu, window::SwapChain, Backend, Primitive, Renderer, Settings, Target, }; use iced_winit::{ futures, mouse, winit, Cache, Clipboard, Size, UserInterface, @@ -62,7 +62,8 @@ pub fn main() { // Initialize iced let mut events = Vec::new(); let mut cache = Some(Cache::default()); - let mut renderer = Renderer::new(&mut device, Settings::default()); + let mut renderer = + Renderer::new(Backend::new(&mut device, Settings::default())); let mut output = (Primitive::None, mouse::Interaction::default()); let clipboard = Clipboard::new(&window); @@ -189,7 +190,7 @@ pub fn main() { scene.draw(&mut encoder, &frame.view); // And then iced on top - let mouse_interaction = renderer.draw( + let mouse_interaction = renderer.backend_mut().draw( &mut device, &mut encoder, Target { diff --git a/glow/Cargo.toml b/glow/Cargo.toml index e130d563..212fbb30 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -21,9 +21,9 @@ glyph_brush = "0.6" version = "0.2" path = "../native" -[dependencies.iced_style] +[dependencies.iced_graphics] version = "0.1" -path = "../style" +path = "../graphics" [dependencies.surfman] path = "../../surfman/surfman" diff --git a/glow/src/renderer.rs b/glow/src/backend.rs similarity index 88% rename from glow/src/renderer.rs rename to glow/src/backend.rs index 40228a6b..7293eba1 100644 --- a/glow/src/renderer.rs +++ b/glow/src/backend.rs @@ -1,19 +1,17 @@ -use crate::{ - quad, text, triangle, Defaults, Primitive, Quad, Settings, Transformation, - Viewport, -}; - -use iced_native::{ - layout, mouse, Background, Color, Layout, Point, Rectangle, Vector, Widget, -}; - -mod widget; +use crate::quad; +use crate::text; +use crate::triangle; +use crate::{Quad, Settings, Transformation, Viewport}; +use iced_graphics::backend; +use iced_graphics::Primitive; +use iced_native::mouse; +use iced_native::{Background, Font, Point, Rectangle, Size, Vector}; /// A [`glow`] renderer. /// /// [`glow`]: https://github.com/grovesNL/glow #[derive(Debug)] -pub struct Renderer { +pub struct Backend { quad_pipeline: quad::Pipeline, text_pipeline: text::Pipeline, triangle_pipeline: triangle::Pipeline, @@ -43,7 +41,7 @@ impl<'a> Layer<'a> { } } -impl Renderer { +impl Backend { /// Creates a new [`Renderer`]. /// /// [`Renderer`]: struct.Renderer.html @@ -399,57 +397,27 @@ impl Renderer { } } -impl iced_native::Renderer for Renderer { - type Output = (Primitive, mouse::Interaction); - type Defaults = Defaults; - - fn layout<'a, Message>( - &mut self, - element: &iced_native::Element<'a, Message, Self>, - limits: &iced_native::layout::Limits, - ) -> iced_native::layout::Node { - let node = element.layout(self, limits); - - self.text_pipeline.clear_measurement_cache(); - - node +impl iced_graphics::Backend for Backend { + fn trim_measurements(&mut self) { + self.text_pipeline.trim_measurement_cache() } } -impl layout::Debugger for Renderer { - fn explain( - &mut self, - defaults: &Defaults, - widget: &dyn Widget, - layout: Layout<'_>, - cursor_position: Point, - color: Color, - ) -> Self::Output { - let mut primitives = Vec::new(); - let (primitive, cursor) = - widget.draw(self, defaults, layout, cursor_position); +impl backend::Text for Backend { + const ICON_FONT: Font = text::BUILTIN_ICONS; + const CHECKMARK_ICON: char = text::CHECKMARK_ICON; - explain_layout(layout, color, &mut primitives); - primitives.push(primitive); + fn measure( + &self, + contents: &str, + size: f32, + font: Font, + bounds: Size, + ) -> (f32, f32) { + self.text_pipeline.measure(contents, size, font, bounds) + } - (Primitive::Group { primitives }, cursor) - } -} - -fn explain_layout( - layout: Layout<'_>, - color: Color, - primitives: &mut Vec, -) { - primitives.push(Primitive::Quad { - bounds: layout.bounds(), - background: Background::Color(Color::TRANSPARENT), - border_radius: 0, - border_width: 1, - border_color: [0.6, 0.6, 0.6, 0.5].into(), - }); - - for child in layout.children() { - explain_layout(child, color, primitives); + fn space_width(&self, size: f32) -> f32 { + self.text_pipeline.space_width(size) } } diff --git a/glow/src/lib.rs b/glow/src/lib.rs index ce447192..27c39b99 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -4,10 +4,8 @@ //#![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] -mod defaults; -mod primitive; +mod backend; mod quad; -mod renderer; mod text; mod transformation; mod triangle; @@ -17,15 +15,15 @@ pub mod settings; pub mod widget; pub mod window; -pub use defaults::Defaults; -pub use primitive::Primitive; -pub use renderer::Renderer; pub use settings::Settings; pub use viewport::Viewport; +pub(crate) use backend::Backend; pub(crate) use quad::Quad; pub(crate) use transformation::Transformation; +pub type Renderer = iced_graphics::Renderer; + #[doc(no_inline)] pub use widget::*; diff --git a/glow/src/renderer/widget.rs b/glow/src/renderer/widget.rs deleted file mode 100644 index 37421fbe..00000000 --- a/glow/src/renderer/widget.rs +++ /dev/null @@ -1,19 +0,0 @@ -mod button; -mod checkbox; -mod column; -mod container; -mod pane_grid; -mod progress_bar; -mod radio; -mod row; -mod scrollable; -mod slider; -mod space; -mod text; -mod text_input; - -#[cfg(feature = "svg")] -mod svg; - -#[cfg(feature = "image")] -mod image; diff --git a/glow/src/renderer/widget/button.rs b/glow/src/renderer/widget/button.rs deleted file mode 100644 index eb225038..00000000 --- a/glow/src/renderer/widget/button.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::{button::StyleSheet, defaults, Defaults, Primitive, Renderer}; -use iced_native::{ - mouse, Background, Color, Element, Layout, Point, Rectangle, Vector, -}; - -impl iced_native::button::Renderer for Renderer { - const DEFAULT_PADDING: u16 = 5; - - type Style = Box; - - fn draw( - &mut self, - _defaults: &Defaults, - bounds: Rectangle, - cursor_position: Point, - is_disabled: bool, - is_pressed: bool, - style: &Box, - content: &Element<'_, Message, Self>, - content_layout: Layout<'_>, - ) -> Self::Output { - let is_mouse_over = bounds.contains(cursor_position); - - let styling = if is_disabled { - style.disabled() - } else if is_mouse_over { - if is_pressed { - style.pressed() - } else { - style.hovered() - } - } else { - style.active() - }; - - let (content, _) = content.draw( - self, - &Defaults { - text: defaults::Text { - color: styling.text_color, - }, - }, - content_layout, - cursor_position, - ); - - ( - if styling.background.is_some() || styling.border_width > 0 { - let background = Primitive::Quad { - bounds, - background: styling - .background - .unwrap_or(Background::Color(Color::TRANSPARENT)), - border_radius: styling.border_radius, - border_width: styling.border_width, - border_color: styling.border_color, - }; - - if styling.shadow_offset == Vector::default() { - Primitive::Group { - primitives: vec![background, content], - } - } else { - // TODO: Implement proper shadow support - let shadow = Primitive::Quad { - bounds: Rectangle { - x: bounds.x + styling.shadow_offset.x, - y: bounds.y + styling.shadow_offset.y, - ..bounds - }, - background: Background::Color( - [0.0, 0.0, 0.0, 0.5].into(), - ), - border_radius: styling.border_radius, - border_width: 0, - border_color: Color::TRANSPARENT, - }; - - Primitive::Group { - primitives: vec![shadow, background, content], - } - } - } else { - content - }, - if is_mouse_over { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - }, - ) - } -} diff --git a/glow/src/renderer/widget/checkbox.rs b/glow/src/renderer/widget/checkbox.rs deleted file mode 100644 index 0340bf62..00000000 --- a/glow/src/renderer/widget/checkbox.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::{checkbox::StyleSheet, Primitive, Renderer}; -use iced_native::{ - checkbox, mouse, HorizontalAlignment, Rectangle, VerticalAlignment, -}; - -impl checkbox::Renderer for Renderer { - type Style = Box; - - const DEFAULT_SIZE: u16 = 20; - const DEFAULT_SPACING: u16 = 15; - - fn draw( - &mut self, - bounds: Rectangle, - is_checked: bool, - is_mouse_over: bool, - (label, _): Self::Output, - style_sheet: &Self::Style, - ) -> Self::Output { - let style = if is_mouse_over { - style_sheet.hovered(is_checked) - } else { - style_sheet.active(is_checked) - }; - - let checkbox = Primitive::Quad { - bounds, - background: style.background, - border_radius: style.border_radius, - border_width: style.border_width, - border_color: style.border_color, - }; - - ( - Primitive::Group { - primitives: if is_checked { - let check = Primitive::Text { - content: crate::text::CHECKMARK_ICON.to_string(), - font: crate::text::BUILTIN_ICONS, - size: bounds.height * 0.7, - bounds: Rectangle { - x: bounds.center_x(), - y: bounds.center_y(), - ..bounds - }, - color: style.checkmark_color, - horizontal_alignment: HorizontalAlignment::Center, - vertical_alignment: VerticalAlignment::Center, - }; - - vec![checkbox, check, label] - } else { - vec![checkbox, label] - }, - }, - if is_mouse_over { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - }, - ) - } -} diff --git a/glow/src/renderer/widget/image.rs b/glow/src/renderer/widget/image.rs deleted file mode 100644 index c4c04984..00000000 --- a/glow/src/renderer/widget/image.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{image, mouse, Layout}; - -impl image::Renderer for Renderer { - fn dimensions(&self, handle: &image::Handle) -> (u32, u32) { - self.image_pipeline.dimensions(handle) - } - - fn draw( - &mut self, - handle: image::Handle, - layout: Layout<'_>, - ) -> Self::Output { - ( - Primitive::Image { - handle, - bounds: layout.bounds(), - }, - mouse::Interaction::default(), - ) - } -} diff --git a/glow/src/renderer/widget/row.rs b/glow/src/renderer/widget/row.rs deleted file mode 100644 index d0b7ef09..00000000 --- a/glow/src/renderer/widget/row.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{mouse, row, Element, Layout, Point}; - -impl row::Renderer for Renderer { - fn draw( - &mut self, - defaults: &Self::Defaults, - children: &[Element<'_, Message, Self>], - layout: Layout<'_>, - cursor_position: Point, - ) -> Self::Output { - let mut mouse_interaction = mouse::Interaction::default(); - - ( - Primitive::Group { - primitives: children - .iter() - .zip(layout.children()) - .map(|(child, layout)| { - let (primitive, new_mouse_interaction) = - child.draw(self, defaults, layout, cursor_position); - - if new_mouse_interaction > mouse_interaction { - mouse_interaction = new_mouse_interaction; - } - - primitive - }) - .collect(), - }, - mouse_interaction, - ) - } -} diff --git a/glow/src/renderer/widget/slider.rs b/glow/src/renderer/widget/slider.rs deleted file mode 100644 index 220feace..00000000 --- a/glow/src/renderer/widget/slider.rs +++ /dev/null @@ -1,106 +0,0 @@ -use crate::{ - slider::{HandleShape, StyleSheet}, - Primitive, Renderer, -}; -use iced_native::{mouse, slider, Background, Color, Point, Rectangle}; - -const HANDLE_HEIGHT: f32 = 22.0; - -impl slider::Renderer for Renderer { - type Style = Box; - - fn height(&self) -> u32 { - 30 - } - - fn draw( - &mut self, - bounds: Rectangle, - cursor_position: Point, - range: std::ops::RangeInclusive, - value: f32, - is_dragging: bool, - style_sheet: &Self::Style, - ) -> Self::Output { - let is_mouse_over = bounds.contains(cursor_position); - - let style = if is_dragging { - style_sheet.dragging() - } else if is_mouse_over { - style_sheet.hovered() - } else { - style_sheet.active() - }; - - let rail_y = bounds.y + (bounds.height / 2.0).round(); - - let (rail_top, rail_bottom) = ( - Primitive::Quad { - bounds: Rectangle { - x: bounds.x, - y: rail_y, - width: bounds.width, - height: 2.0, - }, - background: Background::Color(style.rail_colors.0), - border_radius: 0, - border_width: 0, - border_color: Color::TRANSPARENT, - }, - Primitive::Quad { - bounds: Rectangle { - x: bounds.x, - y: rail_y + 2.0, - width: bounds.width, - height: 2.0, - }, - background: Background::Color(style.rail_colors.1), - border_radius: 0, - border_width: 0, - border_color: Color::TRANSPARENT, - }, - ); - - let (range_start, range_end) = range.into_inner(); - - let (handle_width, handle_height, handle_border_radius) = - match style.handle.shape { - HandleShape::Circle { radius } => { - (f32::from(radius * 2), f32::from(radius * 2), radius) - } - HandleShape::Rectangle { - width, - border_radius, - } => (f32::from(width), HANDLE_HEIGHT, border_radius), - }; - - let handle_offset = (bounds.width - handle_width) - * ((value - range_start) / (range_end - range_start).max(1.0)); - - let handle = Primitive::Quad { - bounds: Rectangle { - x: bounds.x + handle_offset.round(), - y: rail_y - handle_height / 2.0, - width: handle_width, - height: handle_height, - }, - background: Background::Color(style.handle.color), - border_radius: handle_border_radius, - border_width: style.handle.border_width, - border_color: style.handle.border_color, - }; - - ( - Primitive::Group { - primitives: vec![rail_top, rail_bottom, handle], - }, - if is_dragging { - mouse::Interaction::Grabbing - } else if is_mouse_over { - mouse::Interaction::Grab - } else { - mouse::Interaction::default() - }, - ) - } -} diff --git a/glow/src/renderer/widget/space.rs b/glow/src/renderer/widget/space.rs deleted file mode 100644 index 225f7e6c..00000000 --- a/glow/src/renderer/widget/space.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{mouse, space, Rectangle}; - -impl space::Renderer for Renderer { - fn draw(&mut self, _bounds: Rectangle) -> Self::Output { - (Primitive::None, mouse::Interaction::default()) - } -} diff --git a/glow/src/renderer/widget/svg.rs b/glow/src/renderer/widget/svg.rs deleted file mode 100644 index f6d6d0ba..00000000 --- a/glow/src/renderer/widget/svg.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{mouse, svg, Layout}; - -impl svg::Renderer for Renderer { - fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) { - self.image_pipeline.viewport_dimensions(handle) - } - - fn draw( - &mut self, - handle: svg::Handle, - layout: Layout<'_>, - ) -> Self::Output { - ( - Primitive::Svg { - handle, - bounds: layout.bounds(), - }, - mouse::Interaction::default(), - ) - } -} diff --git a/glow/src/renderer/widget/text_input.rs b/glow/src/renderer/widget/text_input.rs deleted file mode 100644 index 57be6692..00000000 --- a/glow/src/renderer/widget/text_input.rs +++ /dev/null @@ -1,261 +0,0 @@ -use crate::{text_input::StyleSheet, Primitive, Renderer}; - -use iced_native::{ - mouse, - text_input::{self, cursor}, - Background, Color, Font, HorizontalAlignment, Point, Rectangle, Size, - Vector, VerticalAlignment, -}; -use std::f32; - -impl text_input::Renderer for Renderer { - type Style = Box; - - fn default_size(&self) -> u16 { - // TODO: Make this configurable - 20 - } - - fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 { - let (mut width, _) = self.text_pipeline.measure( - value, - f32::from(size), - font, - Size::INFINITY, - ); - - let spaces_around = value.len() - value.trim().len(); - - if spaces_around > 0 { - let space_width = self.text_pipeline.space_width(size as f32); - width += spaces_around as f32 * space_width; - } - - width - } - - fn offset( - &self, - text_bounds: Rectangle, - font: Font, - size: u16, - value: &text_input::Value, - state: &text_input::State, - ) -> f32 { - if state.is_focused() { - let cursor = state.cursor(); - - let focus_position = match cursor.state(value) { - cursor::State::Index(i) => i, - cursor::State::Selection { end, .. } => end, - }; - - let (_, offset) = measure_cursor_and_scroll_offset( - self, - text_bounds, - value, - size, - focus_position, - font, - ); - - offset - } else { - 0.0 - } - } - - fn draw( - &mut self, - bounds: Rectangle, - text_bounds: Rectangle, - cursor_position: Point, - font: Font, - size: u16, - placeholder: &str, - value: &text_input::Value, - state: &text_input::State, - style_sheet: &Self::Style, - ) -> Self::Output { - let is_mouse_over = bounds.contains(cursor_position); - - let style = if state.is_focused() { - style_sheet.focused() - } else if is_mouse_over { - style_sheet.hovered() - } else { - style_sheet.active() - }; - - let input = Primitive::Quad { - bounds, - background: style.background, - border_radius: style.border_radius, - border_width: style.border_width, - border_color: style.border_color, - }; - - let text = value.to_string(); - - let text_value = Primitive::Text { - content: if text.is_empty() { - placeholder.to_string() - } else { - text.clone() - }, - color: if text.is_empty() { - style_sheet.placeholder_color() - } else { - style_sheet.value_color() - }, - font, - bounds: Rectangle { - y: text_bounds.center_y(), - width: f32::INFINITY, - ..text_bounds - }, - size: f32::from(size), - horizontal_alignment: HorizontalAlignment::Left, - vertical_alignment: VerticalAlignment::Center, - }; - - let (contents_primitive, offset) = if state.is_focused() { - let cursor = state.cursor(); - - let (cursor_primitive, offset) = match cursor.state(value) { - cursor::State::Index(position) => { - let (text_value_width, offset) = - measure_cursor_and_scroll_offset( - self, - text_bounds, - value, - size, - position, - font, - ); - - ( - Primitive::Quad { - bounds: Rectangle { - x: text_bounds.x + text_value_width, - y: text_bounds.y, - width: 1.0, - height: text_bounds.height, - }, - background: Background::Color( - style_sheet.value_color(), - ), - border_radius: 0, - border_width: 0, - border_color: Color::TRANSPARENT, - }, - offset, - ) - } - cursor::State::Selection { start, end } => { - let left = start.min(end); - let right = end.max(start); - - let (left_position, left_offset) = - measure_cursor_and_scroll_offset( - self, - text_bounds, - value, - size, - left, - font, - ); - - let (right_position, right_offset) = - measure_cursor_and_scroll_offset( - self, - text_bounds, - value, - size, - right, - font, - ); - - let width = right_position - left_position; - - ( - Primitive::Quad { - bounds: Rectangle { - x: text_bounds.x + left_position, - y: text_bounds.y, - width, - height: text_bounds.height, - }, - background: Background::Color( - style_sheet.selection_color(), - ), - border_radius: 0, - border_width: 0, - border_color: Color::TRANSPARENT, - }, - if end == right { - right_offset - } else { - left_offset - }, - ) - } - }; - - ( - Primitive::Group { - primitives: vec![cursor_primitive, text_value], - }, - Vector::new(offset as u32, 0), - ) - } else { - (text_value, Vector::new(0, 0)) - }; - - let text_width = self.measure_value( - if text.is_empty() { placeholder } else { &text }, - size, - font, - ); - - let contents = if text_width > text_bounds.width { - Primitive::Clip { - bounds: text_bounds, - offset, - content: Box::new(contents_primitive), - } - } else { - contents_primitive - }; - - ( - Primitive::Group { - primitives: vec![input, contents], - }, - if is_mouse_over { - mouse::Interaction::Text - } else { - mouse::Interaction::default() - }, - ) - } -} - -fn measure_cursor_and_scroll_offset( - renderer: &Renderer, - text_bounds: Rectangle, - value: &text_input::Value, - size: u16, - cursor_index: usize, - font: Font, -) -> (f32, f32) { - use iced_native::text_input::Renderer; - - let text_before_cursor = value.until(cursor_index).to_string(); - - let text_value_width = - renderer.measure_value(&text_before_cursor, size, font); - let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0); - - (text_value_width, offset) -} diff --git a/glow/src/text.rs b/glow/src/text.rs index bda619fc..159c80a6 100644 --- a/glow/src/text.rs +++ b/glow/src/text.rs @@ -130,7 +130,7 @@ impl Pipeline { .advance_width } - pub fn clear_measurement_cache(&mut self) { + pub fn trim_measurement_cache(&mut self) { // TODO: We should probably use a `GlyphCalculator` for this. However, // it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop. // This makes stuff quite inconvenient. A manual method for trimming the diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs index f71022d0..8b21c0a8 100644 --- a/glow/src/triangle.rs +++ b/glow/src/triangle.rs @@ -3,6 +3,8 @@ use crate::{settings, Transformation}; use iced_native::{Rectangle, Vector}; use std::mem; +pub use iced_graphics::triangle::Mesh2D; + const UNIFORM_BUFFER_SIZE: usize = 100; const VERTEX_BUFFER_SIZE: usize = 10_000; const INDEX_BUFFER_SIZE: usize = 10_000; @@ -59,26 +61,3 @@ impl From for Uniforms { } } } - -/// A two-dimensional vertex with some color in __linear__ RGBA. -#[repr(C)] -#[derive(Copy, Clone, Debug)] -pub struct Vertex2D { - /// The vertex position - pub position: [f32; 2], - /// The vertex color in __linear__ RGBA. - pub color: [f32; 4], -} - -/// A set of [`Vertex2D`] and indices representing a list of triangles. -/// -/// [`Vertex2D`]: struct.Vertex2D.html -#[derive(Clone, Debug)] -pub struct Mesh2D { - /// The vertices of the mesh - pub vertices: Vec, - /// The list of vertex indices that defines the triangles of the mesh. - /// - /// Therefore, this list should always have a length that is a multiple of 3. - pub indices: Vec, -} diff --git a/glow/src/widget/button.rs b/glow/src/widget/button.rs index b738c55e..fee7a7f8 100644 --- a/glow/src/widget/button.rs +++ b/glow/src/widget/button.rs @@ -6,8 +6,8 @@ //! [`State`]: struct.State.html use crate::Renderer; +pub use iced_graphics::button::{Style, StyleSheet}; pub use iced_native::button::State; -pub use iced_style::button::{Style, StyleSheet}; /// A widget that produces a message when clicked. /// diff --git a/glow/src/widget/checkbox.rs b/glow/src/widget/checkbox.rs index da0d7a84..d27d77cc 100644 --- a/glow/src/widget/checkbox.rs +++ b/glow/src/widget/checkbox.rs @@ -1,7 +1,7 @@ //! Show toggle controls using checkboxes. use crate::Renderer; -pub use iced_style::checkbox::{Style, StyleSheet}; +pub use iced_graphics::checkbox::{Style, StyleSheet}; /// A box that can be checked. /// diff --git a/glow/src/widget/container.rs b/glow/src/widget/container.rs index 9a93a246..bc26cef2 100644 --- a/glow/src/widget/container.rs +++ b/glow/src/widget/container.rs @@ -1,7 +1,7 @@ //! Decorate content and apply alignment. use crate::Renderer; -pub use iced_style::container::{Style, StyleSheet}; +pub use iced_graphics::container::{Style, StyleSheet}; /// An element decorating some content. /// diff --git a/glow/src/widget/progress_bar.rs b/glow/src/widget/progress_bar.rs index 34450b5e..5782103c 100644 --- a/glow/src/widget/progress_bar.rs +++ b/glow/src/widget/progress_bar.rs @@ -6,7 +6,7 @@ //! [`State`]: struct.State.html use crate::Renderer; -pub use iced_style::progress_bar::{Style, StyleSheet}; +pub use iced_graphics::progress_bar::{Style, StyleSheet}; /// A bar that displays progress. /// diff --git a/glow/src/widget/radio.rs b/glow/src/widget/radio.rs index 6e5cf042..0b843d1f 100644 --- a/glow/src/widget/radio.rs +++ b/glow/src/widget/radio.rs @@ -1,7 +1,7 @@ //! Create choices using radio buttons. use crate::Renderer; -pub use iced_style::radio::{Style, StyleSheet}; +pub use iced_graphics::radio::{Style, StyleSheet}; /// A circular button representing a choice. /// diff --git a/glow/src/widget/scrollable.rs b/glow/src/widget/scrollable.rs index 1d236105..fabb4318 100644 --- a/glow/src/widget/scrollable.rs +++ b/glow/src/widget/scrollable.rs @@ -1,8 +1,8 @@ //! Navigate an endless amount of content with a scrollbar. use crate::Renderer; +pub use iced_graphics::scrollable::{Scrollbar, Scroller, StyleSheet}; pub use iced_native::scrollable::State; -pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; /// A widget that can vertically display an infinite amount of content /// with a scrollbar. diff --git a/glow/src/widget/slider.rs b/glow/src/widget/slider.rs index 4e47978f..cf036829 100644 --- a/glow/src/widget/slider.rs +++ b/glow/src/widget/slider.rs @@ -6,8 +6,8 @@ //! [`State`]: struct.State.html use crate::Renderer; +pub use iced_graphics::slider::{Handle, HandleShape, Style, StyleSheet}; pub use iced_native::slider::State; -pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; /// An horizontal bar and a handle that selects a single value from a range of /// values. diff --git a/glow/src/widget/text_input.rs b/glow/src/widget/text_input.rs index 260fe3a6..1da3fbe6 100644 --- a/glow/src/widget/text_input.rs +++ b/glow/src/widget/text_input.rs @@ -6,8 +6,8 @@ //! [`State`]: struct.State.html use crate::Renderer; +pub use iced_graphics::text_input::{Style, StyleSheet}; pub use iced_native::text_input::State; -pub use iced_style::text_input::{Style, StyleSheet}; /// A field that can be filled with text. /// diff --git a/glow/src/window.rs b/glow/src/window.rs index b7adad82..a8edb016 100644 --- a/glow/src/window.rs +++ b/glow/src/window.rs @@ -1,6 +1,4 @@ //! Display rendering results on windows. mod backend; -mod swap_chain; pub use backend::Backend; -pub use swap_chain::SwapChain; diff --git a/glow/src/window/backend.rs b/glow/src/window/backend.rs index 05f988f7..34245f35 100644 --- a/glow/src/window/backend.rs +++ b/glow/src/window/backend.rs @@ -53,7 +53,7 @@ impl iced_native::window::Backend for Backend { .make_context_current(&self.gl_context) .expect("Make context current"); - Renderer::new(self.gl.as_ref().unwrap(), settings) + Renderer::new(crate::Backend::new(self.gl.as_ref().unwrap(), settings)) } fn create_surface( @@ -151,8 +151,13 @@ impl iced_native::window::Backend for Backend { gl.clear(glow::COLOR_BUFFER_BIT); } - let mouse = - renderer.draw(gl, swap_chain, output, scale_factor, overlay); + let mouse = renderer.backend_mut().draw( + gl, + swap_chain, + output, + scale_factor, + overlay, + ); { let mut surface = self diff --git a/glow/src/window/swap_chain.rs b/glow/src/window/swap_chain.rs deleted file mode 100644 index 41d19968..00000000 --- a/glow/src/window/swap_chain.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// The rendering target of a window. -/// -/// It represents a series of virtual framebuffers with a scale factor. -#[derive(Debug)] -pub struct SwapChain; diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml new file mode 100644 index 00000000..dcbf5ce4 --- /dev/null +++ b/graphics/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "iced_graphics" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" + +[features] +canvas = ["lyon"] + +[dependencies] +bytemuck = "1.2" + +[dependencies.iced_native] +version = "0.2" +path = "../native" + +[dependencies.iced_style] +version = "0.1" +path = "../style" + +[dependencies.lyon] +version = "0.15" +optional = true diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs new file mode 100644 index 00000000..3fcb42f7 --- /dev/null +++ b/graphics/src/backend.rs @@ -0,0 +1,30 @@ +use iced_native::image; +use iced_native::svg; +use iced_native::{Font, Size}; + +pub trait Backend { + fn trim_measurements(&mut self) {} +} + +pub trait Text { + const ICON_FONT: Font; + const CHECKMARK_ICON: char; + + fn measure( + &self, + contents: &str, + size: f32, + font: Font, + bounds: Size, + ) -> (f32, f32); + + fn space_width(&self, size: f32) -> f32; +} + +pub trait Image { + fn dimensions(&self, handle: &image::Handle) -> (u32, u32); +} + +pub trait Svg { + fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32); +} diff --git a/glow/src/defaults.rs b/graphics/src/defaults.rs similarity index 100% rename from glow/src/defaults.rs rename to graphics/src/defaults.rs diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs new file mode 100644 index 00000000..596f72a4 --- /dev/null +++ b/graphics/src/lib.rs @@ -0,0 +1,15 @@ +mod defaults; +mod primitive; +mod renderer; +mod widget; + +pub mod backend; +pub mod triangle; + +#[doc(no_inline)] +pub use widget::*; + +pub use backend::Backend; +pub use defaults::Defaults; +pub use primitive::Primitive; +pub use renderer::Renderer; diff --git a/glow/src/primitive.rs b/graphics/src/primitive.rs similarity index 100% rename from glow/src/primitive.rs rename to graphics/src/primitive.rs diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs new file mode 100644 index 00000000..836ed58d --- /dev/null +++ b/graphics/src/renderer.rs @@ -0,0 +1,86 @@ +mod widget; + +use crate::{Backend, Defaults, Primitive}; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::{Background, Color, Element, Point, Widget}; + +pub struct Renderer { + backend: B, +} + +impl Renderer { + pub fn new(backend: B) -> Self { + Self { backend } + } + + pub fn backend(&self) -> &B { + &self.backend + } + + pub fn backend_mut(&mut self) -> &mut B { + &mut self.backend + } +} + +impl iced_native::Renderer for Renderer +where + B: Backend, +{ + type Output = (Primitive, mouse::Interaction); + type Defaults = Defaults; + + fn layout<'a, Message>( + &mut self, + element: &Element<'a, Message, Self>, + limits: &layout::Limits, + ) -> layout::Node { + let layout = element.layout(self, limits); + + self.backend.trim_measurements(); + + layout + } +} + +impl layout::Debugger for Renderer +where + B: Backend, +{ + fn explain( + &mut self, + defaults: &Defaults, + widget: &dyn Widget, + layout: Layout<'_>, + cursor_position: Point, + color: Color, + ) -> Self::Output { + let (primitive, cursor) = + widget.draw(self, defaults, layout, cursor_position); + + let mut primitives = Vec::new(); + + explain_layout(layout, color, &mut primitives); + primitives.push(primitive); + + (Primitive::Group { primitives }, cursor) + } +} + +fn explain_layout( + layout: Layout<'_>, + color: Color, + primitives: &mut Vec, +) { + primitives.push(Primitive::Quad { + bounds: layout.bounds(), + background: Background::Color(Color::TRANSPARENT), + border_radius: 0, + border_width: 1, + border_color: [0.6, 0.6, 0.6, 0.5].into(), + }); + + for child in layout.children() { + explain_layout(child, color, primitives); + } +} diff --git a/wgpu/src/renderer/widget.rs b/graphics/src/renderer/widget.rs similarity index 78% rename from wgpu/src/renderer/widget.rs rename to graphics/src/renderer/widget.rs index 37421fbe..b652fdcf 100644 --- a/wgpu/src/renderer/widget.rs +++ b/graphics/src/renderer/widget.rs @@ -2,6 +2,7 @@ mod button; mod checkbox; mod column; mod container; +mod image; mod pane_grid; mod progress_bar; mod radio; @@ -9,11 +10,6 @@ mod row; mod scrollable; mod slider; mod space; +mod svg; mod text; mod text_input; - -#[cfg(feature = "svg")] -mod svg; - -#[cfg(feature = "image")] -mod image; diff --git a/wgpu/src/renderer/widget/button.rs b/graphics/src/renderer/widget/button.rs similarity index 94% rename from wgpu/src/renderer/widget/button.rs rename to graphics/src/renderer/widget/button.rs index eb225038..6ee60df4 100644 --- a/wgpu/src/renderer/widget/button.rs +++ b/graphics/src/renderer/widget/button.rs @@ -1,9 +1,14 @@ -use crate::{button::StyleSheet, defaults, Defaults, Primitive, Renderer}; +use crate::{ + button::StyleSheet, defaults, Backend, Defaults, Primitive, Renderer, +}; use iced_native::{ mouse, Background, Color, Element, Layout, Point, Rectangle, Vector, }; -impl iced_native::button::Renderer for Renderer { +impl iced_native::button::Renderer for Renderer +where + B: Backend, +{ const DEFAULT_PADDING: u16 = 5; type Style = Box; diff --git a/wgpu/src/renderer/widget/checkbox.rs b/graphics/src/renderer/widget/checkbox.rs similarity index 85% rename from wgpu/src/renderer/widget/checkbox.rs rename to graphics/src/renderer/widget/checkbox.rs index 0340bf62..75168629 100644 --- a/wgpu/src/renderer/widget/checkbox.rs +++ b/graphics/src/renderer/widget/checkbox.rs @@ -1,9 +1,14 @@ -use crate::{checkbox::StyleSheet, Primitive, Renderer}; +use crate::backend::{self, Backend}; +use crate::checkbox::StyleSheet; +use crate::{Primitive, Renderer}; use iced_native::{ checkbox, mouse, HorizontalAlignment, Rectangle, VerticalAlignment, }; -impl checkbox::Renderer for Renderer { +impl checkbox::Renderer for Renderer +where + B: Backend + backend::Text, +{ type Style = Box; const DEFAULT_SIZE: u16 = 20; @@ -35,8 +40,8 @@ impl checkbox::Renderer for Renderer { Primitive::Group { primitives: if is_checked { let check = Primitive::Text { - content: crate::text::CHECKMARK_ICON.to_string(), - font: crate::text::BUILTIN_ICONS, + content: B::CHECKMARK_ICON.to_string(), + font: B::ICON_FONT, size: bounds.height * 0.7, bounds: Rectangle { x: bounds.center_x(), diff --git a/glow/src/renderer/widget/column.rs b/graphics/src/renderer/widget/column.rs similarity index 82% rename from glow/src/renderer/widget/column.rs rename to graphics/src/renderer/widget/column.rs index b853276d..b70d2338 100644 --- a/glow/src/renderer/widget/column.rs +++ b/graphics/src/renderer/widget/column.rs @@ -1,7 +1,12 @@ -use crate::{Primitive, Renderer}; -use iced_native::{column, mouse, Element, Layout, Point}; +use crate::{Backend, Primitive, Renderer}; +use iced_native::column; +use iced_native::mouse; +use iced_native::{Element, Layout, Point}; -impl column::Renderer for Renderer { +impl column::Renderer for Renderer +where + B: Backend, +{ fn draw( &mut self, defaults: &Self::Defaults, diff --git a/glow/src/renderer/widget/container.rs b/graphics/src/renderer/widget/container.rs similarity index 88% rename from glow/src/renderer/widget/container.rs rename to graphics/src/renderer/widget/container.rs index 30cc3f07..a1f6a211 100644 --- a/glow/src/renderer/widget/container.rs +++ b/graphics/src/renderer/widget/container.rs @@ -1,7 +1,12 @@ -use crate::{container, defaults, Defaults, Primitive, Renderer}; +use crate::container; +use crate::defaults::{self, Defaults}; +use crate::{Backend, Primitive, Renderer}; use iced_native::{Background, Color, Element, Layout, Point, Rectangle}; -impl iced_native::container::Renderer for Renderer { +impl iced_native::container::Renderer for Renderer +where + B: Backend, +{ type Style = Box; fn draw( diff --git a/wgpu/src/renderer/widget/image.rs b/graphics/src/renderer/widget/image.rs similarity index 63% rename from wgpu/src/renderer/widget/image.rs rename to graphics/src/renderer/widget/image.rs index c4c04984..3092237f 100644 --- a/wgpu/src/renderer/widget/image.rs +++ b/graphics/src/renderer/widget/image.rs @@ -1,9 +1,15 @@ +use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; -use iced_native::{image, mouse, Layout}; +use iced_native::image; +use iced_native::mouse; +use iced_native::Layout; -impl image::Renderer for Renderer { +impl image::Renderer for Renderer +where + B: Backend + backend::Image, +{ fn dimensions(&self, handle: &image::Handle) -> (u32, u32) { - self.image_pipeline.dimensions(handle) + self.backend().dimensions(handle) } fn draw( diff --git a/glow/src/renderer/widget/pane_grid.rs b/graphics/src/renderer/widget/pane_grid.rs similarity index 92% rename from glow/src/renderer/widget/pane_grid.rs rename to graphics/src/renderer/widget/pane_grid.rs index 2253e4af..3fa171f5 100644 --- a/glow/src/renderer/widget/pane_grid.rs +++ b/graphics/src/renderer/widget/pane_grid.rs @@ -1,11 +1,12 @@ -use crate::{Primitive, Renderer}; -use iced_native::{ - mouse, - pane_grid::{self, Axis, Pane}, - Element, Layout, Point, Rectangle, Vector, -}; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::pane_grid::{self, Axis, Pane}; +use iced_native::{Element, Layout, Point, Rectangle, Vector}; -impl pane_grid::Renderer for Renderer { +impl pane_grid::Renderer for Renderer +where + B: Backend, +{ fn draw( &mut self, defaults: &Self::Defaults, diff --git a/glow/src/renderer/widget/progress_bar.rs b/graphics/src/renderer/widget/progress_bar.rs similarity index 91% rename from glow/src/renderer/widget/progress_bar.rs rename to graphics/src/renderer/widget/progress_bar.rs index 2baeeb14..d8a145a4 100644 --- a/glow/src/renderer/widget/progress_bar.rs +++ b/graphics/src/renderer/widget/progress_bar.rs @@ -1,7 +1,11 @@ -use crate::{progress_bar::StyleSheet, Primitive, Renderer}; +use crate::progress_bar::StyleSheet; +use crate::{Backend, Primitive, Renderer}; use iced_native::{mouse, progress_bar, Color, Rectangle}; -impl progress_bar::Renderer for Renderer { +impl progress_bar::Renderer for Renderer +where + B: Backend, +{ type Style = Box; const DEFAULT_HEIGHT: u16 = 30; diff --git a/glow/src/renderer/widget/radio.rs b/graphics/src/renderer/widget/radio.rs similarity index 93% rename from glow/src/renderer/widget/radio.rs rename to graphics/src/renderer/widget/radio.rs index cee0deb6..4da2b60a 100644 --- a/glow/src/renderer/widget/radio.rs +++ b/graphics/src/renderer/widget/radio.rs @@ -1,10 +1,13 @@ -use crate::{radio::StyleSheet, Primitive, Renderer}; +use crate::{radio::StyleSheet, Backend, Primitive, Renderer}; use iced_native::{mouse, radio, Background, Color, Rectangle}; const SIZE: f32 = 28.0; const DOT_SIZE: f32 = SIZE / 2.0; -impl radio::Renderer for Renderer { +impl radio::Renderer for Renderer +where + B: Backend, +{ type Style = Box; const DEFAULT_SIZE: u16 = SIZE as u16; diff --git a/wgpu/src/renderer/widget/row.rs b/graphics/src/renderer/widget/row.rs similarity index 82% rename from wgpu/src/renderer/widget/row.rs rename to graphics/src/renderer/widget/row.rs index d0b7ef09..b0bb0d2e 100644 --- a/wgpu/src/renderer/widget/row.rs +++ b/graphics/src/renderer/widget/row.rs @@ -1,7 +1,12 @@ -use crate::{Primitive, Renderer}; -use iced_native::{mouse, row, Element, Layout, Point}; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::row; +use iced_native::{Element, Layout, Point}; -impl row::Renderer for Renderer { +impl row::Renderer for Renderer +where + B: Backend, +{ fn draw( &mut self, defaults: &Self::Defaults, diff --git a/glow/src/renderer/widget/scrollable.rs b/graphics/src/renderer/widget/scrollable.rs similarity index 95% rename from glow/src/renderer/widget/scrollable.rs rename to graphics/src/renderer/widget/scrollable.rs index 8a400b82..8db17bec 100644 --- a/glow/src/renderer/widget/scrollable.rs +++ b/graphics/src/renderer/widget/scrollable.rs @@ -1,10 +1,15 @@ -use crate::{Primitive, Renderer}; -use iced_native::{mouse, scrollable, Background, Color, Rectangle, Vector}; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::scrollable; +use iced_native::{Background, Color, Rectangle, Vector}; const SCROLLBAR_WIDTH: u16 = 10; const SCROLLBAR_MARGIN: u16 = 2; -impl scrollable::Renderer for Renderer { +impl scrollable::Renderer for Renderer +where + B: Backend, +{ type Style = Box; fn scrollbar( diff --git a/wgpu/src/renderer/widget/slider.rs b/graphics/src/renderer/widget/slider.rs similarity index 92% rename from wgpu/src/renderer/widget/slider.rs rename to graphics/src/renderer/widget/slider.rs index 220feace..95c843d0 100644 --- a/wgpu/src/renderer/widget/slider.rs +++ b/graphics/src/renderer/widget/slider.rs @@ -1,12 +1,15 @@ -use crate::{ - slider::{HandleShape, StyleSheet}, - Primitive, Renderer, -}; -use iced_native::{mouse, slider, Background, Color, Point, Rectangle}; +use crate::slider::{HandleShape, StyleSheet}; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::slider; +use iced_native::{Background, Color, Point, Rectangle}; const HANDLE_HEIGHT: f32 = 22.0; -impl slider::Renderer for Renderer { +impl slider::Renderer for Renderer +where + B: Backend, +{ type Style = Box; fn height(&self) -> u32 { diff --git a/graphics/src/renderer/widget/space.rs b/graphics/src/renderer/widget/space.rs new file mode 100644 index 00000000..d0e82f8d --- /dev/null +++ b/graphics/src/renderer/widget/space.rs @@ -0,0 +1,13 @@ +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::space; +use iced_native::Rectangle; + +impl space::Renderer for Renderer +where + B: Backend, +{ + fn draw(&mut self, _bounds: Rectangle) -> Self::Output { + (Primitive::None, mouse::Interaction::default()) + } +} diff --git a/wgpu/src/renderer/widget/svg.rs b/graphics/src/renderer/widget/svg.rs similarity index 72% rename from wgpu/src/renderer/widget/svg.rs rename to graphics/src/renderer/widget/svg.rs index f6d6d0ba..4d80869e 100644 --- a/wgpu/src/renderer/widget/svg.rs +++ b/graphics/src/renderer/widget/svg.rs @@ -1,9 +1,13 @@ +use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::{mouse, svg, Layout}; -impl svg::Renderer for Renderer { +impl svg::Renderer for Renderer +where + B: Backend + backend::Svg, +{ fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) { - self.image_pipeline.viewport_dimensions(handle) + self.backend().viewport_dimensions(handle) } fn draw( diff --git a/glow/src/renderer/widget/text.rs b/graphics/src/renderer/widget/text.rs similarity index 92% rename from glow/src/renderer/widget/text.rs rename to graphics/src/renderer/widget/text.rs index 4605ed06..45cb1be2 100644 --- a/glow/src/renderer/widget/text.rs +++ b/graphics/src/renderer/widget/text.rs @@ -1,3 +1,4 @@ +use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::{ mouse, text, Color, Font, HorizontalAlignment, Rectangle, Size, @@ -6,7 +7,10 @@ use iced_native::{ use std::f32; -impl text::Renderer for Renderer { +impl text::Renderer for Renderer +where + B: Backend + backend::Text, +{ type Font = Font; const DEFAULT_SIZE: u16 = 20; @@ -18,7 +22,7 @@ impl text::Renderer for Renderer { font: Font, bounds: Size, ) -> (f32, f32) { - self.text_pipeline + self.backend() .measure(content, f32::from(size), font, bounds) } diff --git a/wgpu/src/renderer/widget/text_input.rs b/graphics/src/renderer/widget/text_input.rs similarity index 93% rename from wgpu/src/renderer/widget/text_input.rs rename to graphics/src/renderer/widget/text_input.rs index 57be6692..33807b0f 100644 --- a/wgpu/src/renderer/widget/text_input.rs +++ b/graphics/src/renderer/widget/text_input.rs @@ -1,4 +1,6 @@ -use crate::{text_input::StyleSheet, Primitive, Renderer}; +use crate::backend::{self, Backend}; +use crate::text_input::StyleSheet; +use crate::{Primitive, Renderer}; use iced_native::{ mouse, @@ -8,7 +10,10 @@ use iced_native::{ }; use std::f32; -impl text_input::Renderer for Renderer { +impl text_input::Renderer for Renderer +where + B: Backend + backend::Text, +{ type Style = Box; fn default_size(&self) -> u16 { @@ -17,17 +22,15 @@ impl text_input::Renderer for Renderer { } fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 { - let (mut width, _) = self.text_pipeline.measure( - value, - f32::from(size), - font, - Size::INFINITY, - ); + let backend = self.backend(); + + let (mut width, _) = + backend.measure(value, f32::from(size), font, Size::INFINITY); let spaces_around = value.len() - value.trim().len(); if spaces_around > 0 { - let space_width = self.text_pipeline.space_width(size as f32); + let space_width = backend.space_width(size as f32); width += spaces_around as f32 * space_width; } @@ -241,14 +244,17 @@ impl text_input::Renderer for Renderer { } } -fn measure_cursor_and_scroll_offset( - renderer: &Renderer, +fn measure_cursor_and_scroll_offset( + renderer: &Renderer, text_bounds: Rectangle, value: &text_input::Value, size: u16, cursor_index: usize, font: Font, -) -> (f32, f32) { +) -> (f32, f32) +where + B: Backend + backend::Text, +{ use iced_native::text_input::Renderer; let text_before_cursor = value.until(cursor_index).to_string(); diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs new file mode 100644 index 00000000..2b157e3a --- /dev/null +++ b/graphics/src/triangle.rs @@ -0,0 +1,26 @@ +/// A set of [`Vertex2D`] and indices representing a list of triangles. +/// +/// [`Vertex2D`]: struct.Vertex2D.html +#[derive(Clone, Debug)] +pub struct Mesh2D { + /// The vertices of the mesh + pub vertices: Vec, + /// The list of vertex indices that defines the triangles of the mesh. + /// + /// Therefore, this list should always have a length that is a multiple of + /// 3. + pub indices: Vec, +} + +/// A two-dimensional vertex with some color in __linear__ RGBA. +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct Vertex2D { + /// The vertex position + pub position: [f32; 2], + /// The vertex color in __linear__ RGBA. + pub color: [f32; 4], +} + +unsafe impl bytemuck::Zeroable for Vertex2D {} +unsafe impl bytemuck::Pod for Vertex2D {} diff --git a/graphics/src/widget.rs b/graphics/src/widget.rs new file mode 100644 index 00000000..6a8e814b --- /dev/null +++ b/graphics/src/widget.rs @@ -0,0 +1,49 @@ +//! Use the widgets supported out-of-the-box. +//! +//! # Re-exports +//! For convenience, the contents of this module are available at the root +//! module. Therefore, you can directly type: +//! +//! ``` +//! use iced_graphics::{button, Button}; +//! ``` +pub mod button; +pub mod checkbox; +pub mod container; +pub mod pane_grid; +pub mod progress_bar; +pub mod radio; +pub mod scrollable; +pub mod slider; +pub mod text_input; + +mod text; + +#[doc(no_inline)] +pub use button::Button; +#[doc(no_inline)] +pub use checkbox::Checkbox; +#[doc(no_inline)] +pub use container::Container; +#[doc(no_inline)] +pub use pane_grid::PaneGrid; +#[doc(no_inline)] +pub use progress_bar::ProgressBar; +#[doc(no_inline)] +pub use radio::Radio; +#[doc(no_inline)] +pub use scrollable::Scrollable; +#[doc(no_inline)] +pub use slider::Slider; +#[doc(no_inline)] +pub use text_input::TextInput; + +pub use text::Text; + +#[cfg(feature = "canvas")] +#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] +pub mod canvas; + +#[cfg(feature = "canvas")] +#[doc(no_inline)] +pub use canvas::Canvas; diff --git a/graphics/src/widget/button.rs b/graphics/src/widget/button.rs new file mode 100644 index 00000000..9debf5c3 --- /dev/null +++ b/graphics/src/widget/button.rs @@ -0,0 +1,16 @@ +//! Allow your users to perform actions by pressing a button. +//! +//! A [`Button`] has some local [`State`]. +//! +//! [`Button`]: type.Button.html +//! [`State`]: struct.State.html +use crate::Renderer; + +pub use iced_native::button::State; +pub use iced_style::button::{Style, StyleSheet}; + +/// A widget that produces a message when clicked. +/// +/// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`. +pub type Button<'a, Message, Backend> = + iced_native::Button<'a, Message, Renderer>; diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs new file mode 100644 index 00000000..d393a5c5 --- /dev/null +++ b/graphics/src/widget/canvas.rs @@ -0,0 +1,236 @@ +//! Draw 2D graphics for your users. +//! +//! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a +//! [`Frame`]. It can be used for animation, data visualization, game graphics, +//! and more! +//! +//! [`Canvas`]: struct.Canvas.html +//! [`Frame`]: struct.Frame.html +use crate::{Backend, Defaults, Primitive, Renderer}; +use iced_native::{ + layout, mouse, Clipboard, Element, Hasher, Layout, Length, Point, Size, + Vector, Widget, +}; +use std::hash::Hash; +use std::marker::PhantomData; + +pub mod path; + +mod cache; +mod cursor; +mod event; +mod fill; +mod frame; +mod geometry; +mod program; +mod stroke; +mod text; + +pub use cache::Cache; +pub use cursor::Cursor; +pub use event::Event; +pub use fill::Fill; +pub use frame::Frame; +pub use geometry::Geometry; +pub use path::Path; +pub use program::Program; +pub use stroke::{LineCap, LineJoin, Stroke}; +pub use text::Text; + +/// A widget capable of drawing 2D graphics. +/// +/// [`Canvas`]: struct.Canvas.html +/// +/// # Examples +/// The repository has a couple of [examples] showcasing how to use a +/// [`Canvas`]: +/// +/// - [`clock`], an application that uses the [`Canvas`] widget to draw a clock +/// and its hands to display the current time. +/// - [`game_of_life`], an interactive version of the Game of Life, invented by +/// John Conway. +/// - [`solar_system`], an animated solar system drawn using the [`Canvas`] widget +/// and showcasing how to compose different transforms. +/// +/// [examples]: https://github.com/hecrj/iced/tree/master/examples +/// [`clock`]: https://github.com/hecrj/iced/tree/master/examples/clock +/// [`game_of_life`]: https://github.com/hecrj/iced/tree/master/examples/game_of_life +/// [`solar_system`]: https://github.com/hecrj/iced/tree/master/examples/solar_system +/// +/// ## Drawing a simple circle +/// If you want to get a quick overview, here's how we can draw a simple circle: +/// +/// ```no_run +/// # mod iced { +/// # pub use iced_graphics::canvas; +/// # pub use iced_native::{Color, Rectangle}; +/// # } +/// use iced::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; +/// use iced::{Color, Rectangle}; +/// +/// // First, we define the data we need for drawing +/// #[derive(Debug)] +/// struct Circle { +/// radius: f32, +/// } +/// +/// // Then, we implement the `Program` trait +/// impl Program<()> for Circle { +/// fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec{ +/// // We prepare a new `Frame` +/// let mut frame = Frame::new(bounds.size()); +/// +/// // We create a `Path` representing a simple circle +/// let circle = Path::circle(frame.center(), self.radius); +/// +/// // And fill it with some color +/// frame.fill(&circle, Fill::Color(Color::BLACK)); +/// +/// // Finally, we produce the geometry +/// vec![frame.into_geometry()] +/// } +/// } +/// +/// // Finally, we simply use our `Circle` to create the `Canvas`! +/// let canvas = Canvas::new(Circle { radius: 50.0 }); +/// ``` +#[derive(Debug)] +pub struct Canvas> { + width: Length, + height: Length, + program: P, + phantom: PhantomData, +} + +impl> Canvas { + const DEFAULT_SIZE: u16 = 100; + + /// Creates a new [`Canvas`]. + /// + /// [`Canvas`]: struct.Canvas.html + pub fn new(program: P) -> Self { + Canvas { + width: Length::Units(Self::DEFAULT_SIZE), + height: Length::Units(Self::DEFAULT_SIZE), + program, + phantom: PhantomData, + } + } + + /// Sets the width of the [`Canvas`]. + /// + /// [`Canvas`]: struct.Canvas.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Canvas`]. + /// + /// [`Canvas`]: struct.Canvas.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } +} + +impl Widget> for Canvas +where + P: Program, + B: Backend, +{ + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.width(self.width).height(self.height); + let size = limits.resolve(Size::ZERO); + + layout::Node::new(size) + } + + fn on_event( + &mut self, + event: iced_native::Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + _renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + let bounds = layout.bounds(); + + let canvas_event = match event { + iced_native::Event::Mouse(mouse_event) => { + Some(Event::Mouse(mouse_event)) + } + _ => None, + }; + + let cursor = Cursor::from_window_position(cursor_position); + + if let Some(canvas_event) = canvas_event { + if let Some(message) = + self.program.update(canvas_event, bounds, cursor) + { + messages.push(message); + } + } + } + + fn draw( + &self, + _renderer: &mut Renderer, + _defaults: &Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> (Primitive, mouse::Interaction) { + let bounds = layout.bounds(); + let translation = Vector::new(bounds.x, bounds.y); + let cursor = Cursor::from_window_position(cursor_position); + + ( + Primitive::Translate { + translation, + content: Box::new(Primitive::Group { + primitives: self + .program + .draw(bounds, cursor) + .into_iter() + .map(Geometry::into_primitive) + .collect(), + }), + }, + self.program.mouse_interaction(bounds, cursor), + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.width.hash(state); + self.height.hash(state); + } +} + +impl<'a, Message, P, B> From> + for Element<'a, Message, Renderer> +where + Message: 'static, + P: Program + 'a, + B: Backend, +{ + fn from(canvas: Canvas) -> Element<'a, Message, Renderer> { + Element::new(canvas) + } +} diff --git a/wgpu/src/widget/canvas/cache.rs b/graphics/src/widget/canvas/cache.rs similarity index 100% rename from wgpu/src/widget/canvas/cache.rs rename to graphics/src/widget/canvas/cache.rs diff --git a/wgpu/src/widget/canvas/cursor.rs b/graphics/src/widget/canvas/cursor.rs similarity index 100% rename from wgpu/src/widget/canvas/cursor.rs rename to graphics/src/widget/canvas/cursor.rs diff --git a/wgpu/src/widget/canvas/event.rs b/graphics/src/widget/canvas/event.rs similarity index 100% rename from wgpu/src/widget/canvas/event.rs rename to graphics/src/widget/canvas/event.rs diff --git a/wgpu/src/widget/canvas/fill.rs b/graphics/src/widget/canvas/fill.rs similarity index 100% rename from wgpu/src/widget/canvas/fill.rs rename to graphics/src/widget/canvas/fill.rs diff --git a/wgpu/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs similarity index 100% rename from wgpu/src/widget/canvas/frame.rs rename to graphics/src/widget/canvas/frame.rs diff --git a/wgpu/src/widget/canvas/geometry.rs b/graphics/src/widget/canvas/geometry.rs similarity index 100% rename from wgpu/src/widget/canvas/geometry.rs rename to graphics/src/widget/canvas/geometry.rs diff --git a/wgpu/src/widget/canvas/path.rs b/graphics/src/widget/canvas/path.rs similarity index 100% rename from wgpu/src/widget/canvas/path.rs rename to graphics/src/widget/canvas/path.rs diff --git a/wgpu/src/widget/canvas/path/arc.rs b/graphics/src/widget/canvas/path/arc.rs similarity index 100% rename from wgpu/src/widget/canvas/path/arc.rs rename to graphics/src/widget/canvas/path/arc.rs diff --git a/wgpu/src/widget/canvas/path/builder.rs b/graphics/src/widget/canvas/path/builder.rs similarity index 100% rename from wgpu/src/widget/canvas/path/builder.rs rename to graphics/src/widget/canvas/path/builder.rs diff --git a/wgpu/src/widget/canvas/program.rs b/graphics/src/widget/canvas/program.rs similarity index 100% rename from wgpu/src/widget/canvas/program.rs rename to graphics/src/widget/canvas/program.rs diff --git a/wgpu/src/widget/canvas/stroke.rs b/graphics/src/widget/canvas/stroke.rs similarity index 100% rename from wgpu/src/widget/canvas/stroke.rs rename to graphics/src/widget/canvas/stroke.rs diff --git a/wgpu/src/widget/canvas/text.rs b/graphics/src/widget/canvas/text.rs similarity index 100% rename from wgpu/src/widget/canvas/text.rs rename to graphics/src/widget/canvas/text.rs diff --git a/graphics/src/widget/checkbox.rs b/graphics/src/widget/checkbox.rs new file mode 100644 index 00000000..3c8b58de --- /dev/null +++ b/graphics/src/widget/checkbox.rs @@ -0,0 +1,10 @@ +//! Show toggle controls using checkboxes. +use crate::Renderer; + +pub use iced_style::checkbox::{Style, StyleSheet}; + +/// A box that can be checked. +/// +/// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`. +pub type Checkbox = + iced_native::Checkbox>; diff --git a/graphics/src/widget/container.rs b/graphics/src/widget/container.rs new file mode 100644 index 00000000..c4c4e5ba --- /dev/null +++ b/graphics/src/widget/container.rs @@ -0,0 +1,11 @@ +//! Decorate content and apply alignment. +use crate::Renderer; + +pub use iced_style::container::{Style, StyleSheet}; + +/// An element decorating some content. +/// +/// This is an alias of an `iced_native` container with a default +/// `Renderer`. +pub type Container<'a, Message, Backend> = + iced_native::Container<'a, Message, Renderer>; diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs new file mode 100644 index 00000000..34be8ee7 --- /dev/null +++ b/graphics/src/widget/pane_grid.rs @@ -0,0 +1,25 @@ +//! Let your users split regions of your application and organize layout dynamically. +//! +//! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) +//! +//! # Example +//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing, +//! drag and drop, and hotkey support. +//! +//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.1/examples/pane_grid +//! [`PaneGrid`]: type.PaneGrid.html +use crate::Renderer; + +pub use iced_native::pane_grid::{ + Axis, Direction, DragEvent, Focus, KeyPressEvent, Pane, ResizeEvent, Split, + State, +}; + +/// A collection of panes distributed using either vertical or horizontal splits +/// to completely fill the space available. +/// +/// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) +/// +/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`. +pub type PaneGrid<'a, Message, Backend> = + iced_native::PaneGrid<'a, Message, Renderer>; diff --git a/graphics/src/widget/progress_bar.rs b/graphics/src/widget/progress_bar.rs new file mode 100644 index 00000000..c1e5702e --- /dev/null +++ b/graphics/src/widget/progress_bar.rs @@ -0,0 +1,15 @@ +//! Allow your users to visually track the progress of a computation. +//! +//! A [`ProgressBar`] has a range of possible values and a current value, +//! as well as a length, height and style. +//! +//! [`ProgressBar`]: type.ProgressBar.html +use crate::Renderer; + +pub use iced_style::progress_bar::{Style, StyleSheet}; + +/// A bar that displays progress. +/// +/// This is an alias of an `iced_native` progress bar with an +/// `iced_wgpu::Renderer`. +pub type ProgressBar = iced_native::ProgressBar>; diff --git a/graphics/src/widget/radio.rs b/graphics/src/widget/radio.rs new file mode 100644 index 00000000..c621a26a --- /dev/null +++ b/graphics/src/widget/radio.rs @@ -0,0 +1,11 @@ +//! Create choices using radio buttons. +use crate::Renderer; + +pub use iced_style::radio::{Style, StyleSheet}; + +/// A circular button representing a choice. +/// +/// This is an alias of an `iced_native` radio button with an +/// `iced_wgpu::Renderer`. +pub type Radio = + iced_native::Radio>; diff --git a/graphics/src/widget/scrollable.rs b/graphics/src/widget/scrollable.rs new file mode 100644 index 00000000..61eae587 --- /dev/null +++ b/graphics/src/widget/scrollable.rs @@ -0,0 +1,13 @@ +//! Navigate an endless amount of content with a scrollbar. +use crate::Renderer; + +pub use iced_native::scrollable::State; +pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; + +/// A widget that can vertically display an infinite amount of content +/// with a scrollbar. +/// +/// This is an alias of an `iced_native` scrollable with a default +/// `Renderer`. +pub type Scrollable<'a, Message, Backend> = + iced_native::Scrollable<'a, Message, Renderer>; diff --git a/graphics/src/widget/slider.rs b/graphics/src/widget/slider.rs new file mode 100644 index 00000000..035c7c41 --- /dev/null +++ b/graphics/src/widget/slider.rs @@ -0,0 +1,17 @@ +//! Display an interactive selector of a single value from a range of values. +//! +//! A [`Slider`] has some local [`State`]. +//! +//! [`Slider`]: struct.Slider.html +//! [`State`]: struct.State.html +use crate::Renderer; + +pub use iced_native::slider::State; +pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; + +/// An horizontal bar and a handle that selects a single value from a range of +/// values. +/// +/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`. +pub type Slider<'a, Message, Backend> = + iced_native::Slider<'a, Message, Renderer>; diff --git a/graphics/src/widget/text.rs b/graphics/src/widget/text.rs new file mode 100644 index 00000000..ec0349f9 --- /dev/null +++ b/graphics/src/widget/text.rs @@ -0,0 +1,7 @@ +//! Write some text for your users to read. +use crate::Renderer; + +/// A paragraph of text. +/// +/// This is an alias of an `iced_native` text with an `iced_wgpu::Renderer`. +pub type Text = iced_native::Text>; diff --git a/graphics/src/widget/text_input.rs b/graphics/src/widget/text_input.rs new file mode 100644 index 00000000..8015626b --- /dev/null +++ b/graphics/src/widget/text_input.rs @@ -0,0 +1,16 @@ +//! Display fields that can be filled with text. +//! +//! A [`TextInput`] has some local [`State`]. +//! +//! [`TextInput`]: struct.TextInput.html +//! [`State`]: struct.State.html +use crate::Renderer; + +pub use iced_native::text_input::State; +pub use iced_style::text_input::{Style, StyleSheet}; + +/// A field that can be filled with text. +/// +/// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`. +pub type TextInput<'a, Message, Backend> = + iced_native::TextInput<'a, Message, Renderer>; diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 48cd6111..e963b601 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -102,7 +102,9 @@ where hasher.finish() }; - let layout = if hash == cache.hash && bounds == cache.bounds { + let layout_is_cached = hash == cache.hash && bounds == cache.bounds; + + let layout = if layout_is_cached { cache.layout } else { renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 612af1ed..88290576 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -9,12 +9,13 @@ repository = "https://github.com/hecrj/iced" [features] svg = ["resvg"] -canvas = ["lyon"] +canvas = ["iced_graphics/canvas"] [dependencies] wgpu = "0.5" wgpu_glyph = "0.8" zerocopy = "0.3" +bytemuck = "1.2" glyph_brush = "0.6" raw-window-handle = "0.3" glam = "0.8" @@ -29,9 +30,9 @@ gfx-memory = "=0.1.1" version = "0.2" path = "../native" -[dependencies.iced_style] +[dependencies.iced_graphics] version = "0.1" -path = "../style" +path = "../graphics" [dependencies.image] version = "0.23" @@ -42,10 +43,6 @@ version = "0.9" features = ["raqote-backend"] optional = true -[dependencies.lyon] -version = "0.15" -optional = true - [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] all-features = true diff --git a/wgpu/src/renderer.rs b/wgpu/src/backend.rs similarity index 89% rename from wgpu/src/renderer.rs rename to wgpu/src/backend.rs index 71b4af38..ba1a57a5 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/backend.rs @@ -1,22 +1,20 @@ -use crate::{ - quad, text, triangle, Defaults, Primitive, Quad, Settings, Target, - Transformation, -}; +use crate::quad; +use crate::text; +use crate::triangle; +use crate::{Quad, Settings, Target, Transformation}; +use iced_graphics::backend; +use iced_graphics::Primitive; +use iced_native::mouse; +use iced_native::{Background, Font, Point, Rectangle, Size, Vector}; #[cfg(any(feature = "image", feature = "svg"))] use crate::image::{self, Image}; -use iced_native::{ - layout, mouse, Background, Color, Layout, Point, Rectangle, Vector, Widget, -}; - -mod widget; - /// A [`wgpu`] renderer. /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs #[derive(Debug)] -pub struct Renderer { +pub struct Backend { quad_pipeline: quad::Pipeline, text_pipeline: text::Pipeline, triangle_pipeline: triangle::Pipeline, @@ -55,7 +53,7 @@ impl<'a> Layer<'a> { } } -impl Renderer { +impl Backend { /// Creates a new [`Renderer`]. /// /// [`Renderer`]: struct.Renderer.html @@ -451,57 +449,44 @@ impl Renderer { } } -impl iced_native::Renderer for Renderer { - type Output = (Primitive, mouse::Interaction); - type Defaults = Defaults; - - fn layout<'a, Message>( - &mut self, - element: &iced_native::Element<'a, Message, Self>, - limits: &iced_native::layout::Limits, - ) -> iced_native::layout::Node { - let node = element.layout(self, limits); - - self.text_pipeline.clear_measurement_cache(); - - node +impl iced_graphics::Backend for Backend { + fn trim_measurements(&mut self) { + self.text_pipeline.trim_measurement_cache() } } -impl layout::Debugger for Renderer { - fn explain( - &mut self, - defaults: &Defaults, - widget: &dyn Widget, - layout: Layout<'_>, - cursor_position: Point, - color: Color, - ) -> Self::Output { - let mut primitives = Vec::new(); - let (primitive, cursor) = - widget.draw(self, defaults, layout, cursor_position); +impl backend::Text for Backend { + const ICON_FONT: Font = text::BUILTIN_ICONS; + const CHECKMARK_ICON: char = text::CHECKMARK_ICON; - explain_layout(layout, color, &mut primitives); - primitives.push(primitive); + fn measure( + &self, + contents: &str, + size: f32, + font: Font, + bounds: Size, + ) -> (f32, f32) { + self.text_pipeline.measure(contents, size, font, bounds) + } - (Primitive::Group { primitives }, cursor) + fn space_width(&self, size: f32) -> f32 { + self.text_pipeline.space_width(size) } } -fn explain_layout( - layout: Layout<'_>, - color: Color, - primitives: &mut Vec, -) { - primitives.push(Primitive::Quad { - bounds: layout.bounds(), - background: Background::Color(Color::TRANSPARENT), - border_radius: 0, - border_width: 1, - border_color: [0.6, 0.6, 0.6, 0.5].into(), - }); - - for child in layout.children() { - explain_layout(child, color, primitives); +#[cfg(feature = "image")] +impl backend::Image for Backend { + fn dimensions(&self, handle: &iced_native::image::Handle) -> (u32, u32) { + self.image_pipeline.dimensions(handle) + } +} + +#[cfg(feature = "svg")] +impl backend::Svg for Backend { + fn viewport_dimensions( + &self, + handle: &iced_native::svg::Handle, + ) -> (u32, u32) { + self.image_pipeline.viewport_dimensions(handle) } } diff --git a/wgpu/src/defaults.rs b/wgpu/src/defaults.rs deleted file mode 100644 index 11718a87..00000000 --- a/wgpu/src/defaults.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Use default styling attributes to inherit styles. -use iced_native::Color; - -/// Some default styling attributes. -#[derive(Debug, Clone, Copy)] -pub struct Defaults { - /// Text styling - pub text: Text, -} - -impl Default for Defaults { - fn default() -> Defaults { - Defaults { - text: Text::default(), - } - } -} - -/// Some default text styling attributes. -#[derive(Debug, Clone, Copy)] -pub struct Text { - /// The default color of text - pub color: Color, -} - -impl Default for Text { - fn default() -> Text { - Text { - color: Color::BLACK, - } - } -} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 799c1f34..86827502 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -20,32 +20,29 @@ //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [WebGPU API]: https://gpuweb.github.io/gpuweb/ //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg))] -pub mod defaults; pub mod settings; pub mod triangle; pub mod widget; pub mod window; -mod primitive; +mod backend; mod quad; -mod renderer; mod target; mod text; mod transformation; mod viewport; +pub use iced_graphics::{Defaults, Primitive}; pub use wgpu; -pub use defaults::Defaults; -pub use primitive::Primitive; -pub use renderer::Renderer; +pub use backend::Backend; pub use settings::Settings; pub use target::Target; pub use viewport::Viewport; @@ -56,5 +53,7 @@ pub use widget::*; pub(crate) use quad::Quad; pub(crate) use transformation::Transformation; +pub type Renderer = iced_graphics::Renderer; + #[cfg(any(feature = "image", feature = "svg"))] mod image; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs deleted file mode 100644 index e73227ef..00000000 --- a/wgpu/src/primitive.rs +++ /dev/null @@ -1,107 +0,0 @@ -use iced_native::{ - image, svg, Background, Color, Font, HorizontalAlignment, Rectangle, Size, - Vector, VerticalAlignment, -}; - -use crate::triangle; -use std::sync::Arc; - -/// A rendering primitive. -#[derive(Debug, Clone)] -pub enum Primitive { - /// An empty primitive - None, - /// A group of primitives - Group { - /// The primitives of the group - primitives: Vec, - }, - /// A text primitive - Text { - /// The contents of the text - content: String, - /// The bounds of the text - bounds: Rectangle, - /// The color of the text - color: Color, - /// The size of the text - size: f32, - /// The font of the text - font: Font, - /// The horizontal alignment of the text - horizontal_alignment: HorizontalAlignment, - /// The vertical alignment of the text - vertical_alignment: VerticalAlignment, - }, - /// A quad primitive - Quad { - /// The bounds of the quad - bounds: Rectangle, - /// The background of the quad - background: Background, - /// The border radius of the quad - border_radius: u16, - /// The border width of the quad - border_width: u16, - /// The border color of the quad - border_color: Color, - }, - /// An image primitive - Image { - /// The handle of the image - handle: image::Handle, - /// The bounds of the image - bounds: Rectangle, - }, - /// An SVG primitive - Svg { - /// The path of the SVG file - handle: svg::Handle, - - /// The bounds of the viewport - bounds: Rectangle, - }, - /// A clip primitive - Clip { - /// The bounds of the clip - bounds: Rectangle, - /// The offset transformation of the clip - offset: Vector, - /// The content of the clip - content: Box, - }, - /// A primitive that applies a translation - Translate { - /// The translation vector - translation: Vector, - - /// The primitive to translate - content: Box, - }, - /// A low-level primitive to render a mesh of triangles. - /// - /// It can be used to render many kinds of geometry freely. - Mesh2D { - /// The size of the drawable region of the mesh. - /// - /// Any geometry that falls out of this region will be clipped. - size: Size, - - /// The vertex and index buffers of the mesh - buffers: triangle::Mesh2D, - }, - /// A cached primitive. - /// - /// This can be useful if you are implementing a widget where primitive - /// generation is expensive. - Cached { - /// The cached primitive - cache: Arc, - }, -} - -impl Default for Primitive { - fn default() -> Primitive { - Primitive::None - } -} diff --git a/wgpu/src/renderer/widget/column.rs b/wgpu/src/renderer/widget/column.rs deleted file mode 100644 index b853276d..00000000 --- a/wgpu/src/renderer/widget/column.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{column, mouse, Element, Layout, Point}; - -impl column::Renderer for Renderer { - fn draw( - &mut self, - defaults: &Self::Defaults, - content: &[Element<'_, Message, Self>], - layout: Layout<'_>, - cursor_position: Point, - ) -> Self::Output { - let mut mouse_interaction = mouse::Interaction::default(); - - ( - Primitive::Group { - primitives: content - .iter() - .zip(layout.children()) - .map(|(child, layout)| { - let (primitive, new_mouse_interaction) = - child.draw(self, defaults, layout, cursor_position); - - if new_mouse_interaction > mouse_interaction { - mouse_interaction = new_mouse_interaction; - } - - primitive - }) - .collect(), - }, - mouse_interaction, - ) - } -} diff --git a/wgpu/src/renderer/widget/container.rs b/wgpu/src/renderer/widget/container.rs deleted file mode 100644 index 30cc3f07..00000000 --- a/wgpu/src/renderer/widget/container.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::{container, defaults, Defaults, Primitive, Renderer}; -use iced_native::{Background, Color, Element, Layout, Point, Rectangle}; - -impl iced_native::container::Renderer for Renderer { - type Style = Box; - - fn draw( - &mut self, - defaults: &Defaults, - bounds: Rectangle, - cursor_position: Point, - style_sheet: &Self::Style, - content: &Element<'_, Message, Self>, - content_layout: Layout<'_>, - ) -> Self::Output { - let style = style_sheet.style(); - - let defaults = Defaults { - text: defaults::Text { - color: style.text_color.unwrap_or(defaults.text.color), - }, - }; - - let (content, mouse_interaction) = - content.draw(self, &defaults, content_layout, cursor_position); - - if style.background.is_some() || style.border_width > 0 { - let quad = Primitive::Quad { - bounds, - background: style - .background - .unwrap_or(Background::Color(Color::TRANSPARENT)), - border_radius: style.border_radius, - border_width: style.border_width, - border_color: style.border_color, - }; - - ( - Primitive::Group { - primitives: vec![quad, content], - }, - mouse_interaction, - ) - } else { - (content, mouse_interaction) - } - } -} diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs deleted file mode 100644 index 2253e4af..00000000 --- a/wgpu/src/renderer/widget/pane_grid.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{ - mouse, - pane_grid::{self, Axis, Pane}, - Element, Layout, Point, Rectangle, Vector, -}; - -impl pane_grid::Renderer for Renderer { - fn draw( - &mut self, - defaults: &Self::Defaults, - content: &[(Pane, Element<'_, Message, Self>)], - dragging: Option, - resizing: Option, - layout: Layout<'_>, - cursor_position: Point, - ) -> Self::Output { - let pane_cursor_position = if dragging.is_some() { - // TODO: Remove once cursor availability is encoded in the type - // system - Point::new(-1.0, -1.0) - } else { - cursor_position - }; - - let mut mouse_interaction = mouse::Interaction::default(); - let mut dragged_pane = None; - - let mut panes: Vec<_> = content - .iter() - .zip(layout.children()) - .enumerate() - .map(|(i, ((id, pane), layout))| { - let (primitive, new_mouse_interaction) = - pane.draw(self, defaults, layout, pane_cursor_position); - - if new_mouse_interaction > mouse_interaction { - mouse_interaction = new_mouse_interaction; - } - - if Some(*id) == dragging { - dragged_pane = Some((i, layout)); - } - - primitive - }) - .collect(); - - let primitives = if let Some((index, layout)) = dragged_pane { - let pane = panes.remove(index); - let bounds = layout.bounds(); - - // TODO: Fix once proper layering is implemented. - // This is a pretty hacky way to achieve layering. - let clip = Primitive::Clip { - bounds: Rectangle { - x: cursor_position.x - bounds.width / 2.0, - y: cursor_position.y - bounds.height / 2.0, - width: bounds.width + 0.5, - height: bounds.height + 0.5, - }, - offset: Vector::new(0, 0), - content: Box::new(Primitive::Translate { - translation: Vector::new( - cursor_position.x - bounds.x - bounds.width / 2.0, - cursor_position.y - bounds.y - bounds.height / 2.0, - ), - content: Box::new(pane), - }), - }; - - panes.push(clip); - - panes - } else { - panes - }; - - ( - Primitive::Group { primitives }, - if dragging.is_some() { - mouse::Interaction::Grabbing - } else if let Some(axis) = resizing { - match axis { - Axis::Horizontal => mouse::Interaction::ResizingVertically, - Axis::Vertical => mouse::Interaction::ResizingHorizontally, - } - } else { - mouse_interaction - }, - ) - } -} diff --git a/wgpu/src/renderer/widget/progress_bar.rs b/wgpu/src/renderer/widget/progress_bar.rs deleted file mode 100644 index 2baeeb14..00000000 --- a/wgpu/src/renderer/widget/progress_bar.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::{progress_bar::StyleSheet, Primitive, Renderer}; -use iced_native::{mouse, progress_bar, Color, Rectangle}; - -impl progress_bar::Renderer for Renderer { - type Style = Box; - - const DEFAULT_HEIGHT: u16 = 30; - - fn draw( - &self, - bounds: Rectangle, - range: std::ops::RangeInclusive, - value: f32, - style_sheet: &Self::Style, - ) -> Self::Output { - let style = style_sheet.style(); - - let (range_start, range_end) = range.into_inner(); - let active_progress_width = bounds.width - * ((value - range_start) / (range_end - range_start).max(1.0)); - - let background = Primitive::Group { - primitives: vec![Primitive::Quad { - bounds: Rectangle { ..bounds }, - background: style.background, - border_radius: style.border_radius, - border_width: 0, - border_color: Color::TRANSPARENT, - }], - }; - - ( - if active_progress_width > 0.0 { - let bar = Primitive::Quad { - bounds: Rectangle { - width: active_progress_width, - ..bounds - }, - background: style.bar, - border_radius: style.border_radius, - border_width: 0, - border_color: Color::TRANSPARENT, - }; - - Primitive::Group { - primitives: vec![background, bar], - } - } else { - background - }, - mouse::Interaction::default(), - ) - } -} diff --git a/wgpu/src/renderer/widget/radio.rs b/wgpu/src/renderer/widget/radio.rs deleted file mode 100644 index cee0deb6..00000000 --- a/wgpu/src/renderer/widget/radio.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::{radio::StyleSheet, Primitive, Renderer}; -use iced_native::{mouse, radio, Background, Color, Rectangle}; - -const SIZE: f32 = 28.0; -const DOT_SIZE: f32 = SIZE / 2.0; - -impl radio::Renderer for Renderer { - type Style = Box; - - const DEFAULT_SIZE: u16 = SIZE as u16; - const DEFAULT_SPACING: u16 = 15; - - fn draw( - &mut self, - bounds: Rectangle, - is_selected: bool, - is_mouse_over: bool, - (label, _): Self::Output, - style_sheet: &Self::Style, - ) -> Self::Output { - let style = if is_mouse_over { - style_sheet.hovered() - } else { - style_sheet.active() - }; - - let radio = Primitive::Quad { - bounds, - background: style.background, - border_radius: (SIZE / 2.0) as u16, - border_width: style.border_width, - border_color: style.border_color, - }; - - ( - Primitive::Group { - primitives: if is_selected { - let radio_circle = Primitive::Quad { - bounds: Rectangle { - x: bounds.x + DOT_SIZE / 2.0, - y: bounds.y + DOT_SIZE / 2.0, - width: bounds.width - DOT_SIZE, - height: bounds.height - DOT_SIZE, - }, - background: Background::Color(style.dot_color), - border_radius: (DOT_SIZE / 2.0) as u16, - border_width: 0, - border_color: Color::TRANSPARENT, - }; - - vec![radio, radio_circle, label] - } else { - vec![radio, label] - }, - }, - if is_mouse_over { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - }, - ) - } -} diff --git a/wgpu/src/renderer/widget/scrollable.rs b/wgpu/src/renderer/widget/scrollable.rs deleted file mode 100644 index 8a400b82..00000000 --- a/wgpu/src/renderer/widget/scrollable.rs +++ /dev/null @@ -1,125 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{mouse, scrollable, Background, Color, Rectangle, Vector}; - -const SCROLLBAR_WIDTH: u16 = 10; -const SCROLLBAR_MARGIN: u16 = 2; - -impl scrollable::Renderer for Renderer { - type Style = Box; - - fn scrollbar( - &self, - bounds: Rectangle, - content_bounds: Rectangle, - offset: u32, - ) -> Option { - if content_bounds.height > bounds.height { - let scrollbar_bounds = Rectangle { - x: bounds.x + bounds.width - - f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), - y: bounds.y, - width: f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), - height: bounds.height, - }; - - let ratio = bounds.height / content_bounds.height; - let scrollbar_height = bounds.height * ratio; - let y_offset = offset as f32 * ratio; - - let scroller_bounds = Rectangle { - x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), - y: scrollbar_bounds.y + y_offset, - width: scrollbar_bounds.width - f32::from(2 * SCROLLBAR_MARGIN), - height: scrollbar_height, - }; - - Some(scrollable::Scrollbar { - bounds: scrollbar_bounds, - scroller: scrollable::Scroller { - bounds: scroller_bounds, - }, - }) - } else { - None - } - } - - fn draw( - &mut self, - state: &scrollable::State, - bounds: Rectangle, - _content_bounds: Rectangle, - is_mouse_over: bool, - is_mouse_over_scrollbar: bool, - scrollbar: Option, - offset: u32, - style_sheet: &Self::Style, - (content, mouse_interaction): Self::Output, - ) -> Self::Output { - ( - if let Some(scrollbar) = scrollbar { - let clip = Primitive::Clip { - bounds, - offset: Vector::new(0, offset), - content: Box::new(content), - }; - - let style = if state.is_scroller_grabbed() { - style_sheet.dragging() - } else if is_mouse_over_scrollbar { - style_sheet.hovered() - } else { - style_sheet.active() - }; - - let is_scrollbar_visible = - style.background.is_some() || style.border_width > 0; - - let scroller = if is_mouse_over - || state.is_scroller_grabbed() - || is_scrollbar_visible - { - Primitive::Quad { - bounds: scrollbar.scroller.bounds, - background: Background::Color(style.scroller.color), - border_radius: style.scroller.border_radius, - border_width: style.scroller.border_width, - border_color: style.scroller.border_color, - } - } else { - Primitive::None - }; - - let scrollbar = if is_scrollbar_visible { - Primitive::Quad { - bounds: Rectangle { - x: scrollbar.bounds.x + f32::from(SCROLLBAR_MARGIN), - width: scrollbar.bounds.width - - f32::from(2 * SCROLLBAR_MARGIN), - ..scrollbar.bounds - }, - background: style - .background - .unwrap_or(Background::Color(Color::TRANSPARENT)), - border_radius: style.border_radius, - border_width: style.border_width, - border_color: style.border_color, - } - } else { - Primitive::None - }; - - Primitive::Group { - primitives: vec![clip, scrollbar, scroller], - } - } else { - content - }, - if is_mouse_over_scrollbar || state.is_scroller_grabbed() { - mouse::Interaction::Idle - } else { - mouse_interaction - }, - ) - } -} diff --git a/wgpu/src/renderer/widget/space.rs b/wgpu/src/renderer/widget/space.rs deleted file mode 100644 index 225f7e6c..00000000 --- a/wgpu/src/renderer/widget/space.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{mouse, space, Rectangle}; - -impl space::Renderer for Renderer { - fn draw(&mut self, _bounds: Rectangle) -> Self::Output { - (Primitive::None, mouse::Interaction::default()) - } -} diff --git a/wgpu/src/renderer/widget/text.rs b/wgpu/src/renderer/widget/text.rs deleted file mode 100644 index 4605ed06..00000000 --- a/wgpu/src/renderer/widget/text.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{ - mouse, text, Color, Font, HorizontalAlignment, Rectangle, Size, - VerticalAlignment, -}; - -use std::f32; - -impl text::Renderer for Renderer { - type Font = Font; - - const DEFAULT_SIZE: u16 = 20; - - fn measure( - &self, - content: &str, - size: u16, - font: Font, - bounds: Size, - ) -> (f32, f32) { - self.text_pipeline - .measure(content, f32::from(size), font, bounds) - } - - fn draw( - &mut self, - defaults: &Self::Defaults, - bounds: Rectangle, - content: &str, - size: u16, - font: Font, - color: Option, - horizontal_alignment: HorizontalAlignment, - vertical_alignment: VerticalAlignment, - ) -> Self::Output { - let x = match horizontal_alignment { - iced_native::HorizontalAlignment::Left => bounds.x, - iced_native::HorizontalAlignment::Center => bounds.center_x(), - iced_native::HorizontalAlignment::Right => bounds.x + bounds.width, - }; - - let y = match vertical_alignment { - iced_native::VerticalAlignment::Top => bounds.y, - iced_native::VerticalAlignment::Center => bounds.center_y(), - iced_native::VerticalAlignment::Bottom => bounds.y + bounds.height, - }; - - ( - Primitive::Text { - content: content.to_string(), - size: f32::from(size), - bounds: Rectangle { x, y, ..bounds }, - color: color.unwrap_or(defaults.text.color), - font, - horizontal_alignment, - vertical_alignment, - }, - mouse::Interaction::default(), - ) - } -} diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index f4521e72..8bcd6d83 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -138,7 +138,7 @@ impl Pipeline { .advance_width } - pub fn clear_measurement_cache(&mut self) { + pub fn trim_measurement_cache(&mut self) { // TODO: We should probably use a `GlyphCalculator` for this. However, // it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop. // This makes stuff quite inconvenient. A manual method for trimming the diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 3e68a269..a71bcedc 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -4,6 +4,8 @@ use iced_native::{Rectangle, Vector}; use std::mem; use zerocopy::AsBytes; +pub use iced_graphics::triangle::{Mesh2D, Vertex2D}; + mod msaa; const UNIFORM_BUFFER_SIZE: usize = 100; @@ -236,7 +238,7 @@ impl Pipeline { .into(); let vertex_buffer = device.create_buffer_with_data( - mesh.vertices.as_bytes(), + bytemuck::cast_slice(&mesh.vertices), wgpu::BufferUsage::COPY_SRC, ); @@ -387,26 +389,3 @@ impl From for Uniforms { } } } - -/// A two-dimensional vertex with some color in __linear__ RGBA. -#[repr(C)] -#[derive(Copy, Clone, Debug, AsBytes)] -pub struct Vertex2D { - /// The vertex position - pub position: [f32; 2], - /// The vertex color in __linear__ RGBA. - pub color: [f32; 4], -} - -/// A set of [`Vertex2D`] and indices representing a list of triangles. -/// -/// [`Vertex2D`]: struct.Vertex2D.html -#[derive(Clone, Debug)] -pub struct Mesh2D { - /// The vertices of the mesh - pub vertices: Vec, - /// The list of vertex indices that defines the triangles of the mesh. - /// - /// Therefore, this list should always have a length that is a multiple of 3. - pub indices: Vec, -} diff --git a/wgpu/src/widget/button.rs b/wgpu/src/widget/button.rs index b738c55e..fee7a7f8 100644 --- a/wgpu/src/widget/button.rs +++ b/wgpu/src/widget/button.rs @@ -6,8 +6,8 @@ //! [`State`]: struct.State.html use crate::Renderer; +pub use iced_graphics::button::{Style, StyleSheet}; pub use iced_native::button::State; -pub use iced_style::button::{Style, StyleSheet}; /// A widget that produces a message when clicked. /// diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 2fc10ea0..bef34857 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -6,228 +6,4 @@ //! //! [`Canvas`]: struct.Canvas.html //! [`Frame`]: struct.Frame.html -use crate::{Defaults, Primitive, Renderer}; - -use iced_native::{ - layout, mouse, Clipboard, Element, Hasher, Layout, Length, Point, Size, - Vector, Widget, -}; -use std::hash::Hash; -use std::marker::PhantomData; - -pub mod path; - -mod cache; -mod cursor; -mod event; -mod fill; -mod frame; -mod geometry; -mod program; -mod stroke; -mod text; - -pub use cache::Cache; -pub use cursor::Cursor; -pub use event::Event; -pub use fill::Fill; -pub use frame::Frame; -pub use geometry::Geometry; -pub use path::Path; -pub use program::Program; -pub use stroke::{LineCap, LineJoin, Stroke}; -pub use text::Text; - -/// A widget capable of drawing 2D graphics. -/// -/// [`Canvas`]: struct.Canvas.html -/// -/// # Examples -/// The repository has a couple of [examples] showcasing how to use a -/// [`Canvas`]: -/// -/// - [`clock`], an application that uses the [`Canvas`] widget to draw a clock -/// and its hands to display the current time. -/// - [`game_of_life`], an interactive version of the Game of Life, invented by -/// John Conway. -/// - [`solar_system`], an animated solar system drawn using the [`Canvas`] widget -/// and showcasing how to compose different transforms. -/// -/// [examples]: https://github.com/hecrj/iced/tree/master/examples -/// [`clock`]: https://github.com/hecrj/iced/tree/master/examples/clock -/// [`game_of_life`]: https://github.com/hecrj/iced/tree/master/examples/game_of_life -/// [`solar_system`]: https://github.com/hecrj/iced/tree/master/examples/solar_system -/// -/// ## Drawing a simple circle -/// If you want to get a quick overview, here's how we can draw a simple circle: -/// -/// ```no_run -/// # mod iced { -/// # pub use iced_wgpu::canvas; -/// # pub use iced_native::{Color, Rectangle}; -/// # } -/// use iced::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; -/// use iced::{Color, Rectangle}; -/// -/// // First, we define the data we need for drawing -/// #[derive(Debug)] -/// struct Circle { -/// radius: f32, -/// } -/// -/// // Then, we implement the `Program` trait -/// impl Program<()> for Circle { -/// fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec{ -/// // We prepare a new `Frame` -/// let mut frame = Frame::new(bounds.size()); -/// -/// // We create a `Path` representing a simple circle -/// let circle = Path::circle(frame.center(), self.radius); -/// -/// // And fill it with some color -/// frame.fill(&circle, Fill::Color(Color::BLACK)); -/// -/// // Finally, we produce the geometry -/// vec![frame.into_geometry()] -/// } -/// } -/// -/// // Finally, we simply use our `Circle` to create the `Canvas`! -/// let canvas = Canvas::new(Circle { radius: 50.0 }); -/// ``` -#[derive(Debug)] -pub struct Canvas> { - width: Length, - height: Length, - program: P, - phantom: PhantomData, -} - -impl> Canvas { - const DEFAULT_SIZE: u16 = 100; - - /// Creates a new [`Canvas`]. - /// - /// [`Canvas`]: struct.Canvas.html - pub fn new(program: P) -> Self { - Canvas { - width: Length::Units(Self::DEFAULT_SIZE), - height: Length::Units(Self::DEFAULT_SIZE), - program, - phantom: PhantomData, - } - } - - /// Sets the width of the [`Canvas`]. - /// - /// [`Canvas`]: struct.Canvas.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Canvas`]. - /// - /// [`Canvas`]: struct.Canvas.html - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } -} - -impl> Widget - for Canvas -{ - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - _renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let limits = limits.width(self.width).height(self.height); - let size = limits.resolve(Size::ZERO); - - layout::Node::new(size) - } - - fn on_event( - &mut self, - event: iced_native::Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - _renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, - ) { - let bounds = layout.bounds(); - - let canvas_event = match event { - iced_native::Event::Mouse(mouse_event) => { - Some(Event::Mouse(mouse_event)) - } - _ => None, - }; - - let cursor = Cursor::from_window_position(cursor_position); - - if let Some(canvas_event) = canvas_event { - if let Some(message) = - self.program.update(canvas_event, bounds, cursor) - { - messages.push(message); - } - } - } - - fn draw( - &self, - _renderer: &mut Renderer, - _defaults: &Defaults, - layout: Layout<'_>, - cursor_position: Point, - ) -> (Primitive, mouse::Interaction) { - let bounds = layout.bounds(); - let translation = Vector::new(bounds.x, bounds.y); - let cursor = Cursor::from_window_position(cursor_position); - - ( - Primitive::Translate { - translation, - content: Box::new(Primitive::Group { - primitives: self - .program - .draw(bounds, cursor) - .into_iter() - .map(Geometry::into_primitive) - .collect(), - }), - }, - self.program.mouse_interaction(bounds, cursor), - ) - } - - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::().hash(state); - - self.width.hash(state); - self.height.hash(state); - } -} - -impl<'a, Message, P: Program + 'a> From> - for Element<'a, Message, Renderer> -where - Message: 'static, -{ - fn from(canvas: Canvas) -> Element<'a, Message, Renderer> { - Element::new(canvas) - } -} +pub use iced_graphics::canvas::*; diff --git a/wgpu/src/widget/checkbox.rs b/wgpu/src/widget/checkbox.rs index da0d7a84..d27d77cc 100644 --- a/wgpu/src/widget/checkbox.rs +++ b/wgpu/src/widget/checkbox.rs @@ -1,7 +1,7 @@ //! Show toggle controls using checkboxes. use crate::Renderer; -pub use iced_style::checkbox::{Style, StyleSheet}; +pub use iced_graphics::checkbox::{Style, StyleSheet}; /// A box that can be checked. /// diff --git a/wgpu/src/widget/container.rs b/wgpu/src/widget/container.rs index 9a93a246..bc26cef2 100644 --- a/wgpu/src/widget/container.rs +++ b/wgpu/src/widget/container.rs @@ -1,7 +1,7 @@ //! Decorate content and apply alignment. use crate::Renderer; -pub use iced_style::container::{Style, StyleSheet}; +pub use iced_graphics::container::{Style, StyleSheet}; /// An element decorating some content. /// diff --git a/wgpu/src/widget/progress_bar.rs b/wgpu/src/widget/progress_bar.rs index 770bcea8..a636a3a6 100644 --- a/wgpu/src/widget/progress_bar.rs +++ b/wgpu/src/widget/progress_bar.rs @@ -6,7 +6,7 @@ //! [`ProgressBar`]: type.ProgressBar.html use crate::Renderer; -pub use iced_style::progress_bar::{Style, StyleSheet}; +pub use iced_graphics::progress_bar::{Style, StyleSheet}; /// A bar that displays progress. /// diff --git a/wgpu/src/widget/radio.rs b/wgpu/src/widget/radio.rs index 6e5cf042..0b843d1f 100644 --- a/wgpu/src/widget/radio.rs +++ b/wgpu/src/widget/radio.rs @@ -1,7 +1,7 @@ //! Create choices using radio buttons. use crate::Renderer; -pub use iced_style::radio::{Style, StyleSheet}; +pub use iced_graphics::radio::{Style, StyleSheet}; /// A circular button representing a choice. /// diff --git a/wgpu/src/widget/scrollable.rs b/wgpu/src/widget/scrollable.rs index 1d236105..fabb4318 100644 --- a/wgpu/src/widget/scrollable.rs +++ b/wgpu/src/widget/scrollable.rs @@ -1,8 +1,8 @@ //! Navigate an endless amount of content with a scrollbar. use crate::Renderer; +pub use iced_graphics::scrollable::{Scrollbar, Scroller, StyleSheet}; pub use iced_native::scrollable::State; -pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; /// A widget that can vertically display an infinite amount of content /// with a scrollbar. diff --git a/wgpu/src/widget/slider.rs b/wgpu/src/widget/slider.rs index 4e47978f..cf036829 100644 --- a/wgpu/src/widget/slider.rs +++ b/wgpu/src/widget/slider.rs @@ -6,8 +6,8 @@ //! [`State`]: struct.State.html use crate::Renderer; +pub use iced_graphics::slider::{Handle, HandleShape, Style, StyleSheet}; pub use iced_native::slider::State; -pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; /// An horizontal bar and a handle that selects a single value from a range of /// values. diff --git a/wgpu/src/widget/text_input.rs b/wgpu/src/widget/text_input.rs index 260fe3a6..1da3fbe6 100644 --- a/wgpu/src/widget/text_input.rs +++ b/wgpu/src/widget/text_input.rs @@ -6,8 +6,8 @@ //! [`State`]: struct.State.html use crate::Renderer; +pub use iced_graphics::text_input::{Style, StyleSheet}; pub use iced_native::text_input::State; -pub use iced_style::text_input::{Style, StyleSheet}; /// A field that can be filled with text. /// diff --git a/wgpu/src/window/backend.rs b/wgpu/src/window/backend.rs index b467486c..92e81cd9 100644 --- a/wgpu/src/window/backend.rs +++ b/wgpu/src/window/backend.rs @@ -51,7 +51,7 @@ impl iced_native::window::Backend for Backend { } fn create_renderer(&mut self, settings: Settings) -> Renderer { - Renderer::new(&mut self.device, settings) + Renderer::new(crate::Backend::new(&mut self.device, settings)) } fn create_surface( @@ -100,7 +100,7 @@ impl iced_native::window::Backend for Backend { depth_stencil_attachment: None, }); - let mouse_interaction = renderer.draw( + let mouse_interaction = renderer.backend_mut().draw( &mut self.device, &mut encoder, Target { From 750a441a8c7c76b240db238283e9cbdab8d6932d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 19 May 2020 19:55:05 +0200 Subject: [PATCH 03/40] Move `Transformation` to `iced_graphics` --- glow/src/lib.rs | 3 +- graphics/Cargo.toml | 1 + graphics/src/lib.rs | 2 + {glow => graphics}/src/transformation.rs | 0 wgpu/src/lib.rs | 3 +- wgpu/src/transformation.rs | 54 ------------------------ 6 files changed, 5 insertions(+), 58 deletions(-) rename {glow => graphics}/src/transformation.rs (100%) delete mode 100644 wgpu/src/transformation.rs diff --git a/glow/src/lib.rs b/glow/src/lib.rs index 27c39b99..f8032433 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -7,7 +7,6 @@ mod backend; mod quad; mod text; -mod transformation; mod triangle; mod viewport; @@ -19,8 +18,8 @@ pub use settings::Settings; pub use viewport::Viewport; pub(crate) use backend::Backend; +pub(crate) use iced_graphics::Transformation; pub(crate) use quad::Quad; -pub(crate) use transformation::Transformation; pub type Renderer = iced_graphics::Renderer; diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index dcbf5ce4..a81807d6 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -9,6 +9,7 @@ canvas = ["lyon"] [dependencies] bytemuck = "1.2" +glam = "0.8" [dependencies.iced_native] version = "0.2" diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 596f72a4..6e5105d2 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -1,6 +1,7 @@ mod defaults; mod primitive; mod renderer; +mod transformation; mod widget; pub mod backend; @@ -13,3 +14,4 @@ pub use backend::Backend; pub use defaults::Defaults; pub use primitive::Primitive; pub use renderer::Renderer; +pub use transformation::Transformation; diff --git a/glow/src/transformation.rs b/graphics/src/transformation.rs similarity index 100% rename from glow/src/transformation.rs rename to graphics/src/transformation.rs diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 86827502..d2905bd0 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -36,7 +36,6 @@ mod backend; mod quad; mod target; mod text; -mod transformation; mod viewport; pub use iced_graphics::{Defaults, Primitive}; @@ -50,8 +49,8 @@ pub use viewport::Viewport; #[doc(no_inline)] pub use widget::*; +pub(crate) use iced_graphics::Transformation; pub(crate) use quad::Quad; -pub(crate) use transformation::Transformation; pub type Renderer = iced_graphics::Renderer; diff --git a/wgpu/src/transformation.rs b/wgpu/src/transformation.rs deleted file mode 100644 index ff3b1d00..00000000 --- a/wgpu/src/transformation.rs +++ /dev/null @@ -1,54 +0,0 @@ -use glam::{Mat4, Vec3, Vec4}; -use std::ops::Mul; - -/// A 2D transformation matrix. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Transformation(Mat4); - -impl Transformation { - /// Get the identity transformation. - pub fn identity() -> Transformation { - Transformation(Mat4::identity()) - } - - /// Creates an orthographic projection. - #[rustfmt::skip] - pub fn orthographic(width: u32, height: u32) -> Transformation { - Transformation(Mat4::from_cols( - Vec4::new(2.0 / width as f32, 0.0, 0.0, 0.0), - Vec4::new(0.0, -2.0 / height as f32, 0.0, 0.0), - Vec4::new(0.0, 0.0, -1.0, 0.0), - Vec4::new(-1.0, 1.0, 0.0, 1.0) - )) - } - - /// Creates a translate transformation. - pub fn translate(x: f32, y: f32) -> Transformation { - Transformation(Mat4::from_translation(Vec3::new(x, y, 0.0))) - } - - /// Creates a scale transformation. - pub fn scale(x: f32, y: f32) -> Transformation { - Transformation(Mat4::from_scale(Vec3::new(x, y, 1.0))) - } -} - -impl Mul for Transformation { - type Output = Self; - - fn mul(self, rhs: Self) -> Self { - Transformation(self.0 * rhs.0) - } -} - -impl AsRef<[f32; 16]> for Transformation { - fn as_ref(&self) -> &[f32; 16] { - self.0.as_ref() - } -} - -impl From for [f32; 16] { - fn from(t: Transformation) -> [f32; 16] { - *t.as_ref() - } -} From a0ac09122a68d9be7d11e5cc765f52cb526ae913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 19 May 2020 19:57:42 +0200 Subject: [PATCH 04/40] Move `Viewport` to `iced_graphics` --- glow/src/lib.rs | 7 +++---- graphics/src/lib.rs | 2 ++ {glow => graphics}/src/viewport.rs | 2 +- wgpu/src/lib.rs | 4 +--- wgpu/src/viewport.rs | 29 ----------------------------- 5 files changed, 7 insertions(+), 37 deletions(-) rename {glow => graphics}/src/viewport.rs (92%) delete mode 100644 wgpu/src/viewport.rs diff --git a/glow/src/lib.rs b/glow/src/lib.rs index f8032433..724065bd 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -8,14 +8,12 @@ mod backend; mod quad; mod text; mod triangle; -mod viewport; pub mod settings; pub mod widget; pub mod window; pub use settings::Settings; -pub use viewport::Viewport; pub(crate) use backend::Backend; pub(crate) use iced_graphics::Transformation; @@ -26,9 +24,10 @@ pub type Renderer = iced_graphics::Renderer; #[doc(no_inline)] pub use widget::*; +pub type Element<'a, Message> = iced_native::Element<'a, Message, Renderer>; + +pub use iced_graphics::Viewport; pub use iced_native::{ Background, Color, Command, HorizontalAlignment, Length, Vector, VerticalAlignment, }; - -pub type Element<'a, Message> = iced_native::Element<'a, Message, Renderer>; diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 6e5105d2..bdaefb41 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -2,6 +2,7 @@ mod defaults; mod primitive; mod renderer; mod transformation; +mod viewport; mod widget; pub mod backend; @@ -15,3 +16,4 @@ pub use defaults::Defaults; pub use primitive::Primitive; pub use renderer::Renderer; pub use transformation::Transformation; +pub use viewport::Viewport; diff --git a/glow/src/viewport.rs b/graphics/src/viewport.rs similarity index 92% rename from glow/src/viewport.rs rename to graphics/src/viewport.rs index c1afee87..48e5a249 100644 --- a/glow/src/viewport.rs +++ b/graphics/src/viewport.rs @@ -27,7 +27,7 @@ impl Viewport { (self.width, self.height) } - pub(crate) fn transformation(&self) -> Transformation { + pub fn transformation(&self) -> Transformation { self.transformation } } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index d2905bd0..99897e60 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -36,15 +36,13 @@ mod backend; mod quad; mod target; mod text; -mod viewport; -pub use iced_graphics::{Defaults, Primitive}; +pub use iced_graphics::{Defaults, Primitive, Viewport}; pub use wgpu; pub use backend::Backend; pub use settings::Settings; pub use target::Target; -pub use viewport::Viewport; #[doc(no_inline)] pub use widget::*; diff --git a/wgpu/src/viewport.rs b/wgpu/src/viewport.rs deleted file mode 100644 index 66242468..00000000 --- a/wgpu/src/viewport.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::Transformation; - -/// A viewing region for displaying computer graphics. -#[derive(Debug)] -pub struct Viewport { - width: u32, - height: u32, - transformation: Transformation, -} - -impl Viewport { - /// Creates a new [`Viewport`] with the given dimensions. - pub fn new(width: u32, height: u32) -> Viewport { - Viewport { - width, - height, - transformation: Transformation::orthographic(width, height), - } - } - - /// Returns the dimensions of the [`Viewport`]. - pub fn dimensions(&self) -> (u32, u32) { - (self.width, self.height) - } - - pub(crate) fn transformation(&self) -> Transformation { - self.transformation - } -} From 4aed0fa4b6d63b739b5557ef16f6077988cd2758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 19 May 2020 20:01:55 +0200 Subject: [PATCH 05/40] Rename `window::Backend` to `Compositor` --- examples/tour/src/main.rs | 2 +- glow/src/window.rs | 4 +-- glow/src/window/{backend.rs => compositor.rs} | 10 +++--- native/src/window.rs | 4 +-- .../src/window/{backend.rs => compositor.rs} | 4 +-- src/application.rs | 2 +- wgpu/src/window.rs | 4 +-- wgpu/src/window/{backend.rs => compositor.rs} | 8 ++--- winit/src/application.rs | 32 ++++++++++++------- 9 files changed, 39 insertions(+), 31 deletions(-) rename glow/src/window/{backend.rs => compositor.rs} (96%) rename native/src/window/{backend.rs => compositor.rs} (95%) rename wgpu/src/window/{backend.rs => compositor.rs} (96%) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 729ae8fb..8c3b1d19 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -26,7 +26,7 @@ pub struct Tour { } impl Application for Tour { - type Backend = window::Backend; + type Compositor = window::Compositor; type Executor = executor::Null; type Message = Message; type Flags = (); diff --git a/glow/src/window.rs b/glow/src/window.rs index a8edb016..aac5fb9e 100644 --- a/glow/src/window.rs +++ b/glow/src/window.rs @@ -1,4 +1,4 @@ //! Display rendering results on windows. -mod backend; +mod compositor; -pub use backend::Backend; +pub use compositor::Compositor; diff --git a/glow/src/window/backend.rs b/glow/src/window/compositor.rs similarity index 96% rename from glow/src/window/backend.rs rename to glow/src/window/compositor.rs index 34245f35..8f770065 100644 --- a/glow/src/window/backend.rs +++ b/glow/src/window/compositor.rs @@ -6,20 +6,20 @@ use raw_window_handle::HasRawWindowHandle; /// A window graphics backend for iced powered by `glow`. #[allow(missing_debug_implementations)] -pub struct Backend { +pub struct Compositor { connection: surfman::Connection, device: surfman::Device, gl_context: surfman::Context, gl: Option, } -impl iced_native::window::Backend for Backend { +impl iced_native::window::Compositor for Compositor { type Settings = Settings; type Renderer = Renderer; type Surface = (); type SwapChain = Viewport; - fn new(settings: Self::Settings) -> Backend { + fn new(_settings: Self::Settings) -> Self { let connection = surfman::Connection::new().expect("Create connection"); let adapter = connection @@ -40,7 +40,7 @@ impl iced_native::window::Backend for Backend { .create_context(&context_descriptor) .expect("Create context"); - Backend { + Self { connection, device, gl_context, @@ -179,7 +179,7 @@ impl iced_native::window::Backend for Backend { } } -impl Drop for Backend { +impl Drop for Compositor { fn drop(&mut self) { self.device .destroy_context(&mut self.gl_context) diff --git a/native/src/window.rs b/native/src/window.rs index 4dcae62f..84269fbf 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -1,6 +1,6 @@ //! Build window-based GUI applications. -mod backend; +mod compositor; mod event; -pub use backend::Backend; +pub use compositor::Compositor; pub use event::Event; diff --git a/native/src/window/backend.rs b/native/src/window/compositor.rs similarity index 95% rename from native/src/window/backend.rs rename to native/src/window/compositor.rs index d8726fd4..ae010c89 100644 --- a/native/src/window/backend.rs +++ b/native/src/window/compositor.rs @@ -2,8 +2,8 @@ use crate::mouse; use raw_window_handle::HasRawWindowHandle; -/// A graphics backend that can render to windows. -pub trait Backend: Sized { +/// A graphics compositor that can draw to windows. +pub trait Compositor: Sized { /// The settings of the backend. type Settings: Default + Clone; diff --git a/src/application.rs b/src/application.rs index 689332f1..0ae2ec55 100644 --- a/src/application.rs +++ b/src/application.rs @@ -216,7 +216,7 @@ impl iced_winit::Application for Instance where A: Application, { - type Backend = iced_wgpu::window::Backend; + type Compositor = iced_wgpu::window::Compositor; type Executor = A::Executor; type Flags = A::Flags; type Message = A::Message; diff --git a/wgpu/src/window.rs b/wgpu/src/window.rs index b7adad82..391d3e36 100644 --- a/wgpu/src/window.rs +++ b/wgpu/src/window.rs @@ -1,6 +1,6 @@ //! Display rendering results on windows. -mod backend; +mod compositor; mod swap_chain; -pub use backend::Backend; +pub use compositor::Compositor; pub use swap_chain::SwapChain; diff --git a/wgpu/src/window/backend.rs b/wgpu/src/window/compositor.rs similarity index 96% rename from wgpu/src/window/backend.rs rename to wgpu/src/window/compositor.rs index 92e81cd9..8950ffd4 100644 --- a/wgpu/src/window/backend.rs +++ b/wgpu/src/window/compositor.rs @@ -5,19 +5,19 @@ use raw_window_handle::HasRawWindowHandle; /// A window graphics backend for iced powered by `wgpu`. #[derive(Debug)] -pub struct Backend { +pub struct Compositor { device: wgpu::Device, queue: wgpu::Queue, format: wgpu::TextureFormat, } -impl iced_native::window::Backend for Backend { +impl iced_native::window::Compositor for Compositor { type Settings = Settings; type Renderer = Renderer; type Surface = wgpu::Surface; type SwapChain = SwapChain; - fn new(settings: Self::Settings) -> Backend { + fn new(settings: Self::Settings) -> Self { let (device, queue) = futures::executor::block_on(async { let adapter = wgpu::Adapter::request( &wgpu::RequestAdapterOptions { @@ -43,7 +43,7 @@ impl iced_native::window::Backend for Backend { .await }); - Backend { + Self { device, queue, format: settings.format, diff --git a/winit/src/application.rs b/winit/src/application.rs index 4bc36586..83b53de2 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -19,7 +19,7 @@ pub trait Application: Sized { /// The graphics backend to use to draw the [`Application`]. /// /// [`Application`]: trait.Application.html - type Backend: window::Backend; + type Compositor: window::Compositor; /// The [`Executor`] that will run commands and subscriptions. /// @@ -91,7 +91,11 @@ pub trait Application: Sized { /// [`Application`]: trait.Application.html fn view( &mut self, - ) -> Element<'_, Self::Message, ::Renderer>; + ) -> Element< + '_, + Self::Message, + ::Renderer, + >; /// Returns the current [`Application`] mode. /// @@ -116,11 +120,11 @@ pub trait Application: Sized { /// [`Settings`]: struct.Settings.html fn run( settings: Settings, - backend_settings: ::Settings, + backend_settings: ::Settings, ) where Self: 'static, { - use window::Backend as _; + use window::Compositor as _; use winit::{ event::{self, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -181,15 +185,15 @@ pub trait Application: Sized { let mut resized = false; let clipboard = Clipboard::new(&window); - let mut backend = Self::Backend::new(backend_settings.clone()); + let mut compositor = Self::Compositor::new(backend_settings.clone()); - let surface = backend.create_surface(&window); - let mut renderer = backend.create_renderer(backend_settings); + let surface = compositor.create_surface(&window); + let mut renderer = compositor.create_renderer(backend_settings); let mut swap_chain = { let physical_size = size.physical(); - backend.create_swap_chain( + compositor.create_swap_chain( &surface, physical_size.width, physical_size.height, @@ -324,7 +328,7 @@ pub trait Application: Sized { if resized { let physical_size = size.physical(); - swap_chain = backend.create_swap_chain( + swap_chain = compositor.create_swap_chain( &surface, physical_size.width, physical_size.height, @@ -333,7 +337,7 @@ pub trait Application: Sized { resized = false; } - let new_mouse_interaction = backend.draw( + let new_mouse_interaction = compositor.draw( &mut renderer, &mut swap_chain, &primitive, @@ -414,10 +418,14 @@ pub trait Application: Sized { fn build_user_interface<'a, A: Application>( application: &'a mut A, cache: Cache, - renderer: &mut ::Renderer, + renderer: &mut ::Renderer, size: winit::dpi::LogicalSize, debug: &mut Debug, -) -> UserInterface<'a, A::Message, ::Renderer> { +) -> UserInterface< + 'a, + A::Message, + ::Renderer, +> { debug.view_started(); let view = application.view(); debug.view_finished(); From e0c4f1a08e756f11c30a99cd739fe78267e5040b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 19 May 2020 20:20:51 +0200 Subject: [PATCH 06/40] Move `font::Source` to `iced_graphics` --- glow/Cargo.toml | 2 +- glow/src/text.rs | 4 +- graphics/Cargo.toml | 5 +++ graphics/src/font.rs | 10 +++++ .../font.rs => graphics/src/font/source.rs | 4 +- graphics/src/lib.rs | 1 + wgpu/Cargo.toml | 2 +- wgpu/src/text.rs | 4 +- wgpu/src/text/font.rs | 37 ------------------- 9 files changed, 21 insertions(+), 48 deletions(-) create mode 100644 graphics/src/font.rs rename glow/src/text/font.rs => graphics/src/font/source.rs (90%) delete mode 100644 wgpu/src/text/font.rs diff --git a/glow/Cargo.toml b/glow/Cargo.toml index 212fbb30..158e2bf0 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -13,7 +13,6 @@ euclid = "0.20" glow = "0.4" bytemuck = "1.2" glam = "0.8" -font-kit = "0.6" log = "0.4" glyph_brush = "0.6" @@ -24,6 +23,7 @@ path = "../native" [dependencies.iced_graphics] version = "0.1" path = "../graphics" +features = ["font-source"] [dependencies.surfman] path = "../../surfman/surfman" diff --git a/glow/src/text.rs b/glow/src/text.rs index 159c80a6..be88ceaf 100644 --- a/glow/src/text.rs +++ b/glow/src/text.rs @@ -1,7 +1,5 @@ -mod font; - use crate::Transformation; - +use iced_graphics::font; use std::{cell::RefCell, collections::HashMap}; pub const BUILTIN_ICONS: iced_native::Font = iced_native::Font::External { diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index a81807d6..c937763c 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [features] canvas = ["lyon"] +font-source = ["font-kit"] [dependencies] bytemuck = "1.2" @@ -22,3 +23,7 @@ path = "../style" [dependencies.lyon] version = "0.15" optional = true + +[dependencies.font-kit] +version = "0.6" +optional = true diff --git a/graphics/src/font.rs b/graphics/src/font.rs new file mode 100644 index 00000000..3890beba --- /dev/null +++ b/graphics/src/font.rs @@ -0,0 +1,10 @@ +#[cfg(feature = "font-source")] +mod source; + +#[cfg(feature = "font-source")] +pub use source::Source; + +#[cfg(feature = "font-source")] +pub use font_kit::{ + error::SelectionError as LoadError, family_name::FamilyName as Family, +}; diff --git a/glow/src/text/font.rs b/graphics/src/font/source.rs similarity index 90% rename from glow/src/text/font.rs rename to graphics/src/font/source.rs index 7346ccdb..6855aa93 100644 --- a/glow/src/text/font.rs +++ b/graphics/src/font/source.rs @@ -1,6 +1,4 @@ -pub use font_kit::{ - error::SelectionError as LoadError, family_name::FamilyName as Family, -}; +use crate::font::{Family, LoadError}; pub struct Source { raw: font_kit::source::SystemSource, diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index bdaefb41..152dc7b0 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -6,6 +6,7 @@ mod viewport; mod widget; pub mod backend; +pub mod font; pub mod triangle; #[doc(no_inline)] diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 88290576..b59c7fa3 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -19,7 +19,6 @@ bytemuck = "1.2" glyph_brush = "0.6" raw-window-handle = "0.3" glam = "0.8" -font-kit = "0.6" log = "0.4" guillotiere = "0.5" # Pin `gfx-memory` until https://github.com/gfx-rs/wgpu-rs/issues/261 is @@ -33,6 +32,7 @@ path = "../native" [dependencies.iced_graphics] version = "0.1" path = "../graphics" +features = ["font-source"] [dependencies.image] version = "0.23" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 8bcd6d83..ae9b6b22 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,7 +1,5 @@ -mod font; - use crate::Transformation; - +use iced_graphics::font; use std::{cell::RefCell, collections::HashMap}; pub const BUILTIN_ICONS: iced_native::Font = iced_native::Font::External { diff --git a/wgpu/src/text/font.rs b/wgpu/src/text/font.rs deleted file mode 100644 index 7346ccdb..00000000 --- a/wgpu/src/text/font.rs +++ /dev/null @@ -1,37 +0,0 @@ -pub use font_kit::{ - error::SelectionError as LoadError, family_name::FamilyName as Family, -}; - -pub struct Source { - raw: font_kit::source::SystemSource, -} - -impl Source { - pub fn new() -> Self { - Source { - raw: font_kit::source::SystemSource::new(), - } - } - - pub fn load(&self, families: &[Family]) -> Result, LoadError> { - let font = self.raw.select_best_match( - families, - &font_kit::properties::Properties::default(), - )?; - - match font { - font_kit::handle::Handle::Path { path, .. } => { - use std::io::Read; - - let mut buf = Vec::new(); - let mut reader = std::fs::File::open(path).expect("Read font"); - let _ = reader.read_to_end(&mut buf); - - Ok(buf) - } - font_kit::handle::Handle::Memory { bytes, .. } => { - Ok(bytes.as_ref().clone()) - } - } - } -} From f0480854a9cd76f443848dbfa14256089b56abfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 19 May 2020 20:30:46 +0200 Subject: [PATCH 07/40] Move built-in fonts to `iced_graphics` --- glow/Cargo.toml | 2 +- glow/src/backend.rs | 5 +++-- glow/src/text.rs | 14 ++------------ graphics/Cargo.toml | 2 ++ .../text/icons.ttf => graphics/fonts/Icons.ttf | Bin {wgpu => graphics}/fonts/Lato-Regular.ttf | Bin {wgpu => graphics}/fonts/OFL.txt | 0 graphics/src/font.rs | 12 ++++++++++++ wgpu/Cargo.toml | 2 +- wgpu/src/backend.rs | 5 +++-- wgpu/src/text.rs | 13 ++----------- wgpu/src/text/icons.ttf | Bin 4912 -> 0 bytes 12 files changed, 26 insertions(+), 29 deletions(-) rename glow/src/text/icons.ttf => graphics/fonts/Icons.ttf (100%) rename {wgpu => graphics}/fonts/Lato-Regular.ttf (100%) rename {wgpu => graphics}/fonts/OFL.txt (100%) delete mode 100644 wgpu/src/text/icons.ttf diff --git a/glow/Cargo.toml b/glow/Cargo.toml index 158e2bf0..72ed8758 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -23,7 +23,7 @@ path = "../native" [dependencies.iced_graphics] version = "0.1" path = "../graphics" -features = ["font-source"] +features = ["font-source", "font-fallback", "font-icons"] [dependencies.surfman] path = "../../surfman/surfman" diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 7293eba1..94683e56 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -3,6 +3,7 @@ use crate::text; use crate::triangle; use crate::{Quad, Settings, Transformation, Viewport}; use iced_graphics::backend; +use iced_graphics::font; use iced_graphics::Primitive; use iced_native::mouse; use iced_native::{Background, Font, Point, Rectangle, Size, Vector}; @@ -404,8 +405,8 @@ impl iced_graphics::Backend for Backend { } impl backend::Text for Backend { - const ICON_FONT: Font = text::BUILTIN_ICONS; - const CHECKMARK_ICON: char = text::CHECKMARK_ICON; + const ICON_FONT: Font = font::ICONS; + const CHECKMARK_ICON: char = font::CHECKMARK_ICON; fn measure( &self, diff --git a/glow/src/text.rs b/glow/src/text.rs index be88ceaf..952fd2cd 100644 --- a/glow/src/text.rs +++ b/glow/src/text.rs @@ -2,16 +2,6 @@ use crate::Transformation; use iced_graphics::font; use std::{cell::RefCell, collections::HashMap}; -pub const BUILTIN_ICONS: iced_native::Font = iced_native::Font::External { - name: "iced_glow icons", - bytes: include_bytes!("text/icons.ttf"), -}; - -pub const CHECKMARK_ICON: char = '\u{F00C}'; - -const FALLBACK_FONT: &[u8] = - include_bytes!("../../wgpu/fonts/Lato-Regular.ttf"); - #[derive(Debug)] pub struct Pipeline { draw_brush: RefCell>, @@ -29,7 +19,7 @@ impl Pipeline { default_font.map(|slice| slice.to_vec()).unwrap_or_else(|| { font_source .load(&[font::Family::SansSerif, font::Family::Serif]) - .unwrap_or_else(|_| FALLBACK_FONT.to_vec()) + .unwrap_or_else(|_| font::FALLBACK.to_vec()) }); let load_glyph_brush = |font: Vec| { @@ -48,7 +38,7 @@ impl Pipeline { .unwrap_or_else(|_: glow_glyph::rusttype::Error| { log::warn!("System font failed to load. Falling back to embedded font..."); - load_glyph_brush(FALLBACK_FONT.to_vec()).expect("Load fallback font") + load_glyph_brush(font::FALLBACK.to_vec()).expect("Load fallback font") }); let draw_brush = diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index c937763c..61f1f6d4 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -7,6 +7,8 @@ edition = "2018" [features] canvas = ["lyon"] font-source = ["font-kit"] +font-fallback = [] +font-icons = [] [dependencies] bytemuck = "1.2" diff --git a/glow/src/text/icons.ttf b/graphics/fonts/Icons.ttf similarity index 100% rename from glow/src/text/icons.ttf rename to graphics/fonts/Icons.ttf diff --git a/wgpu/fonts/Lato-Regular.ttf b/graphics/fonts/Lato-Regular.ttf similarity index 100% rename from wgpu/fonts/Lato-Regular.ttf rename to graphics/fonts/Lato-Regular.ttf diff --git a/wgpu/fonts/OFL.txt b/graphics/fonts/OFL.txt similarity index 100% rename from wgpu/fonts/OFL.txt rename to graphics/fonts/OFL.txt diff --git a/graphics/src/font.rs b/graphics/src/font.rs index 3890beba..a490e609 100644 --- a/graphics/src/font.rs +++ b/graphics/src/font.rs @@ -8,3 +8,15 @@ pub use source::Source; pub use font_kit::{ error::SelectionError as LoadError, family_name::FamilyName as Family, }; + +#[cfg(feature = "font-fallback")] +pub const FALLBACK: &[u8] = include_bytes!("../fonts/Lato-Regular.ttf"); + +#[cfg(feature = "font-icons")] +pub const ICONS: iced_native::Font = iced_native::Font::External { + name: "iced_wgpu icons", + bytes: include_bytes!("../fonts/Icons.ttf"), +}; + +#[cfg(feature = "font-icons")] +pub const CHECKMARK_ICON: char = '\u{F00C}'; diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index b59c7fa3..35ea9c31 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -32,7 +32,7 @@ path = "../native" [dependencies.iced_graphics] version = "0.1" path = "../graphics" -features = ["font-source"] +features = ["font-source", "font-fallback", "font-icons"] [dependencies.image] version = "0.23" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index ba1a57a5..073a79e2 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -3,6 +3,7 @@ use crate::text; use crate::triangle; use crate::{Quad, Settings, Target, Transformation}; use iced_graphics::backend; +use iced_graphics::font; use iced_graphics::Primitive; use iced_native::mouse; use iced_native::{Background, Font, Point, Rectangle, Size, Vector}; @@ -456,8 +457,8 @@ impl iced_graphics::Backend for Backend { } impl backend::Text for Backend { - const ICON_FONT: Font = text::BUILTIN_ICONS; - const CHECKMARK_ICON: char = text::CHECKMARK_ICON; + const ICON_FONT: Font = font::ICONS; + const CHECKMARK_ICON: char = font::CHECKMARK_ICON; fn measure( &self, diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index ae9b6b22..c1782fe5 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -2,15 +2,6 @@ use crate::Transformation; use iced_graphics::font; use std::{cell::RefCell, collections::HashMap}; -pub const BUILTIN_ICONS: iced_native::Font = iced_native::Font::External { - name: "iced_wgpu icons", - bytes: include_bytes!("text/icons.ttf"), -}; - -pub const CHECKMARK_ICON: char = '\u{F00C}'; - -const FALLBACK_FONT: &[u8] = include_bytes!("../fonts/Lato-Regular.ttf"); - #[derive(Debug)] pub struct Pipeline { draw_brush: RefCell>, @@ -32,7 +23,7 @@ impl Pipeline { default_font.map(|slice| slice.to_vec()).unwrap_or_else(|| { font_source .load(&[font::Family::SansSerif, font::Family::Serif]) - .unwrap_or_else(|_| FALLBACK_FONT.to_vec()) + .unwrap_or_else(|_| font::FALLBACK.to_vec()) }); let load_glyph_brush = |font: Vec| { @@ -51,7 +42,7 @@ impl Pipeline { .unwrap_or_else(|_: wgpu_glyph::rusttype::Error| { log::warn!("System font failed to load. Falling back to embedded font..."); - load_glyph_brush(FALLBACK_FONT.to_vec()).expect("Load fallback font") + load_glyph_brush(font::FALLBACK.to_vec()).expect("Load fallback font") }); let draw_brush = brush_builder diff --git a/wgpu/src/text/icons.ttf b/wgpu/src/text/icons.ttf deleted file mode 100644 index 1c832f86576e51451b729a7fe513616dea38d21a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4912 zcmd^CU2I!P6+UyX?W9Q>nA@MRd1*udB^${dM2*d*~2nmo@z{9eFH`wx>@wMBu zDJ$(OH}#zFob%1hnKNhR-jX1qFug-2)pHjvrsbDM-iGDh_$|DC;qb()J@*A7k%xY% zwC2|TarC#pg8qH{>ZNN<8T#G*08!6V@XKm_X|45*cOB@HME&(8ccTt_1Ui1yyR>?v z`kN1a+Dl|IzrI{?%hA6@{sjFd^yAAg^h}u-q2GgkV0o>1z310JWd0dV?`o~&5=Msp zcQm0j_j;Xv(EnZN|Aa2r-L=XuzOye*6c{Elep#<=H0z%ih%dzb?Ruk9|I3SspAhXr z{PZQ_wfE6Dogrgp_WS}3uDZ>2+Dqu6AND_t#p=qEONd|h6!5WEz#=Hrv6}CRcPWVZ z-!?E4eWfkMAL)oNV5Oi5>o!lmnl01x`vdO}nDh6q(q(g=z6Bb`(0(u`b_wgi5A%uz z-}f4<%(5Z-v^_xkH7CIK_XqCZ@3hd{5tHC}|KFbhoq(tPH0WG$rl1R*Q8I5C-=^L4 z%=F$cEFwpSFly|qfm0pcGi(O;B*bu!2%A|J|#9r^ID;qZCL z?}>G^=Oacm3P(6BqFRmI617O=d|1zi2)wVK`p=c{)W4|Te2?S${Rh?C4@>hsM1-?| zLTW$gqw9s*e6+A^c?|Zny|DB7VTM6bpJ||WqqG5As-Mp*KfCA;-EodVZ;#29eyrQ8 zhfe$0gnh=x0pRmK4pKjLsx$=q72n=R30m@TKMm10xq<^bFwa%G_qGj@przDvgbm+PXZF8t<5LmGw%) zZC1)W`o^`TiDt7ZtBu;4ob|_*^+xS#rPNFtZWA=G4 zme79{qck4#K03-Jup20z6+OB{Tq#8NC@Tlnx@NjMf->T1$G$<=Fxv#IP4wa@8#*dU zvwFk_@k-=_eOEC~3G9^4N)y(TbQph+&e>TXs;YBVa64}M;8Y8fjM|&<;&9`>3_r$u ziu50!HU25GrJ{?4tRv;QJ2W_VPK7R9E~uk1bC%#}zq> zGSil@A+DyKm{gOD zCLKq5ZIWA72VwFxsm9qq&dhuHf7v7w;*1Wm*WT>ur%X0I7Vjo|hrKoVeeF?10wH9u z;-bv9ESEPzXNqE+O(kOpvJ<5;M=W=yJw^W$x~l`|h0fijJ=|%4YsN}%_V=0DLOf>0 zow0aQ?X*3^$f~kClT?E?79gb>$h^Yq0j>tH20#Gfl-6Sr*D z6E@Hz8nTe$jG{A}LI_zZ&xUx z8j09`q-hvy8%iYAZrc+qqsTMMJ+^1Ee9HC$EI(;`L6&=MFT^rtdp#_lw!L1K&)D7$ zme1Oxb|yYXAo~!=vlu`k5SDQS!g4^auk8Ed=7!IJcvM89 zuJbV)>yH#=hI7H2=0d7NTvQ2LjV06=j*E>-&7q{WJuj9!Y4Kh4KN+Z(q)K-8Ktz>2 zro`@Xo#oWSS=s73Y|CRh&T()=_VMG2TVUI;*iO+O>N|Mmj5X;U7Q3;MBUm>A-L^{+ zRqIYB)r6heGnG`&e-srJRs#Jfwt}J~G9`0-GLgcUTdkax!%a}YosByW$Mtz3b`N9z zW4J=1sDAv@6tyFhsI*d+lv6E?b^L)_rrKku0LBDMsv;le>AAvfLz*&n+ZZ+XI%&S( zdT~`^KFny%;%H$Px1JLmYLRH)=J3JLf2BavW47oA6R5NPol;VbOHT&R*4I0q852S@!J zSW`&Nq&BNwoL(tstsGCryLCdRj2GZ%lokppIfWM=N9-Fh*n^aZMxeb6SNGN0-l)fm zt7!QR6)V*)r%^TOCidP(xfSEgZ+kCdRg;R08u)>vVAecCyRGW#Lq$a7fxQzKF{L z7j(B$JL~=EQp{~v7$2wlIB$HfOj(n$_(S_0cl;RBoQzIVGraydXP!g!*U??_43@mR zeK_$d$2lBIsUtXx*(ca%ao39BAvFO`&Q?buUgE4}kykm3cUC7)FWX#>>LsN06?>ED zMSybvg28!vQ)tVp0NU~zbJB3V&Kw4L<}jFJ4ug5-oB_DN90m)_VQ`T-3@$Ne8sJmR zVNhTW1BW>bE;AQ!5v=u+QgMN?NbV(OYkOkKCt$u70Vil){z zV(RBKVrs*_y(3_BzKqfd)mu^Kf&6uT)g%cY**}}d2Z;C@F6H)Tb={+28mp%<2DkeR z(c4CYq){k+*SH7w4p@8AcWJtfO`7ozh?{fxYP?zS%<_yk%G%xDckrLk>6;~dI)Q?Z XW9RmOAqK|oi2Gkufv@64F+=oU3sq^e From c2e0c52ce031ffe1c300b3cfa362b0e445ac5afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 19 May 2020 20:34:17 +0200 Subject: [PATCH 08/40] Move `Antialiasing` to `iced_graphics` --- glow/src/settings.rs | 25 +------------------------ graphics/src/antialiasing.rs | 23 +++++++++++++++++++++++ graphics/src/lib.rs | 2 ++ wgpu/src/settings.rs | 25 +------------------------ 4 files changed, 27 insertions(+), 48 deletions(-) create mode 100644 graphics/src/antialiasing.rs diff --git a/glow/src/settings.rs b/glow/src/settings.rs index bffc867e..07b36938 100644 --- a/glow/src/settings.rs +++ b/glow/src/settings.rs @@ -1,6 +1,7 @@ //! Configure a [`Renderer`]. //! //! [`Renderer`]: struct.Renderer.html +pub use iced_graphics::Antialiasing; /// The settings of a [`Renderer`]. /// @@ -24,27 +25,3 @@ impl Default for Settings { } } } - -/// An antialiasing strategy. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Antialiasing { - /// Multisample AA with 2 samples - MSAAx2, - /// Multisample AA with 4 samples - MSAAx4, - /// Multisample AA with 8 samples - MSAAx8, - /// Multisample AA with 16 samples - MSAAx16, -} - -impl Antialiasing { - pub(crate) fn sample_count(self) -> u32 { - match self { - Antialiasing::MSAAx2 => 2, - Antialiasing::MSAAx4 => 4, - Antialiasing::MSAAx8 => 8, - Antialiasing::MSAAx16 => 16, - } - } -} diff --git a/graphics/src/antialiasing.rs b/graphics/src/antialiasing.rs new file mode 100644 index 00000000..f92b3692 --- /dev/null +++ b/graphics/src/antialiasing.rs @@ -0,0 +1,23 @@ +/// An antialiasing strategy. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Antialiasing { + /// Multisample AA with 2 samples + MSAAx2, + /// Multisample AA with 4 samples + MSAAx4, + /// Multisample AA with 8 samples + MSAAx8, + /// Multisample AA with 16 samples + MSAAx16, +} + +impl Antialiasing { + pub fn sample_count(self) -> u32 { + match self { + Antialiasing::MSAAx2 => 2, + Antialiasing::MSAAx4 => 4, + Antialiasing::MSAAx8 => 8, + Antialiasing::MSAAx16 => 16, + } + } +} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 152dc7b0..5dc4040f 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -1,3 +1,4 @@ +mod antialiasing; mod defaults; mod primitive; mod renderer; @@ -12,6 +13,7 @@ pub mod triangle; #[doc(no_inline)] pub use widget::*; +pub use antialiasing::Antialiasing; pub use backend::Backend; pub use defaults::Defaults; pub use primitive::Primitive; diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 5ee245b6..05d301e7 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,6 +1,7 @@ //! Configure a [`Renderer`]. //! //! [`Renderer`]: struct.Renderer.html +pub use iced_graphics::Antialiasing; /// The settings of a [`Renderer`]. /// @@ -30,27 +31,3 @@ impl Default for Settings { } } } - -/// An antialiasing strategy. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Antialiasing { - /// Multisample AA with 2 samples - MSAAx2, - /// Multisample AA with 4 samples - MSAAx4, - /// Multisample AA with 8 samples - MSAAx8, - /// Multisample AA with 16 samples - MSAAx16, -} - -impl Antialiasing { - pub(crate) fn sample_count(self) -> u32 { - match self { - Antialiasing::MSAAx2 => 2, - Antialiasing::MSAAx4 => 4, - Antialiasing::MSAAx8 => 8, - Antialiasing::MSAAx16 => 16, - } - } -} From e6180912488db4d59fbffcb46c5930282306cb92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 19 May 2020 21:00:40 +0200 Subject: [PATCH 09/40] Merge unnecessary split widget modules --- graphics/src/renderer.rs | 2 - graphics/src/renderer/widget.rs | 15 -- graphics/src/renderer/widget/button.rs | 98 ------- graphics/src/renderer/widget/checkbox.rs | 68 ----- graphics/src/renderer/widget/container.rs | 53 ---- graphics/src/renderer/widget/pane_grid.rs | 94 ------- graphics/src/renderer/widget/progress_bar.rs | 58 ---- graphics/src/renderer/widget/radio.rs | 66 ----- graphics/src/renderer/widget/scrollable.rs | 130 --------- graphics/src/renderer/widget/slider.rs | 109 -------- graphics/src/renderer/widget/text.rs | 65 ----- graphics/src/renderer/widget/text_input.rs | 267 ------------------- graphics/src/widget.rs | 10 + graphics/src/widget/button.rs | 99 ++++++- graphics/src/widget/checkbox.rs | 68 ++++- graphics/src/{renderer => }/widget/column.rs | 3 + graphics/src/widget/container.rs | 54 +++- graphics/src/{renderer => }/widget/image.rs | 2 + graphics/src/widget/pane_grid.rs | 95 ++++++- graphics/src/widget/progress_bar.rs | 60 ++++- graphics/src/widget/radio.rs | 69 ++++- graphics/src/{renderer => }/widget/row.rs | 7 +- graphics/src/widget/scrollable.rs | 131 ++++++++- graphics/src/widget/slider.rs | 109 +++++++- graphics/src/{renderer => }/widget/space.rs | 2 + graphics/src/{renderer => }/widget/svg.rs | 2 + graphics/src/widget/text.rs | 67 ++++- graphics/src/widget/text_input.rs | 266 +++++++++++++++++- 28 files changed, 1032 insertions(+), 1037 deletions(-) delete mode 100644 graphics/src/renderer/widget.rs delete mode 100644 graphics/src/renderer/widget/button.rs delete mode 100644 graphics/src/renderer/widget/checkbox.rs delete mode 100644 graphics/src/renderer/widget/container.rs delete mode 100644 graphics/src/renderer/widget/pane_grid.rs delete mode 100644 graphics/src/renderer/widget/progress_bar.rs delete mode 100644 graphics/src/renderer/widget/radio.rs delete mode 100644 graphics/src/renderer/widget/scrollable.rs delete mode 100644 graphics/src/renderer/widget/slider.rs delete mode 100644 graphics/src/renderer/widget/text.rs delete mode 100644 graphics/src/renderer/widget/text_input.rs rename graphics/src/{renderer => }/widget/column.rs (92%) rename graphics/src/{renderer => }/widget/image.rs (95%) rename graphics/src/{renderer => }/widget/row.rs (85%) rename graphics/src/{renderer => }/widget/space.rs (91%) rename graphics/src/{renderer => }/widget/svg.rs (95%) diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 836ed58d..f16e04b1 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -1,5 +1,3 @@ -mod widget; - use crate::{Backend, Defaults, Primitive}; use iced_native::layout::{self, Layout}; use iced_native::mouse; diff --git a/graphics/src/renderer/widget.rs b/graphics/src/renderer/widget.rs deleted file mode 100644 index b652fdcf..00000000 --- a/graphics/src/renderer/widget.rs +++ /dev/null @@ -1,15 +0,0 @@ -mod button; -mod checkbox; -mod column; -mod container; -mod image; -mod pane_grid; -mod progress_bar; -mod radio; -mod row; -mod scrollable; -mod slider; -mod space; -mod svg; -mod text; -mod text_input; diff --git a/graphics/src/renderer/widget/button.rs b/graphics/src/renderer/widget/button.rs deleted file mode 100644 index 6ee60df4..00000000 --- a/graphics/src/renderer/widget/button.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::{ - button::StyleSheet, defaults, Backend, Defaults, Primitive, Renderer, -}; -use iced_native::{ - mouse, Background, Color, Element, Layout, Point, Rectangle, Vector, -}; - -impl iced_native::button::Renderer for Renderer -where - B: Backend, -{ - const DEFAULT_PADDING: u16 = 5; - - type Style = Box; - - fn draw( - &mut self, - _defaults: &Defaults, - bounds: Rectangle, - cursor_position: Point, - is_disabled: bool, - is_pressed: bool, - style: &Box, - content: &Element<'_, Message, Self>, - content_layout: Layout<'_>, - ) -> Self::Output { - let is_mouse_over = bounds.contains(cursor_position); - - let styling = if is_disabled { - style.disabled() - } else if is_mouse_over { - if is_pressed { - style.pressed() - } else { - style.hovered() - } - } else { - style.active() - }; - - let (content, _) = content.draw( - self, - &Defaults { - text: defaults::Text { - color: styling.text_color, - }, - }, - content_layout, - cursor_position, - ); - - ( - if styling.background.is_some() || styling.border_width > 0 { - let background = Primitive::Quad { - bounds, - background: styling - .background - .unwrap_or(Background::Color(Color::TRANSPARENT)), - border_radius: styling.border_radius, - border_width: styling.border_width, - border_color: styling.border_color, - }; - - if styling.shadow_offset == Vector::default() { - Primitive::Group { - primitives: vec![background, content], - } - } else { - // TODO: Implement proper shadow support - let shadow = Primitive::Quad { - bounds: Rectangle { - x: bounds.x + styling.shadow_offset.x, - y: bounds.y + styling.shadow_offset.y, - ..bounds - }, - background: Background::Color( - [0.0, 0.0, 0.0, 0.5].into(), - ), - border_radius: styling.border_radius, - border_width: 0, - border_color: Color::TRANSPARENT, - }; - - Primitive::Group { - primitives: vec![shadow, background, content], - } - } - } else { - content - }, - if is_mouse_over { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - }, - ) - } -} diff --git a/graphics/src/renderer/widget/checkbox.rs b/graphics/src/renderer/widget/checkbox.rs deleted file mode 100644 index 75168629..00000000 --- a/graphics/src/renderer/widget/checkbox.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::backend::{self, Backend}; -use crate::checkbox::StyleSheet; -use crate::{Primitive, Renderer}; -use iced_native::{ - checkbox, mouse, HorizontalAlignment, Rectangle, VerticalAlignment, -}; - -impl checkbox::Renderer for Renderer -where - B: Backend + backend::Text, -{ - type Style = Box; - - const DEFAULT_SIZE: u16 = 20; - const DEFAULT_SPACING: u16 = 15; - - fn draw( - &mut self, - bounds: Rectangle, - is_checked: bool, - is_mouse_over: bool, - (label, _): Self::Output, - style_sheet: &Self::Style, - ) -> Self::Output { - let style = if is_mouse_over { - style_sheet.hovered(is_checked) - } else { - style_sheet.active(is_checked) - }; - - let checkbox = Primitive::Quad { - bounds, - background: style.background, - border_radius: style.border_radius, - border_width: style.border_width, - border_color: style.border_color, - }; - - ( - Primitive::Group { - primitives: if is_checked { - let check = Primitive::Text { - content: B::CHECKMARK_ICON.to_string(), - font: B::ICON_FONT, - size: bounds.height * 0.7, - bounds: Rectangle { - x: bounds.center_x(), - y: bounds.center_y(), - ..bounds - }, - color: style.checkmark_color, - horizontal_alignment: HorizontalAlignment::Center, - vertical_alignment: VerticalAlignment::Center, - }; - - vec![checkbox, check, label] - } else { - vec![checkbox, label] - }, - }, - if is_mouse_over { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - }, - ) - } -} diff --git a/graphics/src/renderer/widget/container.rs b/graphics/src/renderer/widget/container.rs deleted file mode 100644 index a1f6a211..00000000 --- a/graphics/src/renderer/widget/container.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::container; -use crate::defaults::{self, Defaults}; -use crate::{Backend, Primitive, Renderer}; -use iced_native::{Background, Color, Element, Layout, Point, Rectangle}; - -impl iced_native::container::Renderer for Renderer -where - B: Backend, -{ - type Style = Box; - - fn draw( - &mut self, - defaults: &Defaults, - bounds: Rectangle, - cursor_position: Point, - style_sheet: &Self::Style, - content: &Element<'_, Message, Self>, - content_layout: Layout<'_>, - ) -> Self::Output { - let style = style_sheet.style(); - - let defaults = Defaults { - text: defaults::Text { - color: style.text_color.unwrap_or(defaults.text.color), - }, - }; - - let (content, mouse_interaction) = - content.draw(self, &defaults, content_layout, cursor_position); - - if style.background.is_some() || style.border_width > 0 { - let quad = Primitive::Quad { - bounds, - background: style - .background - .unwrap_or(Background::Color(Color::TRANSPARENT)), - border_radius: style.border_radius, - border_width: style.border_width, - border_color: style.border_color, - }; - - ( - Primitive::Group { - primitives: vec![quad, content], - }, - mouse_interaction, - ) - } else { - (content, mouse_interaction) - } - } -} diff --git a/graphics/src/renderer/widget/pane_grid.rs b/graphics/src/renderer/widget/pane_grid.rs deleted file mode 100644 index 3fa171f5..00000000 --- a/graphics/src/renderer/widget/pane_grid.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::{Backend, Primitive, Renderer}; -use iced_native::mouse; -use iced_native::pane_grid::{self, Axis, Pane}; -use iced_native::{Element, Layout, Point, Rectangle, Vector}; - -impl pane_grid::Renderer for Renderer -where - B: Backend, -{ - fn draw( - &mut self, - defaults: &Self::Defaults, - content: &[(Pane, Element<'_, Message, Self>)], - dragging: Option, - resizing: Option, - layout: Layout<'_>, - cursor_position: Point, - ) -> Self::Output { - let pane_cursor_position = if dragging.is_some() { - // TODO: Remove once cursor availability is encoded in the type - // system - Point::new(-1.0, -1.0) - } else { - cursor_position - }; - - let mut mouse_interaction = mouse::Interaction::default(); - let mut dragged_pane = None; - - let mut panes: Vec<_> = content - .iter() - .zip(layout.children()) - .enumerate() - .map(|(i, ((id, pane), layout))| { - let (primitive, new_mouse_interaction) = - pane.draw(self, defaults, layout, pane_cursor_position); - - if new_mouse_interaction > mouse_interaction { - mouse_interaction = new_mouse_interaction; - } - - if Some(*id) == dragging { - dragged_pane = Some((i, layout)); - } - - primitive - }) - .collect(); - - let primitives = if let Some((index, layout)) = dragged_pane { - let pane = panes.remove(index); - let bounds = layout.bounds(); - - // TODO: Fix once proper layering is implemented. - // This is a pretty hacky way to achieve layering. - let clip = Primitive::Clip { - bounds: Rectangle { - x: cursor_position.x - bounds.width / 2.0, - y: cursor_position.y - bounds.height / 2.0, - width: bounds.width + 0.5, - height: bounds.height + 0.5, - }, - offset: Vector::new(0, 0), - content: Box::new(Primitive::Translate { - translation: Vector::new( - cursor_position.x - bounds.x - bounds.width / 2.0, - cursor_position.y - bounds.y - bounds.height / 2.0, - ), - content: Box::new(pane), - }), - }; - - panes.push(clip); - - panes - } else { - panes - }; - - ( - Primitive::Group { primitives }, - if dragging.is_some() { - mouse::Interaction::Grabbing - } else if let Some(axis) = resizing { - match axis { - Axis::Horizontal => mouse::Interaction::ResizingVertically, - Axis::Vertical => mouse::Interaction::ResizingHorizontally, - } - } else { - mouse_interaction - }, - ) - } -} diff --git a/graphics/src/renderer/widget/progress_bar.rs b/graphics/src/renderer/widget/progress_bar.rs deleted file mode 100644 index d8a145a4..00000000 --- a/graphics/src/renderer/widget/progress_bar.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::progress_bar::StyleSheet; -use crate::{Backend, Primitive, Renderer}; -use iced_native::{mouse, progress_bar, Color, Rectangle}; - -impl progress_bar::Renderer for Renderer -where - B: Backend, -{ - type Style = Box; - - const DEFAULT_HEIGHT: u16 = 30; - - fn draw( - &self, - bounds: Rectangle, - range: std::ops::RangeInclusive, - value: f32, - style_sheet: &Self::Style, - ) -> Self::Output { - let style = style_sheet.style(); - - let (range_start, range_end) = range.into_inner(); - let active_progress_width = bounds.width - * ((value - range_start) / (range_end - range_start).max(1.0)); - - let background = Primitive::Group { - primitives: vec![Primitive::Quad { - bounds: Rectangle { ..bounds }, - background: style.background, - border_radius: style.border_radius, - border_width: 0, - border_color: Color::TRANSPARENT, - }], - }; - - ( - if active_progress_width > 0.0 { - let bar = Primitive::Quad { - bounds: Rectangle { - width: active_progress_width, - ..bounds - }, - background: style.bar, - border_radius: style.border_radius, - border_width: 0, - border_color: Color::TRANSPARENT, - }; - - Primitive::Group { - primitives: vec![background, bar], - } - } else { - background - }, - mouse::Interaction::default(), - ) - } -} diff --git a/graphics/src/renderer/widget/radio.rs b/graphics/src/renderer/widget/radio.rs deleted file mode 100644 index 4da2b60a..00000000 --- a/graphics/src/renderer/widget/radio.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::{radio::StyleSheet, Backend, Primitive, Renderer}; -use iced_native::{mouse, radio, Background, Color, Rectangle}; - -const SIZE: f32 = 28.0; -const DOT_SIZE: f32 = SIZE / 2.0; - -impl radio::Renderer for Renderer -where - B: Backend, -{ - type Style = Box; - - const DEFAULT_SIZE: u16 = SIZE as u16; - const DEFAULT_SPACING: u16 = 15; - - fn draw( - &mut self, - bounds: Rectangle, - is_selected: bool, - is_mouse_over: bool, - (label, _): Self::Output, - style_sheet: &Self::Style, - ) -> Self::Output { - let style = if is_mouse_over { - style_sheet.hovered() - } else { - style_sheet.active() - }; - - let radio = Primitive::Quad { - bounds, - background: style.background, - border_radius: (SIZE / 2.0) as u16, - border_width: style.border_width, - border_color: style.border_color, - }; - - ( - Primitive::Group { - primitives: if is_selected { - let radio_circle = Primitive::Quad { - bounds: Rectangle { - x: bounds.x + DOT_SIZE / 2.0, - y: bounds.y + DOT_SIZE / 2.0, - width: bounds.width - DOT_SIZE, - height: bounds.height - DOT_SIZE, - }, - background: Background::Color(style.dot_color), - border_radius: (DOT_SIZE / 2.0) as u16, - border_width: 0, - border_color: Color::TRANSPARENT, - }; - - vec![radio, radio_circle, label] - } else { - vec![radio, label] - }, - }, - if is_mouse_over { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - }, - ) - } -} diff --git a/graphics/src/renderer/widget/scrollable.rs b/graphics/src/renderer/widget/scrollable.rs deleted file mode 100644 index 8db17bec..00000000 --- a/graphics/src/renderer/widget/scrollable.rs +++ /dev/null @@ -1,130 +0,0 @@ -use crate::{Backend, Primitive, Renderer}; -use iced_native::mouse; -use iced_native::scrollable; -use iced_native::{Background, Color, Rectangle, Vector}; - -const SCROLLBAR_WIDTH: u16 = 10; -const SCROLLBAR_MARGIN: u16 = 2; - -impl scrollable::Renderer for Renderer -where - B: Backend, -{ - type Style = Box; - - fn scrollbar( - &self, - bounds: Rectangle, - content_bounds: Rectangle, - offset: u32, - ) -> Option { - if content_bounds.height > bounds.height { - let scrollbar_bounds = Rectangle { - x: bounds.x + bounds.width - - f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), - y: bounds.y, - width: f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), - height: bounds.height, - }; - - let ratio = bounds.height / content_bounds.height; - let scrollbar_height = bounds.height * ratio; - let y_offset = offset as f32 * ratio; - - let scroller_bounds = Rectangle { - x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), - y: scrollbar_bounds.y + y_offset, - width: scrollbar_bounds.width - f32::from(2 * SCROLLBAR_MARGIN), - height: scrollbar_height, - }; - - Some(scrollable::Scrollbar { - bounds: scrollbar_bounds, - scroller: scrollable::Scroller { - bounds: scroller_bounds, - }, - }) - } else { - None - } - } - - fn draw( - &mut self, - state: &scrollable::State, - bounds: Rectangle, - _content_bounds: Rectangle, - is_mouse_over: bool, - is_mouse_over_scrollbar: bool, - scrollbar: Option, - offset: u32, - style_sheet: &Self::Style, - (content, mouse_interaction): Self::Output, - ) -> Self::Output { - ( - if let Some(scrollbar) = scrollbar { - let clip = Primitive::Clip { - bounds, - offset: Vector::new(0, offset), - content: Box::new(content), - }; - - let style = if state.is_scroller_grabbed() { - style_sheet.dragging() - } else if is_mouse_over_scrollbar { - style_sheet.hovered() - } else { - style_sheet.active() - }; - - let is_scrollbar_visible = - style.background.is_some() || style.border_width > 0; - - let scroller = if is_mouse_over - || state.is_scroller_grabbed() - || is_scrollbar_visible - { - Primitive::Quad { - bounds: scrollbar.scroller.bounds, - background: Background::Color(style.scroller.color), - border_radius: style.scroller.border_radius, - border_width: style.scroller.border_width, - border_color: style.scroller.border_color, - } - } else { - Primitive::None - }; - - let scrollbar = if is_scrollbar_visible { - Primitive::Quad { - bounds: Rectangle { - x: scrollbar.bounds.x + f32::from(SCROLLBAR_MARGIN), - width: scrollbar.bounds.width - - f32::from(2 * SCROLLBAR_MARGIN), - ..scrollbar.bounds - }, - background: style - .background - .unwrap_or(Background::Color(Color::TRANSPARENT)), - border_radius: style.border_radius, - border_width: style.border_width, - border_color: style.border_color, - } - } else { - Primitive::None - }; - - Primitive::Group { - primitives: vec![clip, scrollbar, scroller], - } - } else { - content - }, - if is_mouse_over_scrollbar || state.is_scroller_grabbed() { - mouse::Interaction::Idle - } else { - mouse_interaction - }, - ) - } -} diff --git a/graphics/src/renderer/widget/slider.rs b/graphics/src/renderer/widget/slider.rs deleted file mode 100644 index 95c843d0..00000000 --- a/graphics/src/renderer/widget/slider.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::slider::{HandleShape, StyleSheet}; -use crate::{Backend, Primitive, Renderer}; -use iced_native::mouse; -use iced_native::slider; -use iced_native::{Background, Color, Point, Rectangle}; - -const HANDLE_HEIGHT: f32 = 22.0; - -impl slider::Renderer for Renderer -where - B: Backend, -{ - type Style = Box; - - fn height(&self) -> u32 { - 30 - } - - fn draw( - &mut self, - bounds: Rectangle, - cursor_position: Point, - range: std::ops::RangeInclusive, - value: f32, - is_dragging: bool, - style_sheet: &Self::Style, - ) -> Self::Output { - let is_mouse_over = bounds.contains(cursor_position); - - let style = if is_dragging { - style_sheet.dragging() - } else if is_mouse_over { - style_sheet.hovered() - } else { - style_sheet.active() - }; - - let rail_y = bounds.y + (bounds.height / 2.0).round(); - - let (rail_top, rail_bottom) = ( - Primitive::Quad { - bounds: Rectangle { - x: bounds.x, - y: rail_y, - width: bounds.width, - height: 2.0, - }, - background: Background::Color(style.rail_colors.0), - border_radius: 0, - border_width: 0, - border_color: Color::TRANSPARENT, - }, - Primitive::Quad { - bounds: Rectangle { - x: bounds.x, - y: rail_y + 2.0, - width: bounds.width, - height: 2.0, - }, - background: Background::Color(style.rail_colors.1), - border_radius: 0, - border_width: 0, - border_color: Color::TRANSPARENT, - }, - ); - - let (range_start, range_end) = range.into_inner(); - - let (handle_width, handle_height, handle_border_radius) = - match style.handle.shape { - HandleShape::Circle { radius } => { - (f32::from(radius * 2), f32::from(radius * 2), radius) - } - HandleShape::Rectangle { - width, - border_radius, - } => (f32::from(width), HANDLE_HEIGHT, border_radius), - }; - - let handle_offset = (bounds.width - handle_width) - * ((value - range_start) / (range_end - range_start).max(1.0)); - - let handle = Primitive::Quad { - bounds: Rectangle { - x: bounds.x + handle_offset.round(), - y: rail_y - handle_height / 2.0, - width: handle_width, - height: handle_height, - }, - background: Background::Color(style.handle.color), - border_radius: handle_border_radius, - border_width: style.handle.border_width, - border_color: style.handle.border_color, - }; - - ( - Primitive::Group { - primitives: vec![rail_top, rail_bottom, handle], - }, - if is_dragging { - mouse::Interaction::Grabbing - } else if is_mouse_over { - mouse::Interaction::Grab - } else { - mouse::Interaction::default() - }, - ) - } -} diff --git a/graphics/src/renderer/widget/text.rs b/graphics/src/renderer/widget/text.rs deleted file mode 100644 index 45cb1be2..00000000 --- a/graphics/src/renderer/widget/text.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::backend::{self, Backend}; -use crate::{Primitive, Renderer}; -use iced_native::{ - mouse, text, Color, Font, HorizontalAlignment, Rectangle, Size, - VerticalAlignment, -}; - -use std::f32; - -impl text::Renderer for Renderer -where - B: Backend + backend::Text, -{ - type Font = Font; - - const DEFAULT_SIZE: u16 = 20; - - fn measure( - &self, - content: &str, - size: u16, - font: Font, - bounds: Size, - ) -> (f32, f32) { - self.backend() - .measure(content, f32::from(size), font, bounds) - } - - fn draw( - &mut self, - defaults: &Self::Defaults, - bounds: Rectangle, - content: &str, - size: u16, - font: Font, - color: Option, - horizontal_alignment: HorizontalAlignment, - vertical_alignment: VerticalAlignment, - ) -> Self::Output { - let x = match horizontal_alignment { - iced_native::HorizontalAlignment::Left => bounds.x, - iced_native::HorizontalAlignment::Center => bounds.center_x(), - iced_native::HorizontalAlignment::Right => bounds.x + bounds.width, - }; - - let y = match vertical_alignment { - iced_native::VerticalAlignment::Top => bounds.y, - iced_native::VerticalAlignment::Center => bounds.center_y(), - iced_native::VerticalAlignment::Bottom => bounds.y + bounds.height, - }; - - ( - Primitive::Text { - content: content.to_string(), - size: f32::from(size), - bounds: Rectangle { x, y, ..bounds }, - color: color.unwrap_or(defaults.text.color), - font, - horizontal_alignment, - vertical_alignment, - }, - mouse::Interaction::default(), - ) - } -} diff --git a/graphics/src/renderer/widget/text_input.rs b/graphics/src/renderer/widget/text_input.rs deleted file mode 100644 index 33807b0f..00000000 --- a/graphics/src/renderer/widget/text_input.rs +++ /dev/null @@ -1,267 +0,0 @@ -use crate::backend::{self, Backend}; -use crate::text_input::StyleSheet; -use crate::{Primitive, Renderer}; - -use iced_native::{ - mouse, - text_input::{self, cursor}, - Background, Color, Font, HorizontalAlignment, Point, Rectangle, Size, - Vector, VerticalAlignment, -}; -use std::f32; - -impl text_input::Renderer for Renderer -where - B: Backend + backend::Text, -{ - type Style = Box; - - fn default_size(&self) -> u16 { - // TODO: Make this configurable - 20 - } - - fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 { - let backend = self.backend(); - - let (mut width, _) = - backend.measure(value, f32::from(size), font, Size::INFINITY); - - let spaces_around = value.len() - value.trim().len(); - - if spaces_around > 0 { - let space_width = backend.space_width(size as f32); - width += spaces_around as f32 * space_width; - } - - width - } - - fn offset( - &self, - text_bounds: Rectangle, - font: Font, - size: u16, - value: &text_input::Value, - state: &text_input::State, - ) -> f32 { - if state.is_focused() { - let cursor = state.cursor(); - - let focus_position = match cursor.state(value) { - cursor::State::Index(i) => i, - cursor::State::Selection { end, .. } => end, - }; - - let (_, offset) = measure_cursor_and_scroll_offset( - self, - text_bounds, - value, - size, - focus_position, - font, - ); - - offset - } else { - 0.0 - } - } - - fn draw( - &mut self, - bounds: Rectangle, - text_bounds: Rectangle, - cursor_position: Point, - font: Font, - size: u16, - placeholder: &str, - value: &text_input::Value, - state: &text_input::State, - style_sheet: &Self::Style, - ) -> Self::Output { - let is_mouse_over = bounds.contains(cursor_position); - - let style = if state.is_focused() { - style_sheet.focused() - } else if is_mouse_over { - style_sheet.hovered() - } else { - style_sheet.active() - }; - - let input = Primitive::Quad { - bounds, - background: style.background, - border_radius: style.border_radius, - border_width: style.border_width, - border_color: style.border_color, - }; - - let text = value.to_string(); - - let text_value = Primitive::Text { - content: if text.is_empty() { - placeholder.to_string() - } else { - text.clone() - }, - color: if text.is_empty() { - style_sheet.placeholder_color() - } else { - style_sheet.value_color() - }, - font, - bounds: Rectangle { - y: text_bounds.center_y(), - width: f32::INFINITY, - ..text_bounds - }, - size: f32::from(size), - horizontal_alignment: HorizontalAlignment::Left, - vertical_alignment: VerticalAlignment::Center, - }; - - let (contents_primitive, offset) = if state.is_focused() { - let cursor = state.cursor(); - - let (cursor_primitive, offset) = match cursor.state(value) { - cursor::State::Index(position) => { - let (text_value_width, offset) = - measure_cursor_and_scroll_offset( - self, - text_bounds, - value, - size, - position, - font, - ); - - ( - Primitive::Quad { - bounds: Rectangle { - x: text_bounds.x + text_value_width, - y: text_bounds.y, - width: 1.0, - height: text_bounds.height, - }, - background: Background::Color( - style_sheet.value_color(), - ), - border_radius: 0, - border_width: 0, - border_color: Color::TRANSPARENT, - }, - offset, - ) - } - cursor::State::Selection { start, end } => { - let left = start.min(end); - let right = end.max(start); - - let (left_position, left_offset) = - measure_cursor_and_scroll_offset( - self, - text_bounds, - value, - size, - left, - font, - ); - - let (right_position, right_offset) = - measure_cursor_and_scroll_offset( - self, - text_bounds, - value, - size, - right, - font, - ); - - let width = right_position - left_position; - - ( - Primitive::Quad { - bounds: Rectangle { - x: text_bounds.x + left_position, - y: text_bounds.y, - width, - height: text_bounds.height, - }, - background: Background::Color( - style_sheet.selection_color(), - ), - border_radius: 0, - border_width: 0, - border_color: Color::TRANSPARENT, - }, - if end == right { - right_offset - } else { - left_offset - }, - ) - } - }; - - ( - Primitive::Group { - primitives: vec![cursor_primitive, text_value], - }, - Vector::new(offset as u32, 0), - ) - } else { - (text_value, Vector::new(0, 0)) - }; - - let text_width = self.measure_value( - if text.is_empty() { placeholder } else { &text }, - size, - font, - ); - - let contents = if text_width > text_bounds.width { - Primitive::Clip { - bounds: text_bounds, - offset, - content: Box::new(contents_primitive), - } - } else { - contents_primitive - }; - - ( - Primitive::Group { - primitives: vec![input, contents], - }, - if is_mouse_over { - mouse::Interaction::Text - } else { - mouse::Interaction::default() - }, - ) - } -} - -fn measure_cursor_and_scroll_offset( - renderer: &Renderer, - text_bounds: Rectangle, - value: &text_input::Value, - size: u16, - cursor_index: usize, - font: Font, -) -> (f32, f32) -where - B: Backend + backend::Text, -{ - use iced_native::text_input::Renderer; - - let text_before_cursor = value.until(cursor_index).to_string(); - - let text_value_width = - renderer.measure_value(&text_before_cursor, size, font); - let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0); - - (text_value_width, offset) -} diff --git a/graphics/src/widget.rs b/graphics/src/widget.rs index 6a8e814b..d9c91d77 100644 --- a/graphics/src/widget.rs +++ b/graphics/src/widget.rs @@ -17,6 +17,11 @@ pub mod scrollable; pub mod slider; pub mod text_input; +mod column; +mod image; +mod row; +mod space; +mod svg; mod text; #[doc(no_inline)] @@ -38,6 +43,11 @@ pub use slider::Slider; #[doc(no_inline)] pub use text_input::TextInput; +pub use column::Column; +pub use image::Image; +pub use row::Row; +pub use space::Space; +pub use svg::Svg; pub use text::Text; #[cfg(feature = "canvas")] diff --git a/graphics/src/widget/button.rs b/graphics/src/widget/button.rs index 9debf5c3..aeb862d5 100644 --- a/graphics/src/widget/button.rs +++ b/graphics/src/widget/button.rs @@ -4,7 +4,12 @@ //! //! [`Button`]: type.Button.html //! [`State`]: struct.State.html -use crate::Renderer; +use crate::defaults::{self, Defaults}; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::{ + Background, Color, Element, Layout, Point, Rectangle, Vector, +}; pub use iced_native::button::State; pub use iced_style::button::{Style, StyleSheet}; @@ -14,3 +19,95 @@ pub use iced_style::button::{Style, StyleSheet}; /// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`. pub type Button<'a, Message, Backend> = iced_native::Button<'a, Message, Renderer>; + +impl iced_native::button::Renderer for Renderer +where + B: Backend, +{ + const DEFAULT_PADDING: u16 = 5; + + type Style = Box; + + fn draw( + &mut self, + _defaults: &Defaults, + bounds: Rectangle, + cursor_position: Point, + is_disabled: bool, + is_pressed: bool, + style: &Box, + content: &Element<'_, Message, Self>, + content_layout: Layout<'_>, + ) -> Self::Output { + let is_mouse_over = bounds.contains(cursor_position); + + let styling = if is_disabled { + style.disabled() + } else if is_mouse_over { + if is_pressed { + style.pressed() + } else { + style.hovered() + } + } else { + style.active() + }; + + let (content, _) = content.draw( + self, + &Defaults { + text: defaults::Text { + color: styling.text_color, + }, + }, + content_layout, + cursor_position, + ); + + ( + if styling.background.is_some() || styling.border_width > 0 { + let background = Primitive::Quad { + bounds, + background: styling + .background + .unwrap_or(Background::Color(Color::TRANSPARENT)), + border_radius: styling.border_radius, + border_width: styling.border_width, + border_color: styling.border_color, + }; + + if styling.shadow_offset == Vector::default() { + Primitive::Group { + primitives: vec![background, content], + } + } else { + // TODO: Implement proper shadow support + let shadow = Primitive::Quad { + bounds: Rectangle { + x: bounds.x + styling.shadow_offset.x, + y: bounds.y + styling.shadow_offset.y, + ..bounds + }, + background: Background::Color( + [0.0, 0.0, 0.0, 0.5].into(), + ), + border_radius: styling.border_radius, + border_width: 0, + border_color: Color::TRANSPARENT, + }; + + Primitive::Group { + primitives: vec![shadow, background, content], + } + } + } else { + content + }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/graphics/src/widget/checkbox.rs b/graphics/src/widget/checkbox.rs index 3c8b58de..cb7fd2cf 100644 --- a/graphics/src/widget/checkbox.rs +++ b/graphics/src/widget/checkbox.rs @@ -1,5 +1,9 @@ //! Show toggle controls using checkboxes. -use crate::Renderer; +use crate::backend::{self, Backend}; +use crate::{Primitive, Renderer}; +use iced_native::checkbox; +use iced_native::mouse; +use iced_native::{HorizontalAlignment, Rectangle, VerticalAlignment}; pub use iced_style::checkbox::{Style, StyleSheet}; @@ -8,3 +12,65 @@ pub use iced_style::checkbox::{Style, StyleSheet}; /// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`. pub type Checkbox = iced_native::Checkbox>; + +impl checkbox::Renderer for Renderer +where + B: Backend + backend::Text, +{ + type Style = Box; + + const DEFAULT_SIZE: u16 = 20; + const DEFAULT_SPACING: u16 = 15; + + fn draw( + &mut self, + bounds: Rectangle, + is_checked: bool, + is_mouse_over: bool, + (label, _): Self::Output, + style_sheet: &Self::Style, + ) -> Self::Output { + let style = if is_mouse_over { + style_sheet.hovered(is_checked) + } else { + style_sheet.active(is_checked) + }; + + let checkbox = Primitive::Quad { + bounds, + background: style.background, + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, + }; + + ( + Primitive::Group { + primitives: if is_checked { + let check = Primitive::Text { + content: B::CHECKMARK_ICON.to_string(), + font: B::ICON_FONT, + size: bounds.height * 0.7, + bounds: Rectangle { + x: bounds.center_x(), + y: bounds.center_y(), + ..bounds + }, + color: style.checkmark_color, + horizontal_alignment: HorizontalAlignment::Center, + vertical_alignment: VerticalAlignment::Center, + }; + + vec![checkbox, check, label] + } else { + vec![checkbox, label] + }, + }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/graphics/src/renderer/widget/column.rs b/graphics/src/widget/column.rs similarity index 92% rename from graphics/src/renderer/widget/column.rs rename to graphics/src/widget/column.rs index b70d2338..9183d2ee 100644 --- a/graphics/src/renderer/widget/column.rs +++ b/graphics/src/widget/column.rs @@ -3,6 +3,9 @@ use iced_native::column; use iced_native::mouse; use iced_native::{Element, Layout, Point}; +pub type Column<'a, Message, Backend> = + iced_native::Column<'a, Message, Renderer>; + impl column::Renderer for Renderer where B: Backend, diff --git a/graphics/src/widget/container.rs b/graphics/src/widget/container.rs index c4c4e5ba..070cb48b 100644 --- a/graphics/src/widget/container.rs +++ b/graphics/src/widget/container.rs @@ -1,5 +1,8 @@ //! Decorate content and apply alignment. -use crate::Renderer; +use crate::container; +use crate::defaults::{self, Defaults}; +use crate::{Backend, Primitive, Renderer}; +use iced_native::{Background, Color, Element, Layout, Point, Rectangle}; pub use iced_style::container::{Style, StyleSheet}; @@ -9,3 +12,52 @@ pub use iced_style::container::{Style, StyleSheet}; /// `Renderer`. pub type Container<'a, Message, Backend> = iced_native::Container<'a, Message, Renderer>; + +impl iced_native::container::Renderer for Renderer +where + B: Backend, +{ + type Style = Box; + + fn draw( + &mut self, + defaults: &Defaults, + bounds: Rectangle, + cursor_position: Point, + style_sheet: &Self::Style, + content: &Element<'_, Message, Self>, + content_layout: Layout<'_>, + ) -> Self::Output { + let style = style_sheet.style(); + + let defaults = Defaults { + text: defaults::Text { + color: style.text_color.unwrap_or(defaults.text.color), + }, + }; + + let (content, mouse_interaction) = + content.draw(self, &defaults, content_layout, cursor_position); + + if style.background.is_some() || style.border_width > 0 { + let quad = Primitive::Quad { + bounds, + background: style + .background + .unwrap_or(Background::Color(Color::TRANSPARENT)), + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, + }; + + ( + Primitive::Group { + primitives: vec![quad, content], + }, + mouse_interaction, + ) + } else { + (content, mouse_interaction) + } + } +} diff --git a/graphics/src/renderer/widget/image.rs b/graphics/src/widget/image.rs similarity index 95% rename from graphics/src/renderer/widget/image.rs rename to graphics/src/widget/image.rs index 3092237f..79d82cb1 100644 --- a/graphics/src/renderer/widget/image.rs +++ b/graphics/src/widget/image.rs @@ -4,6 +4,8 @@ use iced_native::image; use iced_native::mouse; use iced_native::Layout; +pub use iced_native::Image; + impl image::Renderer for Renderer where B: Backend + backend::Image, diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs index 34be8ee7..56af683d 100644 --- a/graphics/src/widget/pane_grid.rs +++ b/graphics/src/widget/pane_grid.rs @@ -8,7 +8,10 @@ //! //! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.1/examples/pane_grid //! [`PaneGrid`]: type.PaneGrid.html -use crate::Renderer; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::pane_grid; +use iced_native::{Element, Layout, Point, Rectangle, Vector}; pub use iced_native::pane_grid::{ Axis, Direction, DragEvent, Focus, KeyPressEvent, Pane, ResizeEvent, Split, @@ -23,3 +26,93 @@ pub use iced_native::pane_grid::{ /// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`. pub type PaneGrid<'a, Message, Backend> = iced_native::PaneGrid<'a, Message, Renderer>; + +impl pane_grid::Renderer for Renderer +where + B: Backend, +{ + fn draw( + &mut self, + defaults: &Self::Defaults, + content: &[(Pane, Element<'_, Message, Self>)], + dragging: Option, + resizing: Option, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let pane_cursor_position = if dragging.is_some() { + // TODO: Remove once cursor availability is encoded in the type + // system + Point::new(-1.0, -1.0) + } else { + cursor_position + }; + + let mut mouse_interaction = mouse::Interaction::default(); + let mut dragged_pane = None; + + let mut panes: Vec<_> = content + .iter() + .zip(layout.children()) + .enumerate() + .map(|(i, ((id, pane), layout))| { + let (primitive, new_mouse_interaction) = + pane.draw(self, defaults, layout, pane_cursor_position); + + if new_mouse_interaction > mouse_interaction { + mouse_interaction = new_mouse_interaction; + } + + if Some(*id) == dragging { + dragged_pane = Some((i, layout)); + } + + primitive + }) + .collect(); + + let primitives = if let Some((index, layout)) = dragged_pane { + let pane = panes.remove(index); + let bounds = layout.bounds(); + + // TODO: Fix once proper layering is implemented. + // This is a pretty hacky way to achieve layering. + let clip = Primitive::Clip { + bounds: Rectangle { + x: cursor_position.x - bounds.width / 2.0, + y: cursor_position.y - bounds.height / 2.0, + width: bounds.width + 0.5, + height: bounds.height + 0.5, + }, + offset: Vector::new(0, 0), + content: Box::new(Primitive::Translate { + translation: Vector::new( + cursor_position.x - bounds.x - bounds.width / 2.0, + cursor_position.y - bounds.y - bounds.height / 2.0, + ), + content: Box::new(pane), + }), + }; + + panes.push(clip); + + panes + } else { + panes + }; + + ( + Primitive::Group { primitives }, + if dragging.is_some() { + mouse::Interaction::Grabbing + } else if let Some(axis) = resizing { + match axis { + Axis::Horizontal => mouse::Interaction::ResizingVertically, + Axis::Vertical => mouse::Interaction::ResizingHorizontally, + } + } else { + mouse_interaction + }, + ) + } +} diff --git a/graphics/src/widget/progress_bar.rs b/graphics/src/widget/progress_bar.rs index c1e5702e..48acb3c1 100644 --- a/graphics/src/widget/progress_bar.rs +++ b/graphics/src/widget/progress_bar.rs @@ -4,7 +4,10 @@ //! as well as a length, height and style. //! //! [`ProgressBar`]: type.ProgressBar.html -use crate::Renderer; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::progress_bar; +use iced_native::{Color, Rectangle}; pub use iced_style::progress_bar::{Style, StyleSheet}; @@ -13,3 +16,58 @@ pub use iced_style::progress_bar::{Style, StyleSheet}; /// This is an alias of an `iced_native` progress bar with an /// `iced_wgpu::Renderer`. pub type ProgressBar = iced_native::ProgressBar>; + +impl progress_bar::Renderer for Renderer +where + B: Backend, +{ + type Style = Box; + + const DEFAULT_HEIGHT: u16 = 30; + + fn draw( + &self, + bounds: Rectangle, + range: std::ops::RangeInclusive, + value: f32, + style_sheet: &Self::Style, + ) -> Self::Output { + let style = style_sheet.style(); + + let (range_start, range_end) = range.into_inner(); + let active_progress_width = bounds.width + * ((value - range_start) / (range_end - range_start).max(1.0)); + + let background = Primitive::Group { + primitives: vec![Primitive::Quad { + bounds: Rectangle { ..bounds }, + background: style.background, + border_radius: style.border_radius, + border_width: 0, + border_color: Color::TRANSPARENT, + }], + }; + + ( + if active_progress_width > 0.0 { + let bar = Primitive::Quad { + bounds: Rectangle { + width: active_progress_width, + ..bounds + }, + background: style.bar, + border_radius: style.border_radius, + border_width: 0, + border_color: Color::TRANSPARENT, + }; + + Primitive::Group { + primitives: vec![background, bar], + } + } else { + background + }, + mouse::Interaction::default(), + ) + } +} diff --git a/graphics/src/widget/radio.rs b/graphics/src/widget/radio.rs index c621a26a..dd8b5f17 100644 --- a/graphics/src/widget/radio.rs +++ b/graphics/src/widget/radio.rs @@ -1,5 +1,8 @@ //! Create choices using radio buttons. -use crate::Renderer; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::radio; +use iced_native::{Background, Color, Rectangle}; pub use iced_style::radio::{Style, StyleSheet}; @@ -9,3 +12,67 @@ pub use iced_style::radio::{Style, StyleSheet}; /// `iced_wgpu::Renderer`. pub type Radio = iced_native::Radio>; + +const SIZE: f32 = 28.0; +const DOT_SIZE: f32 = SIZE / 2.0; + +impl radio::Renderer for Renderer +where + B: Backend, +{ + type Style = Box; + + const DEFAULT_SIZE: u16 = SIZE as u16; + const DEFAULT_SPACING: u16 = 15; + + fn draw( + &mut self, + bounds: Rectangle, + is_selected: bool, + is_mouse_over: bool, + (label, _): Self::Output, + style_sheet: &Self::Style, + ) -> Self::Output { + let style = if is_mouse_over { + style_sheet.hovered() + } else { + style_sheet.active() + }; + + let radio = Primitive::Quad { + bounds, + background: style.background, + border_radius: (SIZE / 2.0) as u16, + border_width: style.border_width, + border_color: style.border_color, + }; + + ( + Primitive::Group { + primitives: if is_selected { + let radio_circle = Primitive::Quad { + bounds: Rectangle { + x: bounds.x + DOT_SIZE / 2.0, + y: bounds.y + DOT_SIZE / 2.0, + width: bounds.width - DOT_SIZE, + height: bounds.height - DOT_SIZE, + }, + background: Background::Color(style.dot_color), + border_radius: (DOT_SIZE / 2.0) as u16, + border_width: 0, + border_color: Color::TRANSPARENT, + }; + + vec![radio, radio_circle, label] + } else { + vec![radio, label] + }, + }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/graphics/src/renderer/widget/row.rs b/graphics/src/widget/row.rs similarity index 85% rename from graphics/src/renderer/widget/row.rs rename to graphics/src/widget/row.rs index b0bb0d2e..9865d0de 100644 --- a/graphics/src/renderer/widget/row.rs +++ b/graphics/src/widget/row.rs @@ -3,6 +3,9 @@ use iced_native::mouse; use iced_native::row; use iced_native::{Element, Layout, Point}; +pub type Row<'a, Message, Backend> = + iced_native::Row<'a, Message, Renderer>; + impl row::Renderer for Renderer where B: Backend, @@ -10,7 +13,7 @@ where fn draw( &mut self, defaults: &Self::Defaults, - children: &[Element<'_, Message, Self>], + content: &[Element<'_, Message, Self>], layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { @@ -18,7 +21,7 @@ where ( Primitive::Group { - primitives: children + primitives: content .iter() .zip(layout.children()) .map(|(child, layout)| { diff --git a/graphics/src/widget/scrollable.rs b/graphics/src/widget/scrollable.rs index 61eae587..b149db0a 100644 --- a/graphics/src/widget/scrollable.rs +++ b/graphics/src/widget/scrollable.rs @@ -1,5 +1,8 @@ //! Navigate an endless amount of content with a scrollbar. -use crate::Renderer; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::scrollable; +use iced_native::{Background, Color, Rectangle, Vector}; pub use iced_native::scrollable::State; pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; @@ -11,3 +14,129 @@ pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; /// `Renderer`. pub type Scrollable<'a, Message, Backend> = iced_native::Scrollable<'a, Message, Renderer>; + +const SCROLLBAR_WIDTH: u16 = 10; +const SCROLLBAR_MARGIN: u16 = 2; + +impl scrollable::Renderer for Renderer +where + B: Backend, +{ + type Style = Box; + + fn scrollbar( + &self, + bounds: Rectangle, + content_bounds: Rectangle, + offset: u32, + ) -> Option { + if content_bounds.height > bounds.height { + let scrollbar_bounds = Rectangle { + x: bounds.x + bounds.width + - f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), + y: bounds.y, + width: f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), + height: bounds.height, + }; + + let ratio = bounds.height / content_bounds.height; + let scrollbar_height = bounds.height * ratio; + let y_offset = offset as f32 * ratio; + + let scroller_bounds = Rectangle { + x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), + y: scrollbar_bounds.y + y_offset, + width: scrollbar_bounds.width - f32::from(2 * SCROLLBAR_MARGIN), + height: scrollbar_height, + }; + + Some(scrollable::Scrollbar { + bounds: scrollbar_bounds, + scroller: scrollable::Scroller { + bounds: scroller_bounds, + }, + }) + } else { + None + } + } + + fn draw( + &mut self, + state: &scrollable::State, + bounds: Rectangle, + _content_bounds: Rectangle, + is_mouse_over: bool, + is_mouse_over_scrollbar: bool, + scrollbar: Option, + offset: u32, + style_sheet: &Self::Style, + (content, mouse_interaction): Self::Output, + ) -> Self::Output { + ( + if let Some(scrollbar) = scrollbar { + let clip = Primitive::Clip { + bounds, + offset: Vector::new(0, offset), + content: Box::new(content), + }; + + let style = if state.is_scroller_grabbed() { + style_sheet.dragging() + } else if is_mouse_over_scrollbar { + style_sheet.hovered() + } else { + style_sheet.active() + }; + + let is_scrollbar_visible = + style.background.is_some() || style.border_width > 0; + + let scroller = if is_mouse_over + || state.is_scroller_grabbed() + || is_scrollbar_visible + { + Primitive::Quad { + bounds: scrollbar.scroller.bounds, + background: Background::Color(style.scroller.color), + border_radius: style.scroller.border_radius, + border_width: style.scroller.border_width, + border_color: style.scroller.border_color, + } + } else { + Primitive::None + }; + + let scrollbar = if is_scrollbar_visible { + Primitive::Quad { + bounds: Rectangle { + x: scrollbar.bounds.x + f32::from(SCROLLBAR_MARGIN), + width: scrollbar.bounds.width + - f32::from(2 * SCROLLBAR_MARGIN), + ..scrollbar.bounds + }, + background: style + .background + .unwrap_or(Background::Color(Color::TRANSPARENT)), + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, + } + } else { + Primitive::None + }; + + Primitive::Group { + primitives: vec![clip, scrollbar, scroller], + } + } else { + content + }, + if is_mouse_over_scrollbar || state.is_scroller_grabbed() { + mouse::Interaction::Idle + } else { + mouse_interaction + }, + ) + } +} diff --git a/graphics/src/widget/slider.rs b/graphics/src/widget/slider.rs index 035c7c41..b00cde9a 100644 --- a/graphics/src/widget/slider.rs +++ b/graphics/src/widget/slider.rs @@ -4,7 +4,10 @@ //! //! [`Slider`]: struct.Slider.html //! [`State`]: struct.State.html -use crate::Renderer; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::slider; +use iced_native::{Background, Color, Point, Rectangle}; pub use iced_native::slider::State; pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; @@ -15,3 +18,107 @@ pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; /// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`. pub type Slider<'a, Message, Backend> = iced_native::Slider<'a, Message, Renderer>; + +const HANDLE_HEIGHT: f32 = 22.0; + +impl slider::Renderer for Renderer +where + B: Backend, +{ + type Style = Box; + + fn height(&self) -> u32 { + 30 + } + + fn draw( + &mut self, + bounds: Rectangle, + cursor_position: Point, + range: std::ops::RangeInclusive, + value: f32, + is_dragging: bool, + style_sheet: &Self::Style, + ) -> Self::Output { + let is_mouse_over = bounds.contains(cursor_position); + + let style = if is_dragging { + style_sheet.dragging() + } else if is_mouse_over { + style_sheet.hovered() + } else { + style_sheet.active() + }; + + let rail_y = bounds.y + (bounds.height / 2.0).round(); + + let (rail_top, rail_bottom) = ( + Primitive::Quad { + bounds: Rectangle { + x: bounds.x, + y: rail_y, + width: bounds.width, + height: 2.0, + }, + background: Background::Color(style.rail_colors.0), + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + Primitive::Quad { + bounds: Rectangle { + x: bounds.x, + y: rail_y + 2.0, + width: bounds.width, + height: 2.0, + }, + background: Background::Color(style.rail_colors.1), + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + ); + + let (range_start, range_end) = range.into_inner(); + + let (handle_width, handle_height, handle_border_radius) = + match style.handle.shape { + HandleShape::Circle { radius } => { + (f32::from(radius * 2), f32::from(radius * 2), radius) + } + HandleShape::Rectangle { + width, + border_radius, + } => (f32::from(width), HANDLE_HEIGHT, border_radius), + }; + + let handle_offset = (bounds.width - handle_width) + * ((value - range_start) / (range_end - range_start).max(1.0)); + + let handle = Primitive::Quad { + bounds: Rectangle { + x: bounds.x + handle_offset.round(), + y: rail_y - handle_height / 2.0, + width: handle_width, + height: handle_height, + }, + background: Background::Color(style.handle.color), + border_radius: handle_border_radius, + border_width: style.handle.border_width, + border_color: style.handle.border_color, + }; + + ( + Primitive::Group { + primitives: vec![rail_top, rail_bottom, handle], + }, + if is_dragging { + mouse::Interaction::Grabbing + } else if is_mouse_over { + mouse::Interaction::Grab + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/graphics/src/renderer/widget/space.rs b/graphics/src/widget/space.rs similarity index 91% rename from graphics/src/renderer/widget/space.rs rename to graphics/src/widget/space.rs index d0e82f8d..1f31eabe 100644 --- a/graphics/src/renderer/widget/space.rs +++ b/graphics/src/widget/space.rs @@ -3,6 +3,8 @@ use iced_native::mouse; use iced_native::space; use iced_native::Rectangle; +pub use iced_native::Space; + impl space::Renderer for Renderer where B: Backend, diff --git a/graphics/src/renderer/widget/svg.rs b/graphics/src/widget/svg.rs similarity index 95% rename from graphics/src/renderer/widget/svg.rs rename to graphics/src/widget/svg.rs index 4d80869e..b1aa7cea 100644 --- a/graphics/src/renderer/widget/svg.rs +++ b/graphics/src/widget/svg.rs @@ -2,6 +2,8 @@ use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::{mouse, svg, Layout}; +pub use iced_native::Svg; + impl svg::Renderer for Renderer where B: Backend + backend::Svg, diff --git a/graphics/src/widget/text.rs b/graphics/src/widget/text.rs index ec0349f9..327f8e29 100644 --- a/graphics/src/widget/text.rs +++ b/graphics/src/widget/text.rs @@ -1,7 +1,72 @@ //! Write some text for your users to read. -use crate::Renderer; +use crate::backend::{self, Backend}; +use crate::{Primitive, Renderer}; +use iced_native::mouse; +use iced_native::text; +use iced_native::{ + Color, Font, HorizontalAlignment, Rectangle, Size, VerticalAlignment, +}; /// A paragraph of text. /// /// This is an alias of an `iced_native` text with an `iced_wgpu::Renderer`. pub type Text = iced_native::Text>; + +use std::f32; + +impl text::Renderer for Renderer +where + B: Backend + backend::Text, +{ + type Font = Font; + + const DEFAULT_SIZE: u16 = 20; + + fn measure( + &self, + content: &str, + size: u16, + font: Font, + bounds: Size, + ) -> (f32, f32) { + self.backend() + .measure(content, f32::from(size), font, bounds) + } + + fn draw( + &mut self, + defaults: &Self::Defaults, + bounds: Rectangle, + content: &str, + size: u16, + font: Font, + color: Option, + horizontal_alignment: HorizontalAlignment, + vertical_alignment: VerticalAlignment, + ) -> Self::Output { + let x = match horizontal_alignment { + iced_native::HorizontalAlignment::Left => bounds.x, + iced_native::HorizontalAlignment::Center => bounds.center_x(), + iced_native::HorizontalAlignment::Right => bounds.x + bounds.width, + }; + + let y = match vertical_alignment { + iced_native::VerticalAlignment::Top => bounds.y, + iced_native::VerticalAlignment::Center => bounds.center_y(), + iced_native::VerticalAlignment::Bottom => bounds.y + bounds.height, + }; + + ( + Primitive::Text { + content: content.to_string(), + size: f32::from(size), + bounds: Rectangle { x, y, ..bounds }, + color: color.unwrap_or(defaults.text.color), + font, + horizontal_alignment, + vertical_alignment, + }, + mouse::Interaction::default(), + ) + } +} diff --git a/graphics/src/widget/text_input.rs b/graphics/src/widget/text_input.rs index 8015626b..023bdd7f 100644 --- a/graphics/src/widget/text_input.rs +++ b/graphics/src/widget/text_input.rs @@ -4,7 +4,15 @@ //! //! [`TextInput`]: struct.TextInput.html //! [`State`]: struct.State.html -use crate::Renderer; +use crate::backend::{self, Backend}; +use crate::{Primitive, Renderer}; +use iced_native::mouse; +use iced_native::text_input::{self, cursor}; +use iced_native::{ + Background, Color, Font, HorizontalAlignment, Point, Rectangle, Size, + Vector, VerticalAlignment, +}; +use std::f32; pub use iced_native::text_input::State; pub use iced_style::text_input::{Style, StyleSheet}; @@ -14,3 +22,259 @@ pub use iced_style::text_input::{Style, StyleSheet}; /// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`. pub type TextInput<'a, Message, Backend> = iced_native::TextInput<'a, Message, Renderer>; + +impl text_input::Renderer for Renderer +where + B: Backend + backend::Text, +{ + type Style = Box; + + fn default_size(&self) -> u16 { + // TODO: Make this configurable + 20 + } + + fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 { + let backend = self.backend(); + + let (mut width, _) = + backend.measure(value, f32::from(size), font, Size::INFINITY); + + let spaces_around = value.len() - value.trim().len(); + + if spaces_around > 0 { + let space_width = backend.space_width(size as f32); + width += spaces_around as f32 * space_width; + } + + width + } + + fn offset( + &self, + text_bounds: Rectangle, + font: Font, + size: u16, + value: &text_input::Value, + state: &text_input::State, + ) -> f32 { + if state.is_focused() { + let cursor = state.cursor(); + + let focus_position = match cursor.state(value) { + cursor::State::Index(i) => i, + cursor::State::Selection { end, .. } => end, + }; + + let (_, offset) = measure_cursor_and_scroll_offset( + self, + text_bounds, + value, + size, + focus_position, + font, + ); + + offset + } else { + 0.0 + } + } + + fn draw( + &mut self, + bounds: Rectangle, + text_bounds: Rectangle, + cursor_position: Point, + font: Font, + size: u16, + placeholder: &str, + value: &text_input::Value, + state: &text_input::State, + style_sheet: &Self::Style, + ) -> Self::Output { + let is_mouse_over = bounds.contains(cursor_position); + + let style = if state.is_focused() { + style_sheet.focused() + } else if is_mouse_over { + style_sheet.hovered() + } else { + style_sheet.active() + }; + + let input = Primitive::Quad { + bounds, + background: style.background, + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, + }; + + let text = value.to_string(); + + let text_value = Primitive::Text { + content: if text.is_empty() { + placeholder.to_string() + } else { + text.clone() + }, + color: if text.is_empty() { + style_sheet.placeholder_color() + } else { + style_sheet.value_color() + }, + font, + bounds: Rectangle { + y: text_bounds.center_y(), + width: f32::INFINITY, + ..text_bounds + }, + size: f32::from(size), + horizontal_alignment: HorizontalAlignment::Left, + vertical_alignment: VerticalAlignment::Center, + }; + + let (contents_primitive, offset) = if state.is_focused() { + let cursor = state.cursor(); + + let (cursor_primitive, offset) = match cursor.state(value) { + cursor::State::Index(position) => { + let (text_value_width, offset) = + measure_cursor_and_scroll_offset( + self, + text_bounds, + value, + size, + position, + font, + ); + + ( + Primitive::Quad { + bounds: Rectangle { + x: text_bounds.x + text_value_width, + y: text_bounds.y, + width: 1.0, + height: text_bounds.height, + }, + background: Background::Color( + style_sheet.value_color(), + ), + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + offset, + ) + } + cursor::State::Selection { start, end } => { + let left = start.min(end); + let right = end.max(start); + + let (left_position, left_offset) = + measure_cursor_and_scroll_offset( + self, + text_bounds, + value, + size, + left, + font, + ); + + let (right_position, right_offset) = + measure_cursor_and_scroll_offset( + self, + text_bounds, + value, + size, + right, + font, + ); + + let width = right_position - left_position; + + ( + Primitive::Quad { + bounds: Rectangle { + x: text_bounds.x + left_position, + y: text_bounds.y, + width, + height: text_bounds.height, + }, + background: Background::Color( + style_sheet.selection_color(), + ), + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + if end == right { + right_offset + } else { + left_offset + }, + ) + } + }; + + ( + Primitive::Group { + primitives: vec![cursor_primitive, text_value], + }, + Vector::new(offset as u32, 0), + ) + } else { + (text_value, Vector::new(0, 0)) + }; + + let text_width = self.measure_value( + if text.is_empty() { placeholder } else { &text }, + size, + font, + ); + + let contents = if text_width > text_bounds.width { + Primitive::Clip { + bounds: text_bounds, + offset, + content: Box::new(contents_primitive), + } + } else { + contents_primitive + }; + + ( + Primitive::Group { + primitives: vec![input, contents], + }, + if is_mouse_over { + mouse::Interaction::Text + } else { + mouse::Interaction::default() + }, + ) + } +} + +fn measure_cursor_and_scroll_offset( + renderer: &Renderer, + text_bounds: Rectangle, + value: &text_input::Value, + size: u16, + cursor_index: usize, + font: Font, +) -> (f32, f32) +where + B: Backend + backend::Text, +{ + use iced_native::text_input::Renderer; + + let text_before_cursor = value.until(cursor_index).to_string(); + + let text_value_width = + renderer.measure_value(&text_before_cursor, size, font); + let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0); + + (text_value_width, offset) +} From 720e7756f2afe30706b6b1a7fbde86b9f15e1d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 19 May 2020 22:55:12 +0200 Subject: [PATCH 10/40] Move `Layer` to `iced_graphics` --- glow/src/backend.rs | 291 ++++----------------------- glow/src/lib.rs | 1 - glow/src/quad.rs | 33 +-- glow/src/triangle.rs | 5 +- graphics/src/layer.rs | 266 +++++++++++++++++++++++++ graphics/src/lib.rs | 7 + graphics/src/primitive.rs | 6 +- graphics/src/triangle.rs | 1 + graphics/src/widget.rs | 4 +- graphics/src/widget/canvas/frame.rs | 2 +- graphics/src/widget/image.rs | 2 +- graphics/src/widget/svg.rs | 2 +- wgpu/src/backend.rs | 299 ++++------------------------ wgpu/src/image.rs | 57 +++--- wgpu/src/lib.rs | 1 - wgpu/src/quad.rs | 30 +-- wgpu/src/text.rs | 4 - wgpu/src/triangle.rs | 38 ++-- 18 files changed, 433 insertions(+), 616 deletions(-) create mode 100644 graphics/src/layer.rs diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 94683e56..fb6782e6 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -1,12 +1,13 @@ use crate::quad; use crate::text; use crate::triangle; -use crate::{Quad, Settings, Transformation, Viewport}; +use crate::{Settings, Transformation, Viewport}; use iced_graphics::backend; use iced_graphics::font; +use iced_graphics::Layer; use iced_graphics::Primitive; use iced_native::mouse; -use iced_native::{Background, Font, Point, Rectangle, Size, Vector}; +use iced_native::{Font, HorizontalAlignment, Size, VerticalAlignment}; /// A [`glow`] renderer. /// @@ -18,30 +19,6 @@ pub struct Backend { triangle_pipeline: triangle::Pipeline, } -struct Layer<'a> { - bounds: Rectangle, - quads: Vec, - text: Vec>, - meshes: Vec<(Vector, Rectangle, &'a triangle::Mesh2D)>, -} - -impl<'a> Layer<'a> { - pub fn new(bounds: Rectangle) -> Self { - Self { - bounds, - quads: Vec::new(), - text: Vec::new(), - meshes: Vec::new(), - } - } - - pub fn intersection(&self, rectangle: Rectangle) -> Option> { - let layer_bounds: Rectangle = self.bounds.into(); - - layer_bounds.intersection(&rectangle).map(Into::into) - } -} - impl Backend { /// Creates a new [`Renderer`]. /// @@ -71,23 +48,14 @@ impl Backend { viewport: &Viewport, (primitive, mouse_interaction): &(Primitive, mouse::Interaction), scale_factor: f64, - overlay: &[T], + overlay_text: &[T], ) -> mouse::Interaction { let (width, height) = viewport.dimensions(); let scale_factor = scale_factor as f32; let transformation = viewport.transformation(); - let mut layers = Vec::new(); - - layers.push(Layer::new(Rectangle { - x: 0, - y: 0, - width: (width as f32 / scale_factor).round() as u32, - height: (height as f32 / scale_factor).round() as u32, - })); - - self.draw_primitive(Vector::new(0.0, 0.0), primitive, &mut layers); - self.draw_overlay(overlay, &mut layers); + let mut layers = Layer::generate(primitive, viewport); + layers.push(Layer::overlay(overlay_text, viewport)); for layer in layers { self.flush( @@ -104,213 +72,6 @@ impl Backend { *mouse_interaction } - fn draw_primitive<'a>( - &mut self, - translation: Vector, - primitive: &'a Primitive, - layers: &mut Vec>, - ) { - match primitive { - Primitive::None => {} - Primitive::Group { primitives } => { - // TODO: Inspect a bit and regroup (?) - for primitive in primitives { - self.draw_primitive(translation, primitive, layers) - } - } - Primitive::Text { - content, - bounds, - size, - color, - font, - horizontal_alignment, - vertical_alignment, - } => { - let layer = layers.last_mut().unwrap(); - - layer.text.push(glow_glyph::Section { - text: &content, - screen_position: ( - bounds.x + translation.x, - bounds.y + translation.y, - ), - bounds: (bounds.width, bounds.height), - scale: glow_glyph::Scale { x: *size, y: *size }, - color: color.into_linear(), - font_id: self.text_pipeline.find_font(*font), - layout: glow_glyph::Layout::default() - .h_align(match horizontal_alignment { - iced_native::HorizontalAlignment::Left => { - glow_glyph::HorizontalAlign::Left - } - iced_native::HorizontalAlignment::Center => { - glow_glyph::HorizontalAlign::Center - } - iced_native::HorizontalAlignment::Right => { - glow_glyph::HorizontalAlign::Right - } - }) - .v_align(match vertical_alignment { - iced_native::VerticalAlignment::Top => { - glow_glyph::VerticalAlign::Top - } - iced_native::VerticalAlignment::Center => { - glow_glyph::VerticalAlign::Center - } - iced_native::VerticalAlignment::Bottom => { - glow_glyph::VerticalAlign::Bottom - } - }), - ..Default::default() - }) - } - Primitive::Quad { - bounds, - background, - border_radius, - border_width, - border_color, - } => { - let layer = layers.last_mut().unwrap(); - - // TODO: Move some of these computations to the GPU (?) - layer.quads.push(Quad { - position: [ - bounds.x + translation.x, - bounds.y + translation.y, - ], - scale: [bounds.width, bounds.height], - color: match background { - Background::Color(color) => color.into_linear(), - }, - border_radius: *border_radius as f32, - border_width: *border_width as f32, - border_color: border_color.into_linear(), - }); - } - Primitive::Mesh2D { size, buffers } => { - let layer = layers.last_mut().unwrap(); - - // Only draw visible content - if let Some(clip_bounds) = layer.intersection(Rectangle::new( - Point::new(translation.x, translation.y), - *size, - )) { - layer.meshes.push(( - translation, - clip_bounds.into(), - buffers, - )); - } - } - Primitive::Clip { - bounds, - offset, - content, - } => { - let layer = layers.last_mut().unwrap(); - - // Only draw visible content - if let Some(clip_bounds) = - layer.intersection(*bounds + translation) - { - let clip_layer = Layer::new(clip_bounds.into()); - let new_layer = Layer::new(layer.bounds); - - layers.push(clip_layer); - self.draw_primitive( - translation - - Vector::new(offset.x as f32, offset.y as f32), - content, - layers, - ); - layers.push(new_layer); - } - } - Primitive::Translate { - translation: new_translation, - content, - } => { - self.draw_primitive( - translation + *new_translation, - &content, - layers, - ); - } - - Primitive::Cached { cache } => { - self.draw_primitive(translation, &cache, layers); - } - - #[cfg(feature = "image")] - Primitive::Image { handle, bounds } => { - let layer = layers.last_mut().unwrap(); - - layer.images.push(Image { - handle: image::Handle::Raster(handle.clone()), - position: [ - bounds.x + translation.x, - bounds.y + translation.y, - ], - size: [bounds.width, bounds.height], - }); - } - #[cfg(not(feature = "image"))] - Primitive::Image { .. } => {} - - #[cfg(feature = "svg")] - Primitive::Svg { handle, bounds } => { - let layer = layers.last_mut().unwrap(); - - layer.images.push(Image { - handle: image::Handle::Vector(handle.clone()), - position: [ - bounds.x + translation.x, - bounds.y + translation.y, - ], - size: [bounds.width, bounds.height], - }); - } - #[cfg(not(feature = "svg"))] - Primitive::Svg { .. } => {} - } - } - - fn draw_overlay<'a, T: AsRef>( - &mut self, - lines: &'a [T], - layers: &mut Vec>, - ) { - let first = layers.first().unwrap(); - let mut overlay = Layer::new(first.bounds); - - let font_id = self.text_pipeline.overlay_font(); - let scale = glow_glyph::Scale { x: 20.0, y: 20.0 }; - - for (i, line) in lines.iter().enumerate() { - overlay.text.push(glow_glyph::Section { - text: line.as_ref(), - screen_position: (11.0, 11.0 + 25.0 * i as f32), - color: [0.9, 0.9, 0.9, 1.0], - scale, - font_id, - ..glow_glyph::Section::default() - }); - - overlay.text.push(glow_glyph::Section { - text: line.as_ref(), - screen_position: (10.0, 10.0 + 25.0 * i as f32), - color: [0.0, 0.0, 0.0, 1.0], - scale, - font_id, - ..glow_glyph::Section::default() - }); - } - - layers.push(overlay); - } - fn flush( &mut self, gl: &glow::Context, @@ -352,13 +113,14 @@ impl Backend { for text in layer.text.iter() { // Target physical coordinates directly to avoid blurry text let text = glow_glyph::Section { + text: text.content, // TODO: We `round` here to avoid rerasterizing text when // its position changes slightly. This can make text feel a // bit "jumpy". We may be able to do better once we improve // our text rendering/caching pipeline. screen_position: ( - (text.screen_position.0 * scale_factor).round(), - (text.screen_position.1 * scale_factor).round(), + (text.bounds.x * scale_factor).round(), + (text.bounds.y * scale_factor).round(), ), // TODO: Fix precision issues with some scale factors. // @@ -370,14 +132,39 @@ impl Backend { // scaling when rendering. This would ensure that both // measuring and rendering follow the same layout rules. bounds: ( - (text.bounds.0 * scale_factor).ceil(), - (text.bounds.1 * scale_factor).ceil(), + (text.bounds.width * scale_factor).ceil(), + (text.bounds.height * scale_factor).ceil(), ), scale: glow_glyph::Scale { - x: text.scale.x * scale_factor, - y: text.scale.y * scale_factor, + x: text.size * scale_factor, + y: text.size * scale_factor, }, - ..*text + color: text.color, + font_id: self.text_pipeline.find_font(text.font), + layout: glow_glyph::Layout::default() + .h_align(match text.horizontal_alignment { + HorizontalAlignment::Left => { + glow_glyph::HorizontalAlign::Left + } + HorizontalAlignment::Center => { + glow_glyph::HorizontalAlign::Center + } + HorizontalAlignment::Right => { + glow_glyph::HorizontalAlign::Right + } + }) + .v_align(match text.vertical_alignment { + VerticalAlignment::Top => { + glow_glyph::VerticalAlign::Top + } + VerticalAlignment::Center => { + glow_glyph::VerticalAlign::Center + } + VerticalAlignment::Bottom => { + glow_glyph::VerticalAlign::Bottom + } + }), + ..Default::default() }; self.text_pipeline.queue(text); diff --git a/glow/src/lib.rs b/glow/src/lib.rs index 724065bd..d40ed0ae 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -17,7 +17,6 @@ pub use settings::Settings; pub(crate) use backend::Backend; pub(crate) use iced_graphics::Transformation; -pub(crate) use quad::Quad; pub type Renderer = iced_graphics::Renderer; diff --git a/glow/src/quad.rs b/glow/src/quad.rs index 744597d2..26424b39 100644 --- a/glow/src/quad.rs +++ b/glow/src/quad.rs @@ -1,7 +1,10 @@ use crate::{Transformation, Viewport}; use glow::HasContext; +use iced_graphics::layer; use iced_native::Rectangle; +const MAX_INSTANCES: usize = 100_000; + #[derive(Debug)] pub struct Pipeline { program: ::Program, @@ -37,7 +40,7 @@ impl Pipeline { } let (vertex_array, instances) = - unsafe { create_instance_buffer(gl, Quad::MAX) }; + unsafe { create_instance_buffer(gl, MAX_INSTANCES) }; Pipeline { program, @@ -52,7 +55,7 @@ impl Pipeline { &mut self, gl: &glow::Context, viewport: &Viewport, - instances: &[Quad], + instances: &[layer::Quad], transformation: Transformation, scale: f32, bounds: Rectangle, @@ -97,7 +100,7 @@ impl Pipeline { let total = instances.len(); while i < total { - let end = (i + Quad::MAX).min(total); + let end = (i + MAX_INSTANCES).min(total); let amount = end - i; unsafe { @@ -115,7 +118,7 @@ impl Pipeline { ); } - i += Quad::MAX; + i += MAX_INSTANCES; } unsafe { @@ -126,24 +129,6 @@ impl Pipeline { } } -#[derive(Debug, Clone, Copy)] -#[repr(C)] -pub struct Quad { - pub position: [f32; 2], - pub scale: [f32; 2], - pub color: [f32; 4], - pub border_color: [f32; 4], - pub border_radius: f32, - pub border_width: f32, -} - -unsafe impl bytemuck::Zeroable for Quad {} -unsafe impl bytemuck::Pod for Quad {} - -impl Quad { - const MAX: usize = 100_000; -} - unsafe fn create_program( gl: &glow::Context, shader_sources: &[(u32, &str)], @@ -196,11 +181,11 @@ unsafe fn create_instance_buffer( gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer)); gl.buffer_data_size( glow::ARRAY_BUFFER, - (size * std::mem::size_of::()) as i32, + (size * std::mem::size_of::()) as i32, glow::DYNAMIC_DRAW, ); - let stride = std::mem::size_of::() as i32; + let stride = std::mem::size_of::() as i32; gl.enable_vertex_attrib_array(0); gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0); diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs index 8b21c0a8..5836f0cf 100644 --- a/glow/src/triangle.rs +++ b/glow/src/triangle.rs @@ -1,7 +1,6 @@ //! Draw meshes of triangles. use crate::{settings, Transformation}; -use iced_native::{Rectangle, Vector}; -use std::mem; +use iced_graphics::layer; pub use iced_graphics::triangle::Mesh2D; @@ -27,7 +26,7 @@ impl Pipeline { target_height: u32, transformation: Transformation, scale_factor: f32, - meshes: &[(Vector, Rectangle, &Mesh2D)], + meshes: &[layer::Mesh<'_>], ) { } } diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs new file mode 100644 index 00000000..ae9c6ce0 --- /dev/null +++ b/graphics/src/layer.rs @@ -0,0 +1,266 @@ +use crate::image; +use crate::svg; +use crate::triangle; +use crate::{ + Background, Font, HorizontalAlignment, Point, Primitive, Rectangle, Size, + Vector, VerticalAlignment, Viewport, +}; + +pub struct Layer<'a> { + pub bounds: Rectangle, + pub quads: Vec, + pub meshes: Vec>, + pub text: Vec>, + pub images: Vec, +} + +impl<'a> Layer<'a> { + pub fn new(bounds: Rectangle) -> Self { + Self { + bounds, + quads: Vec::new(), + meshes: Vec::new(), + text: Vec::new(), + images: Vec::new(), + } + } + + pub fn overlay(lines: &'a [impl AsRef], viewport: &Viewport) -> Self { + let (width, height) = viewport.dimensions(); + + let mut overlay = Layer::new(Rectangle { + x: 0, + y: 0, + width, + height, + }); + + for (i, line) in lines.iter().enumerate() { + let text = Text { + content: line.as_ref(), + bounds: Rectangle::new( + Point::new(11.0, 11.0 + 25.0 * i as f32), + Size::INFINITY, + ), + color: [0.9, 0.9, 0.9, 1.0], + size: 20.0, + font: Font::Default, + horizontal_alignment: HorizontalAlignment::Left, + vertical_alignment: VerticalAlignment::Top, + }; + + overlay.text.push(text); + + overlay.text.push(Text { + bounds: text.bounds + Vector::new(-1.0, -1.0), + color: [0.0, 0.0, 0.0, 1.0], + ..text + }); + } + + overlay + } + + pub(crate) fn intersection( + &self, + rectangle: Rectangle, + ) -> Option> { + let layer_bounds: Rectangle = self.bounds.into(); + + layer_bounds.intersection(&rectangle).map(Into::into) + } + + pub fn generate( + primitive: &'a Primitive, + viewport: &Viewport, + ) -> Vec { + let mut layers = Vec::new(); + let (width, height) = viewport.dimensions(); + + layers.push(Layer::new(Rectangle { + x: 0, + y: 0, + width, + height, + })); + + Self::process_primitive(&mut layers, Vector::new(0.0, 0.0), primitive); + + layers + } + + fn process_primitive( + layers: &mut Vec, + translation: Vector, + primitive: &'a Primitive, + ) { + match primitive { + Primitive::None => {} + Primitive::Group { primitives } => { + // TODO: Inspect a bit and regroup (?) + for primitive in primitives { + Self::process_primitive(layers, translation, primitive) + } + } + Primitive::Text { + content, + bounds, + size, + color, + font, + horizontal_alignment, + vertical_alignment, + } => { + let layer = layers.last_mut().unwrap(); + + layer.text.push(Text { + content, + bounds: *bounds + translation, + size: *size, + color: color.into_linear(), + font: *font, + horizontal_alignment: *horizontal_alignment, + vertical_alignment: *vertical_alignment, + }); + } + Primitive::Quad { + bounds, + background, + border_radius, + border_width, + border_color, + } => { + let layer = layers.last_mut().unwrap(); + + // TODO: Move some of these computations to the GPU (?) + layer.quads.push(Quad { + position: [ + bounds.x + translation.x, + bounds.y + translation.y, + ], + scale: [bounds.width, bounds.height], + color: match background { + Background::Color(color) => color.into_linear(), + }, + border_radius: *border_radius as f32, + border_width: *border_width as f32, + border_color: border_color.into_linear(), + }); + } + Primitive::Mesh2D { buffers, size } => { + let layer = layers.last_mut().unwrap(); + + let bounds = Rectangle::new( + Point::new(translation.x, translation.y), + *size, + ); + + // Only draw visible content + if let Some(clip_bounds) = layer.intersection(bounds) { + layer.meshes.push(Mesh { + origin: Point::new(translation.x, translation.y), + buffers, + clip_bounds: clip_bounds.into(), + }); + } + } + Primitive::Clip { + bounds, + offset, + content, + } => { + let layer = layers.last_mut().unwrap(); + + // Only draw visible content + if let Some(clip_bounds) = + layer.intersection(*bounds + translation) + { + let clip_layer = Layer::new(clip_bounds.into()); + let new_layer = Layer::new(layer.bounds); + + layers.push(clip_layer); + Self::process_primitive( + layers, + translation + - Vector::new(offset.x as f32, offset.y as f32), + content, + ); + layers.push(new_layer); + } + } + Primitive::Translate { + translation: new_translation, + content, + } => { + Self::process_primitive( + layers, + translation + *new_translation, + &content, + ); + } + Primitive::Cached { cache } => { + Self::process_primitive(layers, translation, &cache); + } + Primitive::Image { handle, bounds } => { + let layer = layers.last_mut().unwrap(); + + layer.images.push(Image::Raster { + handle: handle.clone(), + bounds: *bounds + translation, + }); + } + Primitive::Svg { handle, bounds } => { + let layer = layers.last_mut().unwrap(); + + layer.images.push(Image::Vector { + handle: handle.clone(), + bounds: *bounds + translation, + }); + } + } + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct Quad { + pub position: [f32; 2], + pub scale: [f32; 2], + pub color: [f32; 4], + pub border_color: [f32; 4], + pub border_radius: f32, + pub border_width: f32, +} + +#[derive(Debug, Clone, Copy)] +pub struct Mesh<'a> { + pub origin: Point, + pub buffers: &'a triangle::Mesh2D, + pub clip_bounds: Rectangle, +} + +#[derive(Debug, Clone, Copy)] +pub struct Text<'a> { + pub content: &'a str, + pub bounds: Rectangle, + pub color: [f32; 4], + pub size: f32, + pub font: Font, + pub horizontal_alignment: HorizontalAlignment, + pub vertical_alignment: VerticalAlignment, +} + +#[derive(Debug, Clone)] +pub enum Image { + Raster { + handle: image::Handle, + bounds: Rectangle, + }, + Vector { + handle: svg::Handle, + bounds: Rectangle, + }, +} + +unsafe impl bytemuck::Zeroable for Quad {} +unsafe impl bytemuck::Pod for Quad {} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 5dc4040f..5ab333c7 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -8,6 +8,7 @@ mod widget; pub mod backend; pub mod font; +pub mod layer; pub mod triangle; #[doc(no_inline)] @@ -16,7 +17,13 @@ pub use widget::*; pub use antialiasing::Antialiasing; pub use backend::Backend; pub use defaults::Defaults; +pub use layer::Layer; pub use primitive::Primitive; pub use renderer::Renderer; pub use transformation::Transformation; pub use viewport::Viewport; + +pub use iced_native::{ + Background, Font, HorizontalAlignment, Point, Rectangle, Size, Vector, + VerticalAlignment, +}; diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index e73227ef..95dbf7dd 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -82,13 +82,13 @@ pub enum Primitive { /// /// It can be used to render many kinds of geometry freely. Mesh2D { + /// The vertex and index buffers of the mesh + buffers: triangle::Mesh2D, + /// The size of the drawable region of the mesh. /// /// Any geometry that falls out of this region will be clipped. size: Size, - - /// The vertex and index buffers of the mesh - buffers: triangle::Mesh2D, }, /// A cached primitive. /// diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs index 2b157e3a..474f69b8 100644 --- a/graphics/src/triangle.rs +++ b/graphics/src/triangle.rs @@ -5,6 +5,7 @@ pub struct Mesh2D { /// The vertices of the mesh pub vertices: Vec, + /// The list of vertex indices that defines the triangles of the mesh. /// /// Therefore, this list should always have a length that is a multiple of diff --git a/graphics/src/widget.rs b/graphics/src/widget.rs index d9c91d77..1f6d6559 100644 --- a/graphics/src/widget.rs +++ b/graphics/src/widget.rs @@ -10,18 +10,18 @@ pub mod button; pub mod checkbox; pub mod container; +pub mod image; pub mod pane_grid; pub mod progress_bar; pub mod radio; pub mod scrollable; pub mod slider; +pub mod svg; pub mod text_input; mod column; -mod image; mod row; mod space; -mod svg; mod text; #[doc(no_inline)] diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs index 5262ab4e..48d28d95 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -304,11 +304,11 @@ impl Frame { pub fn into_geometry(mut self) -> Geometry { if !self.buffers.indices.is_empty() { self.primitives.push(Primitive::Mesh2D { - size: self.size, buffers: triangle::Mesh2D { vertices: self.buffers.vertices, indices: self.buffers.indices, }, + size: self.size, }); } diff --git a/graphics/src/widget/image.rs b/graphics/src/widget/image.rs index 79d82cb1..ea49febe 100644 --- a/graphics/src/widget/image.rs +++ b/graphics/src/widget/image.rs @@ -4,7 +4,7 @@ use iced_native::image; use iced_native::mouse; use iced_native::Layout; -pub use iced_native::Image; +pub use iced_native::image::{Handle, Image}; impl image::Renderer for Renderer where diff --git a/graphics/src/widget/svg.rs b/graphics/src/widget/svg.rs index b1aa7cea..8c681478 100644 --- a/graphics/src/widget/svg.rs +++ b/graphics/src/widget/svg.rs @@ -2,7 +2,7 @@ use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::{mouse, svg, Layout}; -pub use iced_native::Svg; +pub use iced_native::svg::{Handle, Svg}; impl svg::Renderer for Renderer where diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 073a79e2..82e4c4b2 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,15 +1,16 @@ use crate::quad; use crate::text; use crate::triangle; -use crate::{Quad, Settings, Target, Transformation}; +use crate::{Settings, Target, Transformation}; use iced_graphics::backend; use iced_graphics::font; +use iced_graphics::layer::Layer; use iced_graphics::Primitive; use iced_native::mouse; -use iced_native::{Background, Font, Point, Rectangle, Size, Vector}; +use iced_native::{Font, HorizontalAlignment, Size, VerticalAlignment}; #[cfg(any(feature = "image", feature = "svg"))] -use crate::image::{self, Image}; +use crate::image; /// A [`wgpu`] renderer. /// @@ -24,36 +25,6 @@ pub struct Backend { image_pipeline: image::Pipeline, } -struct Layer<'a> { - bounds: Rectangle, - quads: Vec, - meshes: Vec<(Vector, Rectangle, &'a triangle::Mesh2D)>, - text: Vec>, - - #[cfg(any(feature = "image", feature = "svg"))] - images: Vec, -} - -impl<'a> Layer<'a> { - pub fn new(bounds: Rectangle) -> Self { - Self { - bounds, - quads: Vec::new(), - text: Vec::new(), - meshes: Vec::new(), - - #[cfg(any(feature = "image", feature = "svg"))] - images: Vec::new(), - } - } - - pub fn intersection(&self, rectangle: Rectangle) -> Option> { - let layer_bounds: Rectangle = self.bounds.into(); - - layer_bounds.intersection(&rectangle).map(Into::into) - } -} - impl Backend { /// Creates a new [`Renderer`]. /// @@ -94,7 +65,7 @@ impl Backend { target: Target<'_>, (primitive, mouse_interaction): &(Primitive, mouse::Interaction), scale_factor: f64, - overlay: &[T], + overlay_text: &[T], ) -> mouse::Interaction { log::debug!("Drawing"); @@ -102,17 +73,8 @@ impl Backend { let scale_factor = scale_factor as f32; let transformation = target.viewport.transformation(); - let mut layers = Vec::new(); - - layers.push(Layer::new(Rectangle { - x: 0, - y: 0, - width, - height, - })); - - self.draw_primitive(Vector::new(0.0, 0.0), primitive, &mut layers); - self.draw_overlay(overlay, &mut layers); + let mut layers = Layer::generate(primitive, &target.viewport); + layers.push(Layer::overlay(overlay_text, &target.viewport)); for layer in layers { self.flush( @@ -133,213 +95,6 @@ impl Backend { *mouse_interaction } - fn draw_primitive<'a>( - &mut self, - translation: Vector, - primitive: &'a Primitive, - layers: &mut Vec>, - ) { - match primitive { - Primitive::None => {} - Primitive::Group { primitives } => { - // TODO: Inspect a bit and regroup (?) - for primitive in primitives { - self.draw_primitive(translation, primitive, layers) - } - } - Primitive::Text { - content, - bounds, - size, - color, - font, - horizontal_alignment, - vertical_alignment, - } => { - let layer = layers.last_mut().unwrap(); - - layer.text.push(wgpu_glyph::Section { - text: &content, - screen_position: ( - bounds.x + translation.x, - bounds.y + translation.y, - ), - bounds: (bounds.width, bounds.height), - scale: wgpu_glyph::Scale { x: *size, y: *size }, - color: color.into_linear(), - font_id: self.text_pipeline.find_font(*font), - layout: wgpu_glyph::Layout::default() - .h_align(match horizontal_alignment { - iced_native::HorizontalAlignment::Left => { - wgpu_glyph::HorizontalAlign::Left - } - iced_native::HorizontalAlignment::Center => { - wgpu_glyph::HorizontalAlign::Center - } - iced_native::HorizontalAlignment::Right => { - wgpu_glyph::HorizontalAlign::Right - } - }) - .v_align(match vertical_alignment { - iced_native::VerticalAlignment::Top => { - wgpu_glyph::VerticalAlign::Top - } - iced_native::VerticalAlignment::Center => { - wgpu_glyph::VerticalAlign::Center - } - iced_native::VerticalAlignment::Bottom => { - wgpu_glyph::VerticalAlign::Bottom - } - }), - ..Default::default() - }) - } - Primitive::Quad { - bounds, - background, - border_radius, - border_width, - border_color, - } => { - let layer = layers.last_mut().unwrap(); - - // TODO: Move some of these computations to the GPU (?) - layer.quads.push(Quad { - position: [ - bounds.x + translation.x, - bounds.y + translation.y, - ], - scale: [bounds.width, bounds.height], - color: match background { - Background::Color(color) => color.into_linear(), - }, - border_radius: *border_radius as f32, - border_width: *border_width as f32, - border_color: border_color.into_linear(), - }); - } - Primitive::Mesh2D { size, buffers } => { - let layer = layers.last_mut().unwrap(); - - // Only draw visible content - if let Some(clip_bounds) = layer.intersection(Rectangle::new( - Point::new(translation.x, translation.y), - *size, - )) { - layer.meshes.push(( - translation, - clip_bounds.into(), - buffers, - )); - } - } - Primitive::Clip { - bounds, - offset, - content, - } => { - let layer = layers.last_mut().unwrap(); - - // Only draw visible content - if let Some(clip_bounds) = - layer.intersection(*bounds + translation) - { - let clip_layer = Layer::new(clip_bounds.into()); - let new_layer = Layer::new(layer.bounds); - - layers.push(clip_layer); - self.draw_primitive( - translation - - Vector::new(offset.x as f32, offset.y as f32), - content, - layers, - ); - layers.push(new_layer); - } - } - Primitive::Translate { - translation: new_translation, - content, - } => { - self.draw_primitive( - translation + *new_translation, - &content, - layers, - ); - } - - Primitive::Cached { cache } => { - self.draw_primitive(translation, &cache, layers); - } - - #[cfg(feature = "image")] - Primitive::Image { handle, bounds } => { - let layer = layers.last_mut().unwrap(); - - layer.images.push(Image { - handle: image::Handle::Raster(handle.clone()), - position: [ - bounds.x + translation.x, - bounds.y + translation.y, - ], - size: [bounds.width, bounds.height], - }); - } - #[cfg(not(feature = "image"))] - Primitive::Image { .. } => {} - - #[cfg(feature = "svg")] - Primitive::Svg { handle, bounds } => { - let layer = layers.last_mut().unwrap(); - - layer.images.push(Image { - handle: image::Handle::Vector(handle.clone()), - position: [ - bounds.x + translation.x, - bounds.y + translation.y, - ], - size: [bounds.width, bounds.height], - }); - } - #[cfg(not(feature = "svg"))] - Primitive::Svg { .. } => {} - } - } - - fn draw_overlay<'a, T: AsRef>( - &mut self, - lines: &'a [T], - layers: &mut Vec>, - ) { - let first = layers.first().unwrap(); - let mut overlay = Layer::new(first.bounds); - - let font_id = self.text_pipeline.overlay_font(); - let scale = wgpu_glyph::Scale { x: 20.0, y: 20.0 }; - - for (i, line) in lines.iter().enumerate() { - overlay.text.push(wgpu_glyph::Section { - text: line.as_ref(), - screen_position: (11.0, 11.0 + 25.0 * i as f32), - color: [0.9, 0.9, 0.9, 1.0], - scale, - font_id, - ..wgpu_glyph::Section::default() - }); - - overlay.text.push(wgpu_glyph::Section { - text: line.as_ref(), - screen_position: (10.0, 10.0 + 25.0 * i as f32), - color: [0.0, 0.0, 0.0, 1.0], - scale, - font_id, - ..wgpu_glyph::Section::default() - }); - } - - layers.push(overlay); - } - fn flush( &mut self, device: &wgpu::Device, @@ -403,13 +158,14 @@ impl Backend { for text in layer.text.iter() { // Target physical coordinates directly to avoid blurry text let text = wgpu_glyph::Section { + text: text.content, // TODO: We `round` here to avoid rerasterizing text when // its position changes slightly. This can make text feel a // bit "jumpy". We may be able to do better once we improve // our text rendering/caching pipeline. screen_position: ( - (text.screen_position.0 * scale_factor).round(), - (text.screen_position.1 * scale_factor).round(), + (text.bounds.x * scale_factor).round(), + (text.bounds.y * scale_factor).round(), ), // TODO: Fix precision issues with some scale factors. // @@ -421,14 +177,39 @@ impl Backend { // scaling when rendering. This would ensure that both // measuring and rendering follow the same layout rules. bounds: ( - (text.bounds.0 * scale_factor).ceil(), - (text.bounds.1 * scale_factor).ceil(), + (text.bounds.width * scale_factor).ceil(), + (text.bounds.height * scale_factor).ceil(), ), scale: wgpu_glyph::Scale { - x: text.scale.x * scale_factor, - y: text.scale.y * scale_factor, + x: text.size * scale_factor, + y: text.size * scale_factor, }, - ..*text + color: text.color, + font_id: self.text_pipeline.find_font(text.font), + layout: wgpu_glyph::Layout::default() + .h_align(match text.horizontal_alignment { + HorizontalAlignment::Left => { + wgpu_glyph::HorizontalAlign::Left + } + HorizontalAlignment::Center => { + wgpu_glyph::HorizontalAlign::Center + } + HorizontalAlignment::Right => { + wgpu_glyph::HorizontalAlign::Right + } + }) + .v_align(match text.vertical_alignment { + VerticalAlignment::Top => { + wgpu_glyph::VerticalAlign::Top + } + VerticalAlignment::Center => { + wgpu_glyph::VerticalAlign::Center + } + VerticalAlignment::Bottom => { + wgpu_glyph::VerticalAlign::Bottom + } + }), + ..Default::default() }; self.text_pipeline.queue(text); diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index ea5dc09d..49f1d29c 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -9,6 +9,7 @@ mod vector; use crate::Transformation; use atlas::Atlas; +use iced_graphics::layer; use iced_native::Rectangle; use std::cell::RefCell; use std::mem; @@ -282,7 +283,7 @@ impl Pipeline { &mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, - images: &[Image], + images: &[layer::Image], transformation: Transformation, bounds: Rectangle, target: &wgpu::TextureView, @@ -297,31 +298,48 @@ impl Pipeline { let mut vector_cache = self.vector_cache.borrow_mut(); for image in images { - match &image.handle { + match &image { #[cfg(feature = "image")] - Handle::Raster(handle) => { + layer::Image::Raster { handle, bounds } => { if let Some(atlas_entry) = raster_cache.upload( handle, device, encoder, &mut self.texture_atlas, ) { - add_instances(image, atlas_entry, instances); + add_instances( + [bounds.x, bounds.y], + [bounds.width, bounds.height], + atlas_entry, + instances, + ); } } + #[cfg(not(feature = "image"))] + layer::Image::Raster { .. } => {} + #[cfg(feature = "svg")] - Handle::Vector(handle) => { + layer::Image::Vector { handle, bounds } => { + let size = [bounds.width, bounds.height]; + if let Some(atlas_entry) = vector_cache.upload( handle, - image.size, + size, _scale, device, encoder, &mut self.texture_atlas, ) { - add_instances(image, atlas_entry, instances); + add_instances( + [bounds.x, bounds.y], + size, + atlas_entry, + instances, + ); } } + #[cfg(not(feature = "svg"))] + layer::Image::Vector { .. } => {} } } @@ -437,20 +455,6 @@ impl Pipeline { } } -pub struct Image { - pub handle: Handle, - pub position: [f32; 2], - pub size: [f32; 2], -} - -pub enum Handle { - #[cfg(feature = "image")] - Raster(image::Handle), - - #[cfg(feature = "svg")] - Vector(svg::Handle), -} - #[repr(C)] #[derive(Clone, Copy, AsBytes)] pub struct Vertex { @@ -495,22 +499,23 @@ struct Uniforms { } fn add_instances( - image: &Image, + image_position: [f32; 2], + image_size: [f32; 2], entry: &atlas::Entry, instances: &mut Vec, ) { match entry { atlas::Entry::Contiguous(allocation) => { - add_instance(image.position, image.size, allocation, instances); + add_instance(image_position, image_size, allocation, instances); } atlas::Entry::Fragmented { fragments, size } => { - let scaling_x = image.size[0] / size.0 as f32; - let scaling_y = image.size[1] / size.1 as f32; + let scaling_x = image_size[0] / size.0 as f32; + let scaling_y = image_size[1] / size.1 as f32; for fragment in fragments { let allocation = &fragment.allocation; - let [x, y] = image.position; + let [x, y] = image_position; let (fragment_x, fragment_y) = fragment.position; let (fragment_width, fragment_height) = allocation.size(); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 99897e60..74007eae 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -48,7 +48,6 @@ pub use target::Target; pub use widget::*; pub(crate) use iced_graphics::Transformation; -pub(crate) use quad::Quad; pub type Renderer = iced_graphics::Renderer; diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 0c2d2244..0b62f44f 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,4 +1,5 @@ use crate::Transformation; +use iced_graphics::layer; use iced_native::Rectangle; use std::mem; @@ -107,7 +108,7 @@ impl Pipeline { }], }, wgpu::VertexBufferDescriptor { - stride: mem::size_of::() as u64, + stride: mem::size_of::() as u64, step_mode: wgpu::InputStepMode::Instance, attributes: &[ wgpu::VertexAttributeDescriptor { @@ -161,7 +162,7 @@ impl Pipeline { let instances = device.create_buffer(&wgpu::BufferDescriptor { label: None, - size: mem::size_of::() as u64 * Quad::MAX as u64, + size: mem::size_of::() as u64 * MAX_INSTANCES as u64, usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, }); @@ -179,7 +180,7 @@ impl Pipeline { &mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, - instances: &[Quad], + instances: &[layer::Quad], transformation: Transformation, scale: f32, bounds: Rectangle, @@ -204,11 +205,11 @@ impl Pipeline { let total = instances.len(); while i < total { - let end = (i + Quad::MAX).min(total); + let end = (i + MAX_INSTANCES).min(total); let amount = end - i; let instance_buffer = device.create_buffer_with_data( - &instances[i..end].as_bytes(), + bytemuck::cast_slice(&instances[i..end]), wgpu::BufferUsage::COPY_SRC, ); @@ -217,7 +218,7 @@ impl Pipeline { 0, &self.instances, 0, - (mem::size_of::() * amount) as u64, + (mem::size_of::() * amount) as u64, ); { @@ -260,7 +261,7 @@ impl Pipeline { ); } - i += Quad::MAX; + i += MAX_INSTANCES; } } } @@ -288,20 +289,7 @@ const QUAD_VERTS: [Vertex; 4] = [ }, ]; -#[repr(C)] -#[derive(Debug, Clone, Copy, AsBytes)] -pub struct Quad { - pub position: [f32; 2], - pub scale: [f32; 2], - pub color: [f32; 4], - pub border_color: [f32; 4], - pub border_radius: f32, - pub border_width: f32, -} - -impl Quad { - const MAX: usize = 100_000; -} +const MAX_INSTANCES: usize = 100_000; #[repr(C)] #[derive(Debug, Clone, Copy, AsBytes)] diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index c1782fe5..17d3f6dd 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -57,10 +57,6 @@ impl Pipeline { } } - pub fn overlay_font(&self) -> wgpu_glyph::FontId { - wgpu_glyph::FontId(0) - } - pub fn queue(&mut self, section: wgpu_glyph::Section<'_>) { self.draw_brush.borrow_mut().queue(section); } diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index a71bcedc..31975005 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -1,6 +1,6 @@ //! Draw meshes of triangles. use crate::{settings, Transformation}; -use iced_native::{Rectangle, Vector}; +use iced_graphics::layer; use std::mem; use zerocopy::AsBytes; @@ -204,14 +204,16 @@ impl Pipeline { target_height: u32, transformation: Transformation, scale_factor: f32, - meshes: &[(Vector, Rectangle, &Mesh2D)], + meshes: &[layer::Mesh<'_>], ) { // This looks a bit crazy, but we are just counting how many vertices // and indices we will need to handle. // TODO: Improve readability let (total_vertices, total_indices) = meshes .iter() - .map(|(_, _, mesh)| (mesh.vertices.len(), mesh.indices.len())) + .map(|layer::Mesh { buffers, .. }| { + (buffers.vertices.len(), buffers.indices.len()) + }) .fold((0, 0), |(total_v, total_i), (v, i)| { (total_v + v, total_i + i) }); @@ -232,18 +234,18 @@ impl Pipeline { let mut last_index = 0; // We upload everything upfront - for (origin, _, mesh) in meshes { + for mesh in meshes { let transform = (transformation - * Transformation::translate(origin.x, origin.y)) + * Transformation::translate(mesh.origin.x, mesh.origin.y)) .into(); let vertex_buffer = device.create_buffer_with_data( - bytemuck::cast_slice(&mesh.vertices), + bytemuck::cast_slice(&mesh.buffers.vertices), wgpu::BufferUsage::COPY_SRC, ); let index_buffer = device.create_buffer_with_data( - mesh.indices.as_bytes(), + mesh.buffers.indices.as_bytes(), wgpu::BufferUsage::COPY_SRC, ); @@ -252,7 +254,8 @@ impl Pipeline { 0, &self.vertex_buffer.raw, (std::mem::size_of::() * last_vertex) as u64, - (std::mem::size_of::() * mesh.vertices.len()) as u64, + (std::mem::size_of::() * mesh.buffers.vertices.len()) + as u64, ); encoder.copy_buffer_to_buffer( @@ -260,18 +263,19 @@ impl Pipeline { 0, &self.index_buffer.raw, (std::mem::size_of::() * last_index) as u64, - (std::mem::size_of::() * mesh.indices.len()) as u64, + (std::mem::size_of::() * mesh.buffers.indices.len()) + as u64, ); uniforms.push(transform); offsets.push(( last_vertex as u64, last_index as u64, - mesh.indices.len(), + mesh.buffers.indices.len(), )); - last_vertex += mesh.vertices.len(); - last_index += mesh.indices.len(); + last_vertex += mesh.buffers.vertices.len(); + last_index += mesh.buffers.indices.len(); } let uniforms_buffer = device.create_buffer_with_data( @@ -322,13 +326,13 @@ impl Pipeline { for (i, (vertex_offset, index_offset, indices)) in offsets.into_iter().enumerate() { - let bounds = meshes[i].1 * scale_factor; + let clip_bounds = meshes[i].clip_bounds * scale_factor; render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, + clip_bounds.x, + clip_bounds.y, + clip_bounds.width, + clip_bounds.height, ); render_pass.set_bind_group( From a1a5fcfd46622d5b18d1716aa2adb4659835ccf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 20 May 2020 20:28:35 +0200 Subject: [PATCH 11/40] Refactor `Viewport` and `Compositor` --- core/src/rectangle.rs | 33 +++++----- core/src/size.rs | 22 ++++--- examples/integration/src/main.rs | 57 +++++++++------- glow/src/backend.rs | 23 +++---- glow/src/quad.rs | 8 +-- glow/src/window/compositor.rs | 16 ++--- graphics/Cargo.toml | 1 + graphics/src/layer.rs | 45 ++++--------- graphics/src/lib.rs | 1 + graphics/src/viewport.rs | 47 +++++++++----- graphics/src/window.rs | 3 + {native => graphics}/src/window/compositor.rs | 10 +-- native/Cargo.toml | 1 - native/src/window.rs | 2 - wgpu/src/backend.rs | 26 ++++---- wgpu/src/lib.rs | 2 - wgpu/src/target.rs | 14 ---- wgpu/src/triangle.rs | 3 +- wgpu/src/window.rs | 2 - wgpu/src/window/compositor.rs | 33 ++++++---- wgpu/src/window/swap_chain.rs | 61 ----------------- winit/Cargo.toml | 4 ++ winit/src/application.rs | 65 +++++++++---------- winit/src/lib.rs | 1 - 24 files changed, 202 insertions(+), 278 deletions(-) create mode 100644 graphics/src/window.rs rename {native => graphics}/src/window/compositor.rs (89%) delete mode 100644 wgpu/src/target.rs delete mode 100644 wgpu/src/window/swap_chain.rs diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 8bc89a44..aa23372e 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -125,17 +125,29 @@ impl Rectangle { None } } + + /// Rounds the [`Rectangle`] to __unsigned__ integer coordinates. + /// + /// [`Rectangle`]: struct.Rectangle.html + pub fn round(self) -> Rectangle { + Rectangle { + x: self.x as u32, + y: self.y as u32, + width: (self.width + 0.5).round() as u32, + height: (self.height + 0.5).round() as u32, + } + } } -impl std::ops::Mul for Rectangle { +impl std::ops::Mul for Rectangle { type Output = Self; fn mul(self, scale: f32) -> Self { Self { - x: (self.x as f32 * scale).round() as u32, - y: (self.y as f32 * scale).round() as u32, - width: (self.width as f32 * scale).round() as u32, - height: (self.height as f32 * scale).round() as u32, + x: self.x as f32 * scale, + y: self.y as f32 * scale, + width: self.width * scale, + height: self.height * scale, } } } @@ -151,17 +163,6 @@ impl From> for Rectangle { } } -impl From> for Rectangle { - fn from(rectangle: Rectangle) -> Rectangle { - Rectangle { - x: rectangle.x as u32, - y: rectangle.y as u32, - width: (rectangle.width + 0.5).round() as u32, - height: (rectangle.height + 0.5).round() as u32, - } - } -} - impl std::ops::Add> for Rectangle where T: std::ops::Add, diff --git a/core/src/size.rs b/core/src/size.rs index a02299e8..aceb5311 100644 --- a/core/src/size.rs +++ b/core/src/size.rs @@ -2,11 +2,20 @@ use std::f32; /// An amount of space in 2 dimensions. #[derive(Debug, Clone, Copy, PartialEq)] -pub struct Size { +pub struct Size { /// The width. - pub width: f32, + pub width: T, /// The height. - pub height: f32, + pub height: T, +} + +impl Size { + /// Creates a new [`Size`] with the given width and height. + /// + /// [`Size`]: struct.Size.html + pub const fn new(width: T, height: T) -> Self { + Size { width, height } + } } impl Size { @@ -25,13 +34,6 @@ impl Size { /// [`Size`]: struct.Size.html pub const INFINITY: Size = Size::new(f32::INFINITY, f32::INFINITY); - /// Creates a new [`Size`] with the given width and height. - /// - /// [`Size`]: struct.Size.html - pub const fn new(width: f32, height: f32) -> Self { - Size { width, height } - } - /// Increments the [`Size`] to account for the given padding. /// /// [`Size`]: struct.Size.html diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 0ade0458..8d16ed56 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -4,9 +4,7 @@ mod scene; use controls::Controls; use scene::Scene; -use iced_wgpu::{ - wgpu, window::SwapChain, Backend, Primitive, Renderer, Settings, Target, -}; +use iced_wgpu::{wgpu, Backend, Primitive, Renderer, Settings, Viewport}; use iced_winit::{ futures, mouse, winit, Cache, Clipboard, Size, UserInterface, }; @@ -22,8 +20,12 @@ pub fn main() { // Initialize winit let event_loop = EventLoop::new(); let window = winit::window::Window::new(&event_loop).unwrap(); - let mut logical_size = - window.inner_size().to_logical(window.scale_factor()); + + let physical_size = window.inner_size(); + let mut viewport = Viewport::with_physical_size( + Size::new(physical_size.width, physical_size.height), + window.scale_factor(), + ); let mut modifiers = ModifiersState::default(); // Initialize WGPU @@ -55,7 +57,16 @@ pub fn main() { let mut swap_chain = { let size = window.inner_size(); - SwapChain::new(&device, &surface, format, size.width, size.height) + device.create_swap_chain( + &surface, + &wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }, + ) }; let mut resized = false; @@ -83,8 +94,11 @@ pub fn main() { modifiers = new_modifiers; } WindowEvent::Resized(new_size) => { - logical_size = - new_size.to_logical(window.scale_factor()); + viewport = Viewport::with_physical_size( + Size::new(new_size.width, new_size.height), + window.scale_factor(), + ); + resized = true; } WindowEvent::CloseRequested => { @@ -117,7 +131,7 @@ pub fn main() { // First, we build our user interface. let mut user_interface = UserInterface::build( controls.view(&scene), - Size::new(logical_size.width, logical_size.height), + viewport.logical_size(), cache.take().unwrap(), &mut renderer, ); @@ -151,7 +165,7 @@ pub fn main() { // user interface. UserInterface::build( controls.view(&scene), - Size::new(logical_size.width, logical_size.height), + viewport.logical_size(), cache.take().unwrap(), &mut renderer, ) @@ -170,17 +184,19 @@ pub fn main() { if resized { let size = window.inner_size(); - swap_chain = SwapChain::new( - &device, + swap_chain = device.create_swap_chain( &surface, - format, - size.width, - size.height, + &wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }, ); } - let (frame, viewport) = - swap_chain.next_frame().expect("Next frame"); + let frame = swap_chain.get_next_texture().expect("Next frame"); let mut encoder = device.create_command_encoder( &wgpu::CommandEncoderDescriptor { label: None }, @@ -193,12 +209,9 @@ pub fn main() { let mouse_interaction = renderer.backend_mut().draw( &mut device, &mut encoder, - Target { - texture: &frame.view, - viewport, - }, + &frame.view, + &viewport, &output, - window.scale_factor(), &["Some debug information!"], ); diff --git a/glow/src/backend.rs b/glow/src/backend.rs index fb6782e6..5e2aa837 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -47,12 +47,10 @@ impl Backend { gl: &glow::Context, viewport: &Viewport, (primitive, mouse_interaction): &(Primitive, mouse::Interaction), - scale_factor: f64, overlay_text: &[T], ) -> mouse::Interaction { - let (width, height) = viewport.dimensions(); - let scale_factor = scale_factor as f32; - let transformation = viewport.transformation(); + let viewport_size = viewport.physical_size(); + let projection = viewport.projection(); let mut layers = Layer::generate(primitive, viewport); layers.push(Layer::overlay(overlay_text, viewport)); @@ -60,12 +58,11 @@ impl Backend { for layer in layers { self.flush( gl, - viewport, - scale_factor, - transformation, + viewport.scale_factor() as f32, + projection, &layer, - width, - height, + viewport_size.width, + viewport_size.height, ); } @@ -75,19 +72,18 @@ impl Backend { fn flush( &mut self, gl: &glow::Context, - viewport: &Viewport, scale_factor: f32, transformation: Transformation, layer: &Layer<'_>, target_width: u32, target_height: u32, ) { - let bounds = layer.bounds * scale_factor; + let bounds = (layer.bounds * scale_factor).round(); if !layer.quads.is_empty() { self.quad_pipeline.draw( gl, - viewport, + target_height, &layer.quads, transformation, scale_factor, @@ -175,8 +171,7 @@ impl Backend { transformation, glow_glyph::Region { x: bounds.x, - y: viewport.height() - - (bounds.y + bounds.height).min(viewport.height()), + y: target_height - (bounds.y + bounds.height), width: bounds.width, height: bounds.height, }, diff --git a/glow/src/quad.rs b/glow/src/quad.rs index 26424b39..fd71757f 100644 --- a/glow/src/quad.rs +++ b/glow/src/quad.rs @@ -1,4 +1,4 @@ -use crate::{Transformation, Viewport}; +use crate::Transformation; use glow::HasContext; use iced_graphics::layer; use iced_native::Rectangle; @@ -54,7 +54,7 @@ impl Pipeline { pub fn draw( &mut self, gl: &glow::Context, - viewport: &Viewport, + target_height: u32, instances: &[layer::Quad], transformation: Transformation, scale: f32, @@ -64,9 +64,7 @@ impl Pipeline { gl.enable(glow::SCISSOR_TEST); gl.scissor( bounds.x as i32, - (viewport.height() - - (bounds.y + bounds.height).min(viewport.height())) - as i32, + (target_height - (bounds.y + bounds.height)) as i32, bounds.width as i32, bounds.height as i32, ); diff --git a/glow/src/window/compositor.rs b/glow/src/window/compositor.rs index 8f770065..514904a8 100644 --- a/glow/src/window/compositor.rs +++ b/glow/src/window/compositor.rs @@ -13,11 +13,11 @@ pub struct Compositor { gl: Option, } -impl iced_native::window::Compositor for Compositor { +impl iced_graphics::window::Compositor for Compositor { type Settings = Settings; type Renderer = Renderer; type Surface = (); - type SwapChain = Viewport; + type SwapChain = (); fn new(_settings: Self::Settings) -> Self { let connection = surfman::Connection::new().expect("Create connection"); @@ -133,16 +133,14 @@ impl iced_native::window::Compositor for Compositor { gl.enable(glow::BLEND); gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); } - - Viewport::new(width, height) } fn draw>( &mut self, renderer: &mut Self::Renderer, swap_chain: &mut Self::SwapChain, + viewport: &Viewport, output: &::Output, - scale_factor: f64, overlay: &[T], ) -> mouse::Interaction { let gl = self.gl.as_ref().unwrap(); @@ -151,13 +149,7 @@ impl iced_native::window::Compositor for Compositor { gl.clear(glow::COLOR_BUFFER_BIT); } - let mouse = renderer.backend_mut().draw( - gl, - swap_chain, - output, - scale_factor, - overlay, - ); + let mouse = renderer.backend_mut().draw(gl, viewport, output, overlay); { let mut surface = self diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 61f1f6d4..12ad3f14 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -13,6 +13,7 @@ font-icons = [] [dependencies] bytemuck = "1.2" glam = "0.8" +raw-window-handle = "0.3" [dependencies.iced_native] version = "0.2" diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index ae9c6ce0..916b5c83 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -7,7 +7,7 @@ use crate::{ }; pub struct Layer<'a> { - pub bounds: Rectangle, + pub bounds: Rectangle, pub quads: Vec, pub meshes: Vec>, pub text: Vec>, @@ -15,7 +15,7 @@ pub struct Layer<'a> { } impl<'a> Layer<'a> { - pub fn new(bounds: Rectangle) -> Self { + pub fn new(bounds: Rectangle) -> Self { Self { bounds, quads: Vec::new(), @@ -26,14 +26,8 @@ impl<'a> Layer<'a> { } pub fn overlay(lines: &'a [impl AsRef], viewport: &Viewport) -> Self { - let (width, height) = viewport.dimensions(); - - let mut overlay = Layer::new(Rectangle { - x: 0, - y: 0, - width, - height, - }); + let mut overlay = + Layer::new(Rectangle::with_size(viewport.logical_size())); for (i, line) in lines.iter().enumerate() { let text = Text { @@ -61,28 +55,14 @@ impl<'a> Layer<'a> { overlay } - pub(crate) fn intersection( - &self, - rectangle: Rectangle, - ) -> Option> { - let layer_bounds: Rectangle = self.bounds.into(); - - layer_bounds.intersection(&rectangle).map(Into::into) - } - pub fn generate( primitive: &'a Primitive, viewport: &Viewport, ) -> Vec { - let mut layers = Vec::new(); - let (width, height) = viewport.dimensions(); + let first_layer = + Layer::new(Rectangle::with_size(viewport.logical_size())); - layers.push(Layer::new(Rectangle { - x: 0, - y: 0, - width, - height, - })); + let mut layers = vec![first_layer]; Self::process_primitive(&mut layers, Vector::new(0.0, 0.0), primitive); @@ -156,11 +136,11 @@ impl<'a> Layer<'a> { ); // Only draw visible content - if let Some(clip_bounds) = layer.intersection(bounds) { + if let Some(clip_bounds) = layer.bounds.intersection(&bounds) { layer.meshes.push(Mesh { origin: Point::new(translation.x, translation.y), buffers, - clip_bounds: clip_bounds.into(), + clip_bounds, }); } } @@ -170,12 +150,13 @@ impl<'a> Layer<'a> { content, } => { let layer = layers.last_mut().unwrap(); + let translated_bounds = *bounds + translation; // Only draw visible content if let Some(clip_bounds) = - layer.intersection(*bounds + translation) + layer.bounds.intersection(&translated_bounds) { - let clip_layer = Layer::new(clip_bounds.into()); + let clip_layer = Layer::new(clip_bounds); let new_layer = Layer::new(layer.bounds); layers.push(clip_layer); @@ -236,7 +217,7 @@ pub struct Quad { pub struct Mesh<'a> { pub origin: Point, pub buffers: &'a triangle::Mesh2D, - pub clip_bounds: Rectangle, + pub clip_bounds: Rectangle, } #[derive(Debug, Clone, Copy)] diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 5ab333c7..2de9d21c 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -10,6 +10,7 @@ pub mod backend; pub mod font; pub mod layer; pub mod triangle; +pub mod window; #[doc(no_inline)] pub use widget::*; diff --git a/graphics/src/viewport.rs b/graphics/src/viewport.rs index 48e5a249..745ef339 100644 --- a/graphics/src/viewport.rs +++ b/graphics/src/viewport.rs @@ -1,33 +1,48 @@ -use crate::Transformation; +use crate::{Size, Transformation}; /// A viewing region for displaying computer graphics. #[derive(Debug)] pub struct Viewport { - width: u32, - height: u32, - transformation: Transformation, + physical_size: Size, + logical_size: Size, + scale_factor: f64, + projection: Transformation, } impl Viewport { - /// Creates a new [`Viewport`] with the given dimensions. - pub fn new(width: u32, height: u32) -> Viewport { + /// Creates a new [`Viewport`] with the given physical dimensions and scale + /// factor. + /// + /// [`Viewport`]: struct.Viewport.html + pub fn with_physical_size(size: Size, scale_factor: f64) -> Viewport { Viewport { - width, - height, - transformation: Transformation::orthographic(width, height), + physical_size: size, + logical_size: Size::new( + (size.width as f64 / scale_factor) as f32, + (size.height as f64 / scale_factor) as f32, + ), + scale_factor, + projection: Transformation::orthographic(size.width, size.height), } } - pub fn height(&self) -> u32 { - self.height + pub fn physical_size(&self) -> Size { + self.physical_size } - /// Returns the dimensions of the [`Viewport`]. - pub fn dimensions(&self) -> (u32, u32) { - (self.width, self.height) + pub fn physical_height(&self) -> u32 { + self.physical_size.height } - pub fn transformation(&self) -> Transformation { - self.transformation + pub fn logical_size(&self) -> Size { + self.logical_size + } + + pub fn scale_factor(&self) -> f64 { + self.scale_factor + } + + pub fn projection(&self) -> Transformation { + self.projection } } diff --git a/graphics/src/window.rs b/graphics/src/window.rs new file mode 100644 index 00000000..a7c8911b --- /dev/null +++ b/graphics/src/window.rs @@ -0,0 +1,3 @@ +mod compositor; + +pub use compositor::Compositor; diff --git a/native/src/window/compositor.rs b/graphics/src/window/compositor.rs similarity index 89% rename from native/src/window/compositor.rs rename to graphics/src/window/compositor.rs index ae010c89..82faa6e1 100644 --- a/native/src/window/compositor.rs +++ b/graphics/src/window/compositor.rs @@ -1,5 +1,5 @@ -use crate::mouse; - +use crate::Viewport; +use iced_native::mouse; use raw_window_handle::HasRawWindowHandle; /// A graphics compositor that can draw to windows. @@ -8,7 +8,7 @@ pub trait Compositor: Sized { type Settings: Default + Clone; /// The iced renderer of the backend. - type Renderer: crate::Renderer; + type Renderer: iced_native::Renderer; /// The surface of the backend. type Surface; @@ -53,8 +53,8 @@ pub trait Compositor: Sized { &mut self, renderer: &mut Self::Renderer, swap_chain: &mut Self::SwapChain, - output: &::Output, - scale_factor: f64, + viewport: &Viewport, + output: &::Output, overlay: &[T], ) -> mouse::Interaction; } diff --git a/native/Cargo.toml b/native/Cargo.toml index 7e9c2a5a..067b8708 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -9,7 +9,6 @@ repository = "https://github.com/hecrj/iced" [dependencies] twox-hash = "1.5" -raw-window-handle = "0.3" unicode-segmentation = "1.6" [dependencies.iced_core] diff --git a/native/src/window.rs b/native/src/window.rs index 84269fbf..220bb3be 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -1,6 +1,4 @@ //! Build window-based GUI applications. -mod compositor; mod event; -pub use compositor::Compositor; pub use event::Event; diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 82e4c4b2..1e62cf07 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,11 +1,11 @@ use crate::quad; use crate::text; use crate::triangle; -use crate::{Settings, Target, Transformation}; +use crate::{Settings, Transformation}; use iced_graphics::backend; use iced_graphics::font; use iced_graphics::layer::Layer; -use iced_graphics::Primitive; +use iced_graphics::{Primitive, Viewport}; use iced_native::mouse; use iced_native::{Font, HorizontalAlignment, Size, VerticalAlignment}; @@ -62,19 +62,19 @@ impl Backend { &mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, - target: Target<'_>, + frame: &wgpu::TextureView, + viewport: &Viewport, (primitive, mouse_interaction): &(Primitive, mouse::Interaction), - scale_factor: f64, overlay_text: &[T], ) -> mouse::Interaction { log::debug!("Drawing"); - let (width, height) = target.viewport.dimensions(); - let scale_factor = scale_factor as f32; - let transformation = target.viewport.transformation(); + let target_size = viewport.physical_size(); + let scale_factor = viewport.scale_factor() as f32; + let transformation = viewport.projection(); - let mut layers = Layer::generate(primitive, &target.viewport); - layers.push(Layer::overlay(overlay_text, &target.viewport)); + let mut layers = Layer::generate(primitive, viewport); + layers.push(Layer::overlay(overlay_text, viewport)); for layer in layers { self.flush( @@ -83,9 +83,9 @@ impl Backend { transformation, &layer, encoder, - target.texture, - width, - height, + &frame, + target_size.width, + target_size.height, ); } @@ -106,7 +106,7 @@ impl Backend { target_width: u32, target_height: u32, ) { - let bounds = layer.bounds * scale_factor; + let bounds = (layer.bounds * scale_factor).round(); if !layer.quads.is_empty() { self.quad_pipeline.draw( diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 74007eae..b0eee0a0 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -34,7 +34,6 @@ pub mod window; mod backend; mod quad; -mod target; mod text; pub use iced_graphics::{Defaults, Primitive, Viewport}; @@ -42,7 +41,6 @@ pub use wgpu; pub use backend::Backend; pub use settings::Settings; -pub use target::Target; #[doc(no_inline)] pub use widget::*; diff --git a/wgpu/src/target.rs b/wgpu/src/target.rs deleted file mode 100644 index 1e72c0c3..00000000 --- a/wgpu/src/target.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::Viewport; - -/// A rendering target. -#[derive(Debug)] -pub struct Target<'a> { - /// The texture where graphics will be rendered. - pub texture: &'a wgpu::TextureView, - - /// The viewport of the target. - /// - /// Most of the time, you will want this to match the dimensions of the - /// texture. - pub viewport: &'a Viewport, -} diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 31975005..dc58a52a 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -326,7 +326,8 @@ impl Pipeline { for (i, (vertex_offset, index_offset, indices)) in offsets.into_iter().enumerate() { - let clip_bounds = meshes[i].clip_bounds * scale_factor; + let clip_bounds = + (meshes[i].clip_bounds * scale_factor).round(); render_pass.set_scissor_rect( clip_bounds.x, diff --git a/wgpu/src/window.rs b/wgpu/src/window.rs index 391d3e36..aac5fb9e 100644 --- a/wgpu/src/window.rs +++ b/wgpu/src/window.rs @@ -1,6 +1,4 @@ //! Display rendering results on windows. mod compositor; -mod swap_chain; pub use compositor::Compositor; -pub use swap_chain::SwapChain; diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 8950ffd4..7eba8924 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,5 +1,6 @@ -use crate::{window::SwapChain, Renderer, Settings, Target}; +use crate::{Renderer, Settings}; +use iced_graphics::Viewport; use iced_native::{futures, mouse}; use raw_window_handle::HasRawWindowHandle; @@ -11,11 +12,11 @@ pub struct Compositor { format: wgpu::TextureFormat, } -impl iced_native::window::Compositor for Compositor { +impl iced_graphics::window::Compositor for Compositor { type Settings = Settings; type Renderer = Renderer; type Surface = wgpu::Surface; - type SwapChain = SwapChain; + type SwapChain = wgpu::SwapChain; fn new(settings: Self::Settings) -> Self { let (device, queue) = futures::executor::block_on(async { @@ -66,19 +67,28 @@ impl iced_native::window::Compositor for Compositor { surface: &Self::Surface, width: u32, height: u32, - ) -> SwapChain { - SwapChain::new(&self.device, surface, self.format, width, height) + ) -> Self::SwapChain { + self.device.create_swap_chain( + surface, + &wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: self.format, + width, + height, + present_mode: wgpu::PresentMode::Mailbox, + }, + ) } fn draw>( &mut self, renderer: &mut Self::Renderer, - swap_chain: &mut SwapChain, + swap_chain: &mut Self::SwapChain, + viewport: &Viewport, output: &::Output, - scale_factor: f64, overlay: &[T], ) -> mouse::Interaction { - let (frame, viewport) = swap_chain.next_frame().expect("Next frame"); + let frame = swap_chain.get_next_texture().expect("Next frame"); let mut encoder = self.device.create_command_encoder( &wgpu::CommandEncoderDescriptor { label: None }, @@ -103,12 +113,9 @@ impl iced_native::window::Compositor for Compositor { let mouse_interaction = renderer.backend_mut().draw( &mut self.device, &mut encoder, - Target { - texture: &frame.view, - viewport, - }, + &frame.view, + viewport, output, - scale_factor, overlay, ); diff --git a/wgpu/src/window/swap_chain.rs b/wgpu/src/window/swap_chain.rs deleted file mode 100644 index 72e58a50..00000000 --- a/wgpu/src/window/swap_chain.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::Viewport; - -/// The rendering target of a window. -/// -/// It represents a series of virtual framebuffers with a scale factor. -#[derive(Debug)] -pub struct SwapChain { - raw: wgpu::SwapChain, - viewport: Viewport, -} - -impl SwapChain {} - -impl SwapChain { - /// Creates a new [`SwapChain`] for the given surface. - /// - /// [`SwapChain`]: struct.SwapChain.html - pub fn new( - device: &wgpu::Device, - surface: &wgpu::Surface, - format: wgpu::TextureFormat, - width: u32, - height: u32, - ) -> SwapChain { - SwapChain { - raw: new_swap_chain(surface, format, width, height, device), - viewport: Viewport::new(width, height), - } - } - - /// Returns the next frame of the [`SwapChain`] alongside its [`Viewport`]. - /// - /// [`SwapChain`]: struct.SwapChain.html - /// [`Viewport`]: ../struct.Viewport.html - pub fn next_frame( - &mut self, - ) -> Result<(wgpu::SwapChainOutput, &Viewport), wgpu::TimeOut> { - let viewport = &self.viewport; - - self.raw.get_next_texture().map(|output| (output, viewport)) - } -} - -fn new_swap_chain( - surface: &wgpu::Surface, - format: wgpu::TextureFormat, - width: u32, - height: u32, - device: &wgpu::Device, -) -> wgpu::SwapChain { - device.create_swap_chain( - &surface, - &wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, - format, - width, - height, - present_mode: wgpu::PresentMode::Mailbox, - }, - ) -} diff --git a/winit/Cargo.toml b/winit/Cargo.toml index b6662451..1bf2d7b3 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -22,5 +22,9 @@ log = "0.4" version = "0.2" path = "../native" +[dependencies.iced_graphics] +version = "0.1" +path = "../graphics" + [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" diff --git a/winit/src/application.rs b/winit/src/application.rs index 83b53de2..9196709b 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,8 +1,9 @@ use crate::{ - conversion, mouse, size::Size, window, Cache, Clipboard, Command, Debug, - Element, Executor, Mode, Proxy, Runtime, Settings, Subscription, - UserInterface, + conversion, mouse, Cache, Clipboard, Command, Debug, Element, Executor, + Mode, Proxy, Runtime, Settings, Size, Subscription, UserInterface, }; +use iced_graphics::window; +use iced_graphics::Viewport; /// An interactive, native cross-platform application. /// @@ -124,7 +125,7 @@ pub trait Application: Sized { ) where Self: 'static, { - use window::Compositor as _; + use iced_graphics::window::Compositor as _; use winit::{ event::{self, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -181,7 +182,11 @@ pub trait Application: Sized { window_builder.build(&event_loop).expect("Open window") }; - let mut size = Size::new(window.inner_size(), window.scale_factor()); + let physical_size = window.inner_size(); + let mut viewport = Viewport::with_physical_size( + Size::new(physical_size.width, physical_size.height), + window.scale_factor(), + ); let mut resized = false; let clipboard = Clipboard::new(&window); @@ -190,21 +195,17 @@ pub trait Application: Sized { let surface = compositor.create_surface(&window); let mut renderer = compositor.create_renderer(backend_settings); - let mut swap_chain = { - let physical_size = size.physical(); - - compositor.create_swap_chain( - &surface, - physical_size.width, - physical_size.height, - ) - }; + let mut swap_chain = compositor.create_swap_chain( + &surface, + physical_size.width, + physical_size.height, + ); let user_interface = build_user_interface( &mut application, Cache::default(), &mut renderer, - size.logical(), + viewport.logical_size(), &mut debug, ); @@ -226,16 +227,11 @@ pub trait Application: Sized { return; } - // TODO: We should be able to keep a user interface alive - // between events once we remove state references. - // - // This will allow us to rebuild it only when a message is - // handled. let mut user_interface = build_user_interface( &mut application, cache.take().unwrap(), &mut renderer, - size.logical(), + viewport.logical_size(), &mut debug, ); @@ -306,7 +302,7 @@ pub trait Application: Sized { &mut application, temp_cache, &mut renderer, - size.logical(), + viewport.logical_size(), &mut debug, ); @@ -326,7 +322,7 @@ pub trait Application: Sized { debug.render_started(); if resized { - let physical_size = size.physical(); + let physical_size = viewport.physical_size(); swap_chain = compositor.create_swap_chain( &surface, @@ -340,8 +336,8 @@ pub trait Application: Sized { let new_mouse_interaction = compositor.draw( &mut renderer, &mut swap_chain, + &viewport, &primitive, - size.scale_factor(), &debug.overlay(), ); @@ -364,7 +360,12 @@ pub trait Application: Sized { } => { match window_event { WindowEvent::Resized(new_size) => { - size = Size::new(new_size, window.scale_factor()); + let size = Size::new(new_size.width, new_size.height); + + viewport = Viewport::with_physical_size( + size, + window.scale_factor(), + ); resized = true; } WindowEvent::CloseRequested => { @@ -402,7 +403,7 @@ pub trait Application: Sized { if let Some(event) = conversion::window_event( &window_event, - size.scale_factor(), + viewport.scale_factor(), modifiers, ) { events.push(event); @@ -419,7 +420,7 @@ fn build_user_interface<'a, A: Application>( application: &'a mut A, cache: Cache, renderer: &mut ::Renderer, - size: winit::dpi::LogicalSize, + size: Size, debug: &mut Debug, ) -> UserInterface< 'a, @@ -431,15 +432,7 @@ fn build_user_interface<'a, A: Application>( debug.view_finished(); debug.layout_started(); - let user_interface = UserInterface::build( - view, - iced_native::Size::new( - size.width.round() as f32, - size.height.round() as f32, - ), - cache, - renderer, - ); + let user_interface = UserInterface::build(view, size, cache, renderer); debug.layout_finished(); user_interface diff --git a/winit/src/lib.rs b/winit/src/lib.rs index f99e1290..b0f235ad 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -32,7 +32,6 @@ mod application; mod clipboard; mod mode; mod proxy; -mod size; // We disable debug capabilities on release builds unless the `debug` feature // is explicitly enabled. From e0e4ee73feead3f05730625c7e1917b63f0b384e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 21 May 2020 00:37:47 +0200 Subject: [PATCH 12/40] Implement `iced_glutin` :tada: --- Cargo.toml | 13 +- examples/custom_widget/Cargo.toml | 2 +- examples/custom_widget/src/main.rs | 18 +- examples/geometry/Cargo.toml | 2 +- examples/geometry/src/main.rs | 24 +- examples/todos/Cargo.toml | 2 +- examples/tour/Cargo.toml | 3 +- examples/tour/src/main.rs | 40 +-- glow/Cargo.toml | 13 +- glow/src/backend.rs | 29 +- glow/src/widget.rs | 11 +- glow/src/widget/canvas.rs | 194 +----------- glow/src/window/compositor.rs | 170 ++--------- glutin/Cargo.toml | 30 ++ glutin/README.md | 27 ++ glutin/src/application.rs | 429 +++++++++++++++++++++++++++ glutin/src/lib.rs | 15 + graphics/Cargo.toml | 1 + graphics/src/layer.rs | 1 + graphics/src/window.rs | 6 + graphics/src/window/gl_compositor.rs | 24 ++ src/application.rs | 20 +- src/element.rs | 2 +- src/lib.rs | 2 +- src/settings.rs | 8 +- src/widget.rs | 24 +- winit/src/application.rs | 32 +- winit/src/lib.rs | 7 +- winit/src/proxy.rs | 1 + winit/src/settings.rs | 36 +++ winit/src/size.rs | 30 -- 31 files changed, 718 insertions(+), 498 deletions(-) create mode 100644 glutin/Cargo.toml create mode 100644 glutin/README.md create mode 100644 glutin/src/application.rs create mode 100644 glutin/src/lib.rs create mode 100644 graphics/src/window/gl_compositor.rs delete mode 100644 winit/src/size.rs diff --git a/Cargo.toml b/Cargo.toml index aa6216cf..9339f4ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,13 +13,13 @@ categories = ["gui"] [features] # Enables the `Image` widget -image = ["iced_wgpu/image"] +image = ["iced_glow/image"] # Enables the `Svg` widget -svg = ["iced_wgpu/svg"] +svg = ["iced_glow/svg"] # Enables the `Canvas` widget -canvas = ["iced_wgpu/canvas"] +canvas = ["iced_glow/canvas"] # Enables a debug view in native platforms (press F12) -debug = ["iced_winit/debug"] +debug = ["iced_glutin/debug"] # Enables `tokio` as the `executor::Default` on native platforms tokio = ["iced_futures/tokio"] # Enables `async-std` as the `executor::Default` on native platforms @@ -36,6 +36,7 @@ members = [ "futures", "graphics", "glow", + "glutin", "native", "style", "web", @@ -67,8 +68,8 @@ iced_core = { version = "0.2", path = "core" } iced_futures = { version = "0.1", path = "futures" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_winit = { version = "0.1", path = "winit" } -iced_wgpu = { version = "0.2", path = "wgpu" } +iced_glutin = { version = "0.1", path = "glutin" } +iced_glow = { version = "0.1", path = "glow" } [target.'cfg(target_arch = "wasm32")'.dependencies] iced_web = { version = "0.2", path = "web" } diff --git a/examples/custom_widget/Cargo.toml b/examples/custom_widget/Cargo.toml index 30747dc0..3942538d 100644 --- a/examples/custom_widget/Cargo.toml +++ b/examples/custom_widget/Cargo.toml @@ -8,4 +8,4 @@ publish = false [dependencies] iced = { path = "../.." } iced_native = { path = "../../native" } -iced_wgpu = { path = "../../wgpu" } +iced_graphics = { path = "../../graphics" } diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index f096fb54..bcf896b0 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -9,11 +9,11 @@ mod circle { // Of course, you can choose to make the implementation renderer-agnostic, // if you wish to, by creating your own `Renderer` trait, which could be // implemented by `iced_wgpu` and other renderers. + use iced_graphics::{Backend, Defaults, Primitive, Renderer}; use iced_native::{ layout, mouse, Background, Color, Element, Hasher, Layout, Length, Point, Size, Widget, }; - use iced_wgpu::{Defaults, Primitive, Renderer}; pub struct Circle { radius: u16, @@ -25,7 +25,10 @@ mod circle { } } - impl Widget for Circle { + impl Widget> for Circle + where + B: Backend, + { fn width(&self) -> Length { Length::Shrink } @@ -36,7 +39,7 @@ mod circle { fn layout( &self, - _renderer: &Renderer, + _renderer: &Renderer, _limits: &layout::Limits, ) -> layout::Node { layout::Node::new(Size::new( @@ -53,7 +56,7 @@ mod circle { fn draw( &self, - _renderer: &mut Renderer, + _renderer: &mut Renderer, _defaults: &Defaults, layout: Layout<'_>, _cursor_position: Point, @@ -71,8 +74,11 @@ mod circle { } } - impl<'a, Message> Into> for Circle { - fn into(self) -> Element<'a, Message, Renderer> { + impl<'a, Message, B> Into>> for Circle + where + B: Backend, + { + fn into(self) -> Element<'a, Message, Renderer> { Element::new(self) } } diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml index 9df52454..34eec4fb 100644 --- a/examples/geometry/Cargo.toml +++ b/examples/geometry/Cargo.toml @@ -8,4 +8,4 @@ publish = false [dependencies] iced = { path = "../.." } iced_native = { path = "../../native" } -iced_wgpu = { path = "../../wgpu" } +iced_graphics = { path = "../../graphics" } diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index aabe6b21..71ce0d8c 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -10,14 +10,14 @@ mod rainbow { // Of course, you can choose to make the implementation renderer-agnostic, // if you wish to, by creating your own `Renderer` trait, which could be // implemented by `iced_wgpu` and other renderers. + use iced_graphics::{ + triangle::{Mesh2D, Vertex2D}, + Backend, Defaults, Primitive, Renderer, + }; use iced_native::{ layout, mouse, Element, Hasher, Layout, Length, Point, Size, Vector, Widget, }; - use iced_wgpu::{ - triangle::{Mesh2D, Vertex2D}, - Defaults, Primitive, Renderer, - }; pub struct Rainbow; @@ -27,7 +27,10 @@ mod rainbow { } } - impl Widget for Rainbow { + impl Widget> for Rainbow + where + B: Backend, + { fn width(&self) -> Length { Length::Fill } @@ -38,7 +41,7 @@ mod rainbow { fn layout( &self, - _renderer: &Renderer, + _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let size = limits.width(Length::Fill).resolve(Size::ZERO); @@ -50,7 +53,7 @@ mod rainbow { fn draw( &self, - _renderer: &mut Renderer, + _renderer: &mut Renderer, _defaults: &Defaults, layout: Layout<'_>, cursor_position: Point, @@ -146,8 +149,11 @@ mod rainbow { } } - impl<'a, Message> Into> for Rainbow { - fn into(self) -> Element<'a, Message, Renderer> { + impl<'a, Message, B> Into>> for Rainbow + where + B: Backend, + { + fn into(self) -> Element<'a, Message, Renderer> { Element::new(self) } } diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index f945cde5..b236cc0d 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../..", features = ["async-std"] } +iced = { path = "../..", features = ["async-std", "debug"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml index b34c5feb..96749e90 100644 --- a/examples/tour/Cargo.toml +++ b/examples/tour/Cargo.toml @@ -6,6 +6,5 @@ edition = "2018" publish = false [dependencies] -iced_winit = { path = "../../winit", features = ["debug"] } -iced_glow = { path = "../../glow" } +iced = { path = "../..", features = ["image", "debug"] } env_logger = "0.7" diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 8c3b1d19..ca7a4f5d 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -1,20 +1,14 @@ -use iced_glow::{ - button, scrollable, slider, text_input, window, Button, Checkbox, Color, - Column, Command, Container, Element, HorizontalAlignment, Image, Length, - Radio, Row, Scrollable, Slider, Space, Text, TextInput, +use iced::{ + button, executor, scrollable, slider, text_input, Application, Button, + Checkbox, Color, Column, Command, Container, Element, HorizontalAlignment, + Image, Length, Radio, Row, Scrollable, Settings, Slider, Space, Text, + TextInput, }; -use iced_winit::{executor, Application, Settings}; pub fn main() { env_logger::init(); - Tour::run( - Settings::default(), - iced_glow::Settings { - default_font: None, - antialiasing: None, - }, - ) + Tour::run(Settings::default()) } pub struct Tour { @@ -26,7 +20,6 @@ pub struct Tour { } impl Application for Tour { - type Compositor = window::Compositor; type Executor = executor::Null; type Message = Message; type Flags = (); @@ -693,18 +686,17 @@ impl<'a> Step { fn ferris<'a>(width: u16) -> Container<'a, StepMessage> { Container::new( - Text::new("Not supported yet!") // This should go away once we unify resource loading on native // platforms - //if cfg!(target_arch = "wasm32") { - // Image::new("images/ferris.png") - //} else { - // Image::new(format!( - // "{}/images/ferris.png", - // env!("CARGO_MANIFEST_DIR") - // )) - //} - //.width(Length::Units(width)), + if cfg!(target_arch = "wasm32") { + Image::new("images/ferris.png") + } else { + Image::new(format!( + "{}/images/ferris.png", + env!("CARGO_MANIFEST_DIR") + )) + } + .width(Length::Units(width)), ) .width(Length::Fill) .center_x() @@ -765,7 +757,7 @@ pub enum Layout { } mod style { - use iced_glow::{button, Background, Color, Vector}; + use iced::{button, Background, Color, Vector}; pub enum Button { Primary, diff --git a/glow/Cargo.toml b/glow/Cargo.toml index 72ed8758..148f4fd5 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -7,8 +7,12 @@ description = "A glow renderer for iced" license = "MIT AND OFL-1.1" repository = "https://github.com/hecrj/iced" +[features] +canvas = ["iced_graphics/canvas"] +image = [] +svg = [] + [dependencies] -raw-window-handle = "0.3" euclid = "0.20" glow = "0.4" bytemuck = "1.2" @@ -23,12 +27,7 @@ path = "../native" [dependencies.iced_graphics] version = "0.1" path = "../graphics" -features = ["font-source", "font-fallback", "font-icons"] - -[dependencies.surfman] -path = "../../surfman/surfman" -default-features = false -features = ["sm-raw-window-handle", "sm-x11"] +features = ["font-source", "font-fallback", "font-icons", "opengl"] [dependencies.glow_glyph] path = "../../glow_glyph" diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 5e2aa837..8c578d5e 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -36,12 +36,6 @@ impl Backend { } } - /// Draws the provided primitives in the given [`Target`]. - /// - /// The text provided as overlay will be renderer on top of the primitives. - /// This is useful for rendering debug information. - /// - /// [`Target`]: struct.Target.html pub fn draw>( &mut self, gl: &glow::Context, @@ -50,6 +44,7 @@ impl Backend { overlay_text: &[T], ) -> mouse::Interaction { let viewport_size = viewport.physical_size(); + let scale_factor = viewport.scale_factor() as f32; let projection = viewport.projection(); let mut layers = Layer::generate(primitive, viewport); @@ -58,7 +53,7 @@ impl Backend { for layer in layers { self.flush( gl, - viewport.scale_factor() as f32, + scale_factor, projection, &layer, viewport_size.width, @@ -78,7 +73,8 @@ impl Backend { target_width: u32, target_height: u32, ) { - let bounds = (layer.bounds * scale_factor).round(); + let mut bounds = (layer.bounds * scale_factor).round(); + bounds.height = bounds.height.min(target_height); if !layer.quads.is_empty() { self.quad_pipeline.draw( @@ -204,3 +200,20 @@ impl backend::Text for Backend { self.text_pipeline.space_width(size) } } + +#[cfg(feature = "image")] +impl backend::Image for Backend { + fn dimensions(&self, _handle: &iced_native::image::Handle) -> (u32, u32) { + (50, 50) + } +} + +#[cfg(feature = "svg")] +impl backend::Svg for Backend { + fn viewport_dimensions( + &self, + _handle: &iced_native::svg::Handle, + ) -> (u32, u32) { + (50, 50) + } +} diff --git a/glow/src/widget.rs b/glow/src/widget.rs index 16e7ca88..362465f4 100644 --- a/glow/src/widget.rs +++ b/glow/src/widget.rs @@ -38,7 +38,16 @@ pub use slider::Slider; #[doc(no_inline)] pub use text_input::TextInput; -pub use iced_native::{Image, Space, Text}; +#[cfg(feature = "canvas")] +#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] +pub mod canvas; + +#[cfg(feature = "canvas")] +#[doc(no_inline)] +pub use canvas::Canvas; + +pub use iced_native::{Image, Space}; pub type Column<'a, Message> = iced_native::Column<'a, Message, Renderer>; pub type Row<'a, Message> = iced_native::Row<'a, Message, Renderer>; +pub type Text = iced_native::Text; diff --git a/glow/src/widget/canvas.rs b/glow/src/widget/canvas.rs index 325f90ce..bef34857 100644 --- a/glow/src/widget/canvas.rs +++ b/glow/src/widget/canvas.rs @@ -6,196 +6,4 @@ //! //! [`Canvas`]: struct.Canvas.html //! [`Frame`]: struct.Frame.html -use crate::{Defaults, Primitive, Renderer}; - -use iced_native::{ - layout, Element, Hasher, Layout, Length, MouseCursor, Point, Size, Widget, -}; -use std::hash::Hash; - -pub mod layer; -pub mod path; - -mod drawable; -mod fill; -mod frame; -mod stroke; -mod text; - -pub use drawable::Drawable; -pub use fill::Fill; -pub use frame::Frame; -pub use layer::Layer; -pub use path::Path; -pub use stroke::{LineCap, LineJoin, Stroke}; -pub use text::Text; - -/// A widget capable of drawing 2D graphics. -/// -/// A [`Canvas`] may contain multiple layers. A [`Layer`] is drawn using the -/// painter's algorithm. In other words, layers will be drawn on top of each -/// other in the same order they are pushed into the [`Canvas`]. -/// -/// [`Canvas`]: struct.Canvas.html -/// [`Layer`]: layer/trait.Layer.html -/// -/// # Examples -/// The repository has a couple of [examples] showcasing how to use a -/// [`Canvas`]: -/// -/// - [`clock`], an application that uses the [`Canvas`] widget to draw a clock -/// and its hands to display the current time. -/// - [`solar_system`], an animated solar system drawn using the [`Canvas`] widget -/// and showcasing how to compose different transforms. -/// -/// [examples]: https://github.com/hecrj/iced/tree/0.1/examples -/// [`clock`]: https://github.com/hecrj/iced/tree/0.1/examples/clock -/// [`solar_system`]: https://github.com/hecrj/iced/tree/0.1/examples/solar_system -/// -/// ## Drawing a simple circle -/// If you want to get a quick overview, here's how we can draw a simple circle: -/// -/// ```no_run -/// # mod iced { -/// # pub use iced_wgpu::canvas; -/// # pub use iced_native::Color; -/// # } -/// use iced::canvas::{self, layer, Canvas, Drawable, Fill, Frame, Path}; -/// use iced::Color; -/// -/// // First, we define the data we need for drawing -/// #[derive(Debug)] -/// struct Circle { -/// radius: f32, -/// } -/// -/// // Then, we implement the `Drawable` trait -/// impl Drawable for Circle { -/// fn draw(&self, frame: &mut Frame) { -/// // We create a `Path` representing a simple circle -/// let circle = Path::new(|p| p.circle(frame.center(), self.radius)); -/// -/// // And fill it with some color -/// frame.fill(&circle, Fill::Color(Color::BLACK)); -/// } -/// } -/// -/// // We can use a `Cache` to avoid unnecessary re-tessellation -/// let cache: layer::Cache = layer::Cache::new(); -/// -/// // Finally, we simply provide the data to our `Cache` and push the resulting -/// // layer into a `Canvas` -/// let canvas = Canvas::new() -/// .push(cache.with(&Circle { radius: 50.0 })); -/// ``` -#[derive(Debug)] -pub struct Canvas<'a> { - width: Length, - height: Length, - layers: Vec>, -} - -impl<'a> Canvas<'a> { - const DEFAULT_SIZE: u16 = 100; - - /// Creates a new [`Canvas`] with no layers. - /// - /// [`Canvas`]: struct.Canvas.html - pub fn new() -> Self { - Canvas { - width: Length::Units(Self::DEFAULT_SIZE), - height: Length::Units(Self::DEFAULT_SIZE), - layers: Vec::new(), - } - } - - /// Sets the width of the [`Canvas`]. - /// - /// [`Canvas`]: struct.Canvas.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Canvas`]. - /// - /// [`Canvas`]: struct.Canvas.html - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Adds a [`Layer`] to the [`Canvas`]. - /// - /// It will be drawn on top of previous layers. - /// - /// [`Layer`]: layer/trait.Layer.html - /// [`Canvas`]: struct.Canvas.html - pub fn push(mut self, layer: impl Layer + 'a) -> Self { - self.layers.push(Box::new(layer)); - self - } -} - -impl<'a, Message> Widget for Canvas<'a> { - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height - } - - fn layout( - &self, - _renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let limits = limits.width(self.width).height(self.height); - let size = limits.resolve(Size::ZERO); - - layout::Node::new(size) - } - - fn draw( - &self, - _renderer: &mut Renderer, - _defaults: &Defaults, - layout: Layout<'_>, - _cursor_position: Point, - ) -> (Primitive, MouseCursor) { - let bounds = layout.bounds(); - let origin = Point::new(bounds.x, bounds.y); - let size = Size::new(bounds.width, bounds.height); - - ( - Primitive::Group { - primitives: self - .layers - .iter() - .map(|layer| Primitive::Cached { - origin, - cache: layer.draw(size), - }) - .collect(), - }, - MouseCursor::Idle, - ) - } - - fn hash_layout(&self, state: &mut Hasher) { - std::any::TypeId::of::>().hash(state); - - self.width.hash(state); - self.height.hash(state); - } -} - -impl<'a, Message> From> for Element<'a, Message, Renderer> -where - Message: 'static, -{ - fn from(canvas: Canvas<'a>) -> Element<'a, Message, Renderer> { - Element::new(canvas) - } -} +pub use iced_graphics::canvas::*; diff --git a/glow/src/window/compositor.rs b/glow/src/window/compositor.rs index 514904a8..1117166f 100644 --- a/glow/src/window/compositor.rs +++ b/glow/src/window/compositor.rs @@ -1,180 +1,64 @@ -use crate::{Renderer, Settings, Viewport}; +use crate::{Backend, Renderer, Settings, Viewport}; +use core::ffi::c_void; use glow::HasContext; +use iced_graphics::Size; use iced_native::mouse; -use raw_window_handle::HasRawWindowHandle; /// A window graphics backend for iced powered by `glow`. #[allow(missing_debug_implementations)] pub struct Compositor { - connection: surfman::Connection, - device: surfman::Device, - gl_context: surfman::Context, - gl: Option, + gl: glow::Context, } -impl iced_graphics::window::Compositor for Compositor { +impl iced_graphics::window::GLCompositor for Compositor { type Settings = Settings; type Renderer = Renderer; - type Surface = (); - type SwapChain = (); - fn new(_settings: Self::Settings) -> Self { - let connection = surfman::Connection::new().expect("Create connection"); + unsafe fn new( + settings: Self::Settings, + loader_function: impl FnMut(&str) -> *const c_void, + ) -> (Self, Self::Renderer) { + let gl = glow::Context::from_loader_function(loader_function); - let adapter = connection - .create_hardware_adapter() - .expect("Create adapter"); + gl.clear_color(1.0, 1.0, 1.0, 1.0); - let mut device = - connection.create_device(&adapter).expect("Create device"); + // Enable auto-conversion from/to sRGB + gl.enable(glow::FRAMEBUFFER_SRGB); - let context_descriptor = device - .create_context_descriptor(&surfman::ContextAttributes { - version: surfman::GLVersion::new(3, 0), - flags: surfman::ContextAttributeFlags::empty(), - }) - .expect("Create context descriptor"); + // Enable alpha blending + gl.enable(glow::BLEND); + gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); - let gl_context = device - .create_context(&context_descriptor) - .expect("Create context"); + let renderer = Renderer::new(Backend::new(&gl, settings)); - Self { - connection, - device, - gl_context, - gl: None, - } + (Self { gl }, renderer) } - fn create_renderer(&mut self, settings: Settings) -> Renderer { - self.device - .make_context_current(&self.gl_context) - .expect("Make context current"); - - Renderer::new(crate::Backend::new(self.gl.as_ref().unwrap(), settings)) - } - - fn create_surface( - &mut self, - window: &W, - ) -> Self::Surface { - let native_widget = self - .connection - .create_native_widget_from_rwh(window.raw_window_handle()) - .expect("Create widget"); - - let surface = self - .device - .create_surface( - &self.gl_context, - surfman::SurfaceAccess::GPUOnly, - surfman::SurfaceType::Widget { native_widget }, - ) - .expect("Create surface"); - - let surfman::SurfaceInfo { .. } = self.device.surface_info(&surface); - - self.device - .bind_surface_to_context(&mut self.gl_context, surface) - .expect("Bind surface to context"); - - self.device - .make_context_current(&self.gl_context) - .expect("Make context current"); - - self.gl = Some(glow::Context::from_loader_function(|s| { - self.device.get_proc_address(&self.gl_context, s) - })); - - //let mut framebuffer = - // skia_safe::gpu::gl::FramebufferInfo::from_fboid(framebuffer_object); - - //framebuffer.format = gl::RGBA8; - - //framebuffer - } - - fn create_swap_chain( - &mut self, - _surface: &Self::Surface, - width: u32, - height: u32, - ) -> Self::SwapChain { - let mut surface = self - .device - .unbind_surface_from_context(&mut self.gl_context) - .expect("Unbind surface") - .expect("Active surface"); - - self.device - .resize_surface( - &self.gl_context, - &mut surface, - euclid::Size2D::new(width as i32, height as i32), - ) - .expect("Resize surface"); - - self.device - .bind_surface_to_context(&mut self.gl_context, surface) - .expect("Bind surface to context"); - - let gl = self.gl.as_ref().unwrap(); - + fn resize_viewport(&mut self, physical_size: Size) { unsafe { - gl.viewport(0, 0, width as i32, height as i32); - gl.clear_color(1.0, 1.0, 1.0, 1.0); - - // Enable auto-conversion from/to sRGB - gl.enable(glow::FRAMEBUFFER_SRGB); - - // Enable alpha blending - gl.enable(glow::BLEND); - gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); + self.gl.viewport( + 0, + 0, + physical_size.width as i32, + physical_size.height as i32, + ); } } fn draw>( &mut self, renderer: &mut Self::Renderer, - swap_chain: &mut Self::SwapChain, viewport: &Viewport, output: &::Output, overlay: &[T], ) -> mouse::Interaction { - let gl = self.gl.as_ref().unwrap(); + let gl = &self.gl; unsafe { gl.clear(glow::COLOR_BUFFER_BIT); } - let mouse = renderer.backend_mut().draw(gl, viewport, output, overlay); - - { - let mut surface = self - .device - .unbind_surface_from_context(&mut self.gl_context) - .expect("Unbind surface") - .expect("Active surface"); - - self.device - .present_surface(&self.gl_context, &mut surface) - .expect("Present surface"); - - self.device - .bind_surface_to_context(&mut self.gl_context, surface) - .expect("Bind surface to context"); - } - - mouse - } -} - -impl Drop for Compositor { - fn drop(&mut self) { - self.device - .destroy_context(&mut self.gl_context) - .expect("Destroy context"); + renderer.backend_mut().draw(gl, viewport, output, overlay) } } diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml new file mode 100644 index 00000000..4652112c --- /dev/null +++ b/glutin/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "iced_glutin" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +description = "A glutin runtime for Iced" +license = "MIT" +repository = "https://github.com/hecrj/iced" +documentation = "https://docs.rs/iced_glutin" +keywords = ["gui", "ui", "graphics", "interface", "widgets"] +categories = ["gui"] + +[features] +debug = ["iced_winit/debug"] + +[dependencies] +glutin = "0.24" + +[dependencies.iced_native] +version = "0.2" +path = "../native" + +[dependencies.iced_winit] +version = "0.1" +path = "../winit" + +[dependencies.iced_graphics] +version = "0.1" +path = "../graphics" +features = ["opengl"] diff --git a/glutin/README.md b/glutin/README.md new file mode 100644 index 00000000..34dec1b3 --- /dev/null +++ b/glutin/README.md @@ -0,0 +1,27 @@ +# `iced_winit` +[![Documentation](https://docs.rs/iced_winit/badge.svg)][documentation] +[![Crates.io](https://img.shields.io/crates/v/iced_winit.svg)](https://crates.io/crates/iced_winit) +[![License](https://img.shields.io/crates/l/iced_winit.svg)](https://github.com/hecrj/iced/blob/master/LICENSE) +[![project chat](https://img.shields.io/badge/chat-on_zulip-brightgreen.svg)](https://iced.zulipchat.com) + +`iced_winit` offers some convenient abstractions on top of [`iced_native`] to quickstart development when using [`winit`]. + +It exposes a renderer-agnostic `Application` trait that can be implemented and then run with a simple call. The use of this trait is optional. A `conversion` module is provided for users that decide to implement a custom event loop. + +![iced_winit](../docs/graphs/winit.png) + +[documentation]: https://docs.rs/iced_winit +[`iced_native`]: ../native +[`winit`]: https://github.com/rust-windowing/winit + +## Installation +Add `iced_winit` as a dependency in your `Cargo.toml`: + +```toml +iced_winit = "0.1" +``` + +__Iced moves fast and the `master` branch can contain breaking changes!__ If +you want to learn about a specific release, check out [the release list]. + +[the release list]: https://github.com/hecrj/iced/releases diff --git a/glutin/src/application.rs b/glutin/src/application.rs new file mode 100644 index 00000000..919897a8 --- /dev/null +++ b/glutin/src/application.rs @@ -0,0 +1,429 @@ +use crate::{ + mouse, Cache, Command, Element, Executor, Runtime, Size, Subscription, + UserInterface, +}; +use iced_graphics::window; +use iced_graphics::Viewport; +use iced_winit::conversion; +use iced_winit::{Clipboard, Debug, Mode, Proxy, Settings}; + +/// An interactive, native cross-platform 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 run in +/// its own window. +/// +/// An [`Application`](trait.Application.html) can execute asynchronous actions +/// by returning a [`Command`](struct.Command.html) in some of its methods. +/// +/// When using an [`Application`] with the `debug` feature enabled, a debug view +/// can be toggled by pressing `F12`. +pub trait Application: Sized { + /// The graphics backend to use to draw the [`Application`]. + /// + /// [`Application`]: trait.Application.html + type Compositor: window::GLCompositor; + + /// The [`Executor`] that will run commands and subscriptions. + /// + /// [`Executor`]: trait.Executor.html + type Executor: Executor; + + /// The type of __messages__ your [`Application`] will produce. + /// + /// [`Application`]: trait.Application.html + type Message: std::fmt::Debug + Send; + + /// The data needed to initialize your [`Application`]. + /// + /// [`Application`]: trait.Application.html + type Flags; + + /// Initializes the [`Application`] with the flags provided to + /// [`run`] as part of the [`Settings`]. + /// + /// 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. + /// + /// [`Application`]: trait.Application.html + /// [`run`]: #method.run.html + /// [`Settings`]: struct.Settings.html + fn new(flags: Self::Flags) -> (Self, Command); + + /// Returns the current title of the [`Application`]. + /// + /// This title can be dynamic! The runtime will automatically update the + /// title of your application when necessary. + /// + /// [`Application`]: trait.Application.html + fn title(&self) -> String; + + /// Handles a __message__ and updates the state of the [`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. + /// + /// [`Application`]: trait.Application.html + /// [`Command`]: struct.Command.html + fn update(&mut self, message: Self::Message) -> Command; + + /// Returns the event `Subscription` for the current state of the + /// application. + /// + /// The messages produced by the `Subscription` will be handled by + /// [`update`](#tymethod.update). + /// + /// A `Subscription` will be kept alive as long as you keep returning it! + /// + /// By default, it returns an empty subscription. + fn subscription(&self) -> Subscription { + Subscription::none() + } + + /// Returns the widgets to display in the [`Application`]. + /// + /// These widgets can produce __messages__ based on user interaction. + /// + /// [`Application`]: trait.Application.html + fn view( + &mut self, + ) -> Element< + '_, + Self::Message, + ::Renderer, + >; + + /// Returns the current [`Application`] mode. + /// + /// The runtime will automatically transition your application if a new mode + /// is returned. + /// + /// By default, an application will run in windowed mode. + /// + /// [`Application`]: trait.Application.html + fn mode(&self) -> Mode { + Mode::Windowed + } + + /// Runs the [`Application`] with the provided [`Settings`]. + /// + /// On native platforms, this method will take control of the current thread + /// and __will NOT return__. + /// + /// It should probably be that last thing you call in your `main` function. + /// + /// [`Application`]: trait.Application.html + /// [`Settings`]: struct.Settings.html + fn run( + settings: Settings, + backend_settings: ::Settings, + ) where + Self: 'static, + { + use glutin::{ + event::{self, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + ContextBuilder, + }; + use iced_graphics::window::GLCompositor as _; + + let mut debug = Debug::new(); + + debug.startup_started(); + let event_loop = EventLoop::with_user_event(); + let mut external_messages = Vec::new(); + + let mut runtime = { + let executor = Self::Executor::new().expect("Create executor"); + + Runtime::new(executor, Proxy::new(event_loop.create_proxy())) + }; + + let flags = settings.flags; + let (mut application, init_command) = + runtime.enter(|| Self::new(flags)); + runtime.spawn(init_command); + + let subscription = application.subscription(); + runtime.track(subscription); + + let mut title = application.title(); + let mut mode = application.mode(); + + let context = { + let window_builder = settings.window.into_builder( + &title, + mode, + event_loop.primary_monitor(), + ); + + let context = ContextBuilder::new() + .with_vsync(true) + .build_windowed(window_builder, &event_loop) + .expect("Open window"); + + #[allow(unsafe_code)] + unsafe { + context.make_current().expect("Make OpenGL context current") + } + }; + + let physical_size = context.window().inner_size(); + let mut viewport = Viewport::with_physical_size( + Size::new(physical_size.width, physical_size.height), + context.window().scale_factor(), + ); + let mut resized = false; + + let clipboard = Clipboard::new(&context.window()); + + #[allow(unsafe_code)] + let (mut compositor, mut renderer) = unsafe { + Self::Compositor::new(backend_settings, |address| { + context.get_proc_address(address) + }) + }; + + let user_interface = build_user_interface( + &mut application, + Cache::default(), + &mut renderer, + viewport.logical_size(), + &mut debug, + ); + + debug.draw_started(); + let mut primitive = user_interface.draw(&mut renderer); + debug.draw_finished(); + + let mut cache = Some(user_interface.into_cache()); + let mut events = Vec::new(); + let mut mouse_interaction = mouse::Interaction::default(); + let mut modifiers = glutin::event::ModifiersState::default(); + debug.startup_finished(); + + context.window().request_redraw(); + + event_loop.run(move |event, _, control_flow| match event { + event::Event::MainEventsCleared => { + if events.is_empty() && external_messages.is_empty() { + return; + } + + let mut user_interface = build_user_interface( + &mut application, + cache.take().unwrap(), + &mut renderer, + viewport.logical_size(), + &mut debug, + ); + + debug.event_processing_started(); + events + .iter() + .cloned() + .for_each(|event| runtime.broadcast(event)); + + let mut messages = user_interface.update( + events.drain(..), + clipboard + .as_ref() + .map(|c| c as &dyn iced_native::Clipboard), + &renderer, + ); + messages.extend(external_messages.drain(..)); + debug.event_processing_finished(); + + if messages.is_empty() { + debug.draw_started(); + primitive = user_interface.draw(&mut renderer); + debug.draw_finished(); + + cache = Some(user_interface.into_cache()); + } else { + // When there are messages, we are forced to rebuild twice + // for now :^) + let temp_cache = user_interface.into_cache(); + + for message in messages { + debug.log_message(&message); + + debug.update_started(); + let command = + runtime.enter(|| application.update(message)); + runtime.spawn(command); + debug.update_finished(); + } + + let subscription = application.subscription(); + runtime.track(subscription); + + // Update window title + let new_title = application.title(); + + if title != new_title { + context.window().set_title(&new_title); + + title = new_title; + } + + // Update window mode + let new_mode = application.mode(); + + if mode != new_mode { + context.window().set_fullscreen( + conversion::fullscreen( + context.window().current_monitor(), + new_mode, + ), + ); + + mode = new_mode; + } + + let user_interface = build_user_interface( + &mut application, + temp_cache, + &mut renderer, + viewport.logical_size(), + &mut debug, + ); + + debug.draw_started(); + primitive = user_interface.draw(&mut renderer); + debug.draw_finished(); + + cache = Some(user_interface.into_cache()); + } + + context.window().request_redraw(); + } + event::Event::UserEvent(message) => { + external_messages.push(message); + } + event::Event::RedrawRequested(_) => { + debug.render_started(); + + if resized { + let physical_size = viewport.physical_size(); + + context.resize(glutin::dpi::PhysicalSize { + width: physical_size.width, + height: physical_size.height, + }); + compositor.resize_viewport(physical_size); + + resized = false; + } + + let new_mouse_interaction = compositor.draw( + &mut renderer, + &viewport, + &primitive, + &debug.overlay(), + ); + + context.swap_buffers().expect("Swap buffers"); + debug.render_finished(); + + if new_mouse_interaction != mouse_interaction { + context.window().set_cursor_icon( + conversion::mouse_interaction(new_mouse_interaction), + ); + + mouse_interaction = new_mouse_interaction; + } + + // TODO: Handle animations! + // Maybe we can use `ControlFlow::WaitUntil` for this. + } + event::Event::WindowEvent { + event: window_event, + .. + } => { + match window_event { + WindowEvent::Resized(new_size) => { + let size = Size::new(new_size.width, new_size.height); + + viewport = Viewport::with_physical_size( + size, + context.window().scale_factor(), + ); + resized = true; + } + WindowEvent::CloseRequested => { + *control_flow = ControlFlow::Exit; + } + WindowEvent::ModifiersChanged(new_modifiers) => { + modifiers = new_modifiers; + } + #[cfg(target_os = "macos")] + WindowEvent::KeyboardInput { + input: + glutin::event::KeyboardInput { + virtual_keycode: + Some(glutin::event::VirtualKeyCode::Q), + state: glutin::event::ElementState::Pressed, + .. + }, + .. + } if modifiers.logo() => { + *control_flow = ControlFlow::Exit; + } + #[cfg(feature = "debug")] + WindowEvent::KeyboardInput { + input: + glutin::event::KeyboardInput { + virtual_keycode: + Some(glutin::event::VirtualKeyCode::F12), + state: glutin::event::ElementState::Pressed, + .. + }, + .. + } => debug.toggle(), + _ => {} + } + + if let Some(event) = conversion::window_event( + &window_event, + viewport.scale_factor(), + modifiers, + ) { + events.push(event); + } + } + _ => { + *control_flow = ControlFlow::Wait; + } + }) + } +} + +fn build_user_interface<'a, A: Application>( + application: &'a mut A, + cache: Cache, + renderer: &mut ::Renderer, + size: Size, + debug: &mut Debug, +) -> UserInterface< + 'a, + A::Message, + ::Renderer, +> { + debug.view_started(); + let view = application.view(); + debug.view_finished(); + + debug.layout_started(); + let user_interface = UserInterface::build(view, size, cache, renderer); + debug.layout_finished(); + + user_interface +} diff --git a/glutin/src/lib.rs b/glutin/src/lib.rs new file mode 100644 index 00000000..2e2d03fc --- /dev/null +++ b/glutin/src/lib.rs @@ -0,0 +1,15 @@ +//#![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![deny(unused_results)] +#![deny(unsafe_code)] +#![forbid(rust_2018_idioms)] + +#[doc(no_inline)] +pub use iced_native::*; + +mod application; + +pub use application::Application; + +pub use iced_winit::settings::{self, Settings}; +pub use iced_winit::Mode; diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 12ad3f14..675bcb60 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -9,6 +9,7 @@ canvas = ["lyon"] font-source = ["font-kit"] font-fallback = [] font-icons = [] +opengl = [] [dependencies] bytemuck = "1.2" diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index 916b5c83..59b792f0 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -6,6 +6,7 @@ use crate::{ Vector, VerticalAlignment, Viewport, }; +#[derive(Debug, Clone)] pub struct Layer<'a> { pub bounds: Rectangle, pub quads: Vec, diff --git a/graphics/src/window.rs b/graphics/src/window.rs index a7c8911b..380efb8c 100644 --- a/graphics/src/window.rs +++ b/graphics/src/window.rs @@ -1,3 +1,9 @@ mod compositor; +#[cfg(feature = "opengl")] +mod gl_compositor; + pub use compositor::Compositor; + +#[cfg(feature = "opengl")] +pub use gl_compositor::GLCompositor; diff --git a/graphics/src/window/gl_compositor.rs b/graphics/src/window/gl_compositor.rs new file mode 100644 index 00000000..979d891e --- /dev/null +++ b/graphics/src/window/gl_compositor.rs @@ -0,0 +1,24 @@ +use crate::{Size, Viewport}; +use iced_native::mouse; + +use core::ffi::c_void; + +pub trait GLCompositor: Sized { + type Renderer: iced_native::Renderer; + type Settings: Default; + + unsafe fn new( + settings: Self::Settings, + loader_function: impl FnMut(&str) -> *const c_void, + ) -> (Self, Self::Renderer); + + fn resize_viewport(&mut self, physical_size: Size); + + fn draw>( + &mut self, + renderer: &mut Self::Renderer, + viewport: &Viewport, + output: &::Output, + overlay: &[T], + ) -> mouse::Interaction; +} diff --git a/src/application.rs b/src/application.rs index 0ae2ec55..644a4824 100644 --- a/src/application.rs +++ b/src/application.rs @@ -188,19 +188,19 @@ pub trait Application: Sized { { #[cfg(not(target_arch = "wasm32"))] { - let wgpu_settings = iced_wgpu::Settings { + let glow_settings = iced_glow::Settings { default_font: settings.default_font, antialiasing: if settings.antialiasing { - Some(iced_wgpu::settings::Antialiasing::MSAAx4) + Some(iced_glow::settings::Antialiasing::MSAAx4) } else { None }, - ..iced_wgpu::Settings::default() + ..iced_glow::Settings::default() }; - as iced_winit::Application>::run( + as iced_glutin::Application>::run( settings.into(), - wgpu_settings, + glow_settings, ); } @@ -212,11 +212,11 @@ pub trait Application: Sized { struct Instance(A); #[cfg(not(target_arch = "wasm32"))] -impl iced_winit::Application for Instance +impl iced_glutin::Application for Instance where A: Application, { - type Compositor = iced_wgpu::window::Compositor; + type Compositor = iced_glow::window::Compositor; type Executor = A::Executor; type Flags = A::Flags; type Message = A::Message; @@ -231,10 +231,10 @@ where self.0.title() } - fn mode(&self) -> iced_winit::Mode { + fn mode(&self) -> iced_glutin::Mode { match self.0.mode() { - window::Mode::Windowed => iced_winit::Mode::Windowed, - window::Mode::Fullscreen => iced_winit::Mode::Fullscreen, + window::Mode::Windowed => iced_glutin::Mode::Windowed, + window::Mode::Fullscreen => iced_glutin::Mode::Fullscreen, } } diff --git a/src/element.rs b/src/element.rs index e5356fb6..e7504615 100644 --- a/src/element.rs +++ b/src/element.rs @@ -3,7 +3,7 @@ /// This is an alias of an `iced_native` element with a default `Renderer`. #[cfg(not(target_arch = "wasm32"))] pub type Element<'a, Message> = - iced_winit::Element<'a, Message, iced_wgpu::Renderer>; + iced_glutin::Element<'a, Message, iced_glow::Renderer>; #[cfg(target_arch = "wasm32")] pub use iced_web::Element; diff --git a/src/lib.rs b/src/lib.rs index 2c08268b..58cc141d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -207,7 +207,7 @@ pub use sandbox::Sandbox; pub use settings::Settings; #[cfg(not(target_arch = "wasm32"))] -use iced_winit as runtime; +use iced_glutin as runtime; #[cfg(target_arch = "wasm32")] use iced_web as runtime; diff --git a/src/settings.rs b/src/settings.rs index 01ad0ee0..36685763 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -51,10 +51,10 @@ impl Settings { } #[cfg(not(target_arch = "wasm32"))] -impl From> for iced_winit::Settings { - fn from(settings: Settings) -> iced_winit::Settings { - iced_winit::Settings { - window: iced_winit::settings::Window { +impl From> for iced_glutin::Settings { + fn from(settings: Settings) -> iced_glutin::Settings { + iced_glutin::Settings { + window: iced_glutin::settings::Window { size: settings.window.size, resizable: settings.window.resizable, decorations: settings.window.decorations, diff --git a/src/widget.rs b/src/widget.rs index e33a6b2c..eebf5f2a 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -18,29 +18,27 @@ //! [`text_input::State`]: text_input/struct.State.html #[cfg(not(target_arch = "wasm32"))] mod platform { - pub use iced_wgpu::widget::{ + pub use iced_glow::widget::{ button, checkbox, container, pane_grid, progress_bar, radio, - scrollable, slider, text_input, Text, + scrollable, slider, text_input, Column, Row, Space, Text, }; #[cfg(feature = "canvas")] #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] - pub use iced_wgpu::widget::canvas; + pub use iced_glow::widget::canvas; #[cfg_attr(docsrs, doc(cfg(feature = "image")))] pub mod image { //! Display images in your user interface. - pub use iced_winit::image::{Handle, Image}; + pub use iced_glutin::image::{Handle, Image}; } #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] pub mod svg { //! Display vector graphics in your user interface. - pub use iced_winit::svg::{Handle, Svg}; + pub use iced_glutin::svg::{Handle, Svg}; } - pub use iced_winit::Space; - #[doc(no_inline)] pub use { button::Button, checkbox::Checkbox, container::Container, image::Image, @@ -52,18 +50,6 @@ mod platform { #[cfg(feature = "canvas")] #[doc(no_inline)] pub use canvas::Canvas; - - /// A container that distributes its contents vertically. - /// - /// This is an alias of an `iced_native` column with a default `Renderer`. - pub type Column<'a, Message> = - iced_winit::Column<'a, Message, iced_wgpu::Renderer>; - - /// A container that distributes its contents horizontally. - /// - /// This is an alias of an `iced_native` row with a default `Renderer`. - pub type Row<'a, Message> = - iced_winit::Row<'a, Message, iced_wgpu::Renderer>; } #[cfg(target_arch = "wasm32")] diff --git a/winit/src/application.rs b/winit/src/application.rs index 9196709b..ccab19f1 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -129,7 +129,6 @@ pub trait Application: Sized { use winit::{ event::{self, WindowEvent}, event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, }; let mut debug = Debug::new(); @@ -155,32 +154,11 @@ pub trait Application: Sized { let mut title = application.title(); let mut mode = application.mode(); - let window = { - let mut window_builder = WindowBuilder::new(); - - let (width, height) = settings.window.size; - - window_builder = window_builder - .with_title(&title) - .with_inner_size(winit::dpi::LogicalSize { width, height }) - .with_resizable(settings.window.resizable) - .with_decorations(settings.window.decorations) - .with_fullscreen(conversion::fullscreen( - event_loop.primary_monitor(), - mode, - )); - - #[cfg(target_os = "windows")] - { - use winit::platform::windows::WindowBuilderExtWindows; - - if let Some(parent) = settings.window.platform_specific.parent { - window_builder = window_builder.with_parent_window(parent); - } - } - - window_builder.build(&event_loop).expect("Open window") - }; + let window = settings + .window + .into_builder(&title, mode, event_loop.primary_monitor()) + .build(&event_loop) + .expect("Open window"); let physical_size = window.inner_size(); let mut viewport = Viewport::with_physical_size( diff --git a/winit/src/lib.rs b/winit/src/lib.rs index b0f235ad..9cf5dc0d 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -15,7 +15,7 @@ //! [`winit`]: https://github.com/rust-windowing/winit //! [`Application`]: trait.Application.html //! [`conversion`]: conversion -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] @@ -44,8 +44,7 @@ mod debug; pub use application::Application; pub use clipboard::Clipboard; +pub use debug::Debug; pub use mode::Mode; +pub use proxy::Proxy; pub use settings::Settings; - -use debug::Debug; -use proxy::Proxy; diff --git a/winit/src/proxy.rs b/winit/src/proxy.rs index cff6ca72..ee96614a 100644 --- a/winit/src/proxy.rs +++ b/winit/src/proxy.rs @@ -5,6 +5,7 @@ use iced_native::futures::{ }; use std::pin::Pin; +#[derive(Debug)] pub struct Proxy { raw: winit::event_loop::EventLoopProxy, } diff --git a/winit/src/settings.rs b/winit/src/settings.rs index d58c51f0..751f5071 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -8,6 +8,11 @@ mod platform; pub use platform::PlatformSpecific; +use crate::conversion; +use crate::Mode; +use winit::monitor::MonitorHandle; +use winit::window::WindowBuilder; + /// The settings of an application. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Settings { @@ -38,6 +43,37 @@ pub struct Window { pub platform_specific: platform::PlatformSpecific, } +impl Window { + pub fn into_builder( + self, + title: &str, + mode: Mode, + primary_monitor: MonitorHandle, + ) -> WindowBuilder { + let mut window_builder = WindowBuilder::new(); + + let (width, height) = self.size; + + window_builder = window_builder + .with_title(title) + .with_inner_size(winit::dpi::LogicalSize { width, height }) + .with_resizable(self.resizable) + .with_decorations(self.decorations) + .with_fullscreen(conversion::fullscreen(primary_monitor, mode)); + + #[cfg(target_os = "windows")] + { + use winit::platform::windows::WindowBuilderExtWindows; + + if let Some(parent) = self.platform_specific.parent { + window_builder = window_builder.with_parent_window(parent); + } + } + + window_builder + } +} + impl Default for Window { fn default() -> Window { Window { diff --git a/winit/src/size.rs b/winit/src/size.rs deleted file mode 100644 index 7e3056d4..00000000 --- a/winit/src/size.rs +++ /dev/null @@ -1,30 +0,0 @@ -pub struct Size { - physical: winit::dpi::PhysicalSize, - logical: winit::dpi::LogicalSize, - scale_factor: f64, -} - -impl Size { - pub fn new( - physical: winit::dpi::PhysicalSize, - scale_factor: f64, - ) -> Size { - Size { - logical: physical.to_logical(scale_factor), - physical, - scale_factor, - } - } - - pub fn physical(&self) -> winit::dpi::PhysicalSize { - self.physical - } - - pub fn logical(&self) -> winit::dpi::LogicalSize { - self.logical - } - - pub fn scale_factor(&self) -> f64 { - self.scale_factor - } -} From d77492c0c37dec1207049b340a318e263cb96b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 21 May 2020 01:01:47 +0200 Subject: [PATCH 13/40] Avoid relying `origin_upper_left` It seems to cause considerable glitches when resizing. --- glow/src/quad.rs | 11 +++++++++++ glow/src/shader/quad.frag | 9 ++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/glow/src/quad.rs b/glow/src/quad.rs index fd71757f..acac3219 100644 --- a/glow/src/quad.rs +++ b/glow/src/quad.rs @@ -12,6 +12,7 @@ pub struct Pipeline { instances: ::Buffer, current_transform: Transformation, current_scale: f32, + current_target_height: u32, } impl Pipeline { @@ -35,6 +36,7 @@ impl Pipeline { &Transformation::identity().into(), ); gl.uniform_1_f32(Some(1), 1.0); + gl.uniform_1_f32(Some(2), 0.0); gl.use_program(None); } @@ -48,6 +50,7 @@ impl Pipeline { instances, current_transform: Transformation::identity(), current_scale: 1.0, + current_target_height: 0, } } @@ -94,6 +97,14 @@ impl Pipeline { self.current_scale = scale; } + if target_height != self.current_target_height { + unsafe { + gl.uniform_1_f32(Some(2), target_height as f32); + } + + self.current_target_height = target_height; + } + let mut i = 0; let total = instances.len(); diff --git a/glow/src/shader/quad.frag b/glow/src/shader/quad.frag index d9e74664..17e7216f 100644 --- a/glow/src/shader/quad.frag +++ b/glow/src/shader/quad.frag @@ -1,6 +1,7 @@ #version 450 -layout(origin_upper_left) in vec4 gl_FragCoord; +layout(location = 2) uniform float u_Screen_Height; + layout(location = 0) in vec4 v_Color; layout(location = 1) in vec4 v_BorderColor; layout(location = 2) in vec2 v_Pos; @@ -31,12 +32,14 @@ float distance(in vec2 frag_coord, in vec2 position, in vec2 size, float radius) void main() { vec4 mixed_color; + vec2 fragCoord = vec2(gl_FragCoord.x, u_Screen_Height - gl_FragCoord.y); + // TODO: Remove branching (?) if(v_BorderWidth > 0) { float internal_border = max(v_BorderRadius - v_BorderWidth, 0); float internal_distance = distance( - gl_FragCoord.xy, + fragCoord, v_Pos + vec2(v_BorderWidth), v_Scale - vec2(v_BorderWidth * 2.0), internal_border @@ -54,7 +57,7 @@ void main() { } float d = distance( - gl_FragCoord.xy, + fragCoord, v_Pos, v_Scale, v_BorderRadius From ae5e2c6c734894d71b2034a498a858b7997c5d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 21 May 2020 04:27:31 +0200 Subject: [PATCH 14/40] Introduce `Program` and `State` --- glutin/src/application.rs | 589 ++++++++------------------- glutin/src/lib.rs | 2 +- graphics/src/window/compositor.rs | 9 +- native/Cargo.toml | 3 + {winit => native}/src/debug/basic.rs | 0 {winit => native}/src/debug/null.rs | 0 native/src/lib.rs | 14 +- native/src/program.rs | 39 ++ native/src/program/state.rs | 154 +++++++ src/application.rs | 37 +- wgpu/src/window/compositor.rs | 22 +- winit/Cargo.toml | 2 +- winit/src/application.rs | 535 ++++++++++-------------- winit/src/lib.rs | 14 +- 14 files changed, 643 insertions(+), 777 deletions(-) rename {winit => native}/src/debug/basic.rs (100%) rename {winit => native}/src/debug/null.rs (100%) create mode 100644 native/src/program.rs create mode 100644 native/src/program/state.rs diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 919897a8..3bb7478a 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -1,429 +1,202 @@ -use crate::{ - mouse, Cache, Command, Element, Executor, Runtime, Size, Subscription, - UserInterface, -}; +use crate::{mouse, Executor, Runtime, Size}; use iced_graphics::window; use iced_graphics::Viewport; +use iced_winit::application; use iced_winit::conversion; -use iced_winit::{Clipboard, Debug, Mode, Proxy, Settings}; +use iced_winit::{program, Clipboard, Debug, Proxy, Settings}; -/// An interactive, native cross-platform 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 run in -/// its own window. -/// -/// An [`Application`](trait.Application.html) can execute asynchronous actions -/// by returning a [`Command`](struct.Command.html) in some of its methods. -/// -/// When using an [`Application`] with the `debug` feature enabled, a debug view -/// can be toggled by pressing `F12`. -pub trait Application: Sized { - /// The graphics backend to use to draw the [`Application`]. - /// - /// [`Application`]: trait.Application.html - type Compositor: window::GLCompositor; +pub use iced_winit::Application; - /// The [`Executor`] that will run commands and subscriptions. - /// - /// [`Executor`]: trait.Executor.html - type Executor: Executor; +pub fn run( + settings: Settings, + compositor_settings: C::Settings, +) where + A: Application + 'static, + E: Executor + 'static, + C: window::GLCompositor + 'static, +{ + use glutin::{ + event, + event_loop::{ControlFlow, EventLoop}, + ContextBuilder, + }; - /// The type of __messages__ your [`Application`] will produce. - /// - /// [`Application`]: trait.Application.html - type Message: std::fmt::Debug + Send; + let mut debug = Debug::new(); + debug.startup_started(); - /// The data needed to initialize your [`Application`]. - /// - /// [`Application`]: trait.Application.html - type Flags; + let event_loop = EventLoop::with_user_event(); + let mut runtime = { + let executor = E::new().expect("Create executor"); + let proxy = Proxy::new(event_loop.create_proxy()); - /// Initializes the [`Application`] with the flags provided to - /// [`run`] as part of the [`Settings`]. - /// - /// 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. - /// - /// [`Application`]: trait.Application.html - /// [`run`]: #method.run.html - /// [`Settings`]: struct.Settings.html - fn new(flags: Self::Flags) -> (Self, Command); + Runtime::new(executor, proxy) + }; - /// Returns the current title of the [`Application`]. - /// - /// This title can be dynamic! The runtime will automatically update the - /// title of your application when necessary. - /// - /// [`Application`]: trait.Application.html - fn title(&self) -> String; + let flags = settings.flags; + let (application, init_command) = runtime.enter(|| A::new(flags)); + runtime.spawn(init_command); - /// Handles a __message__ and updates the state of the [`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. - /// - /// [`Application`]: trait.Application.html - /// [`Command`]: struct.Command.html - fn update(&mut self, message: Self::Message) -> Command; + let subscription = application.subscription(); + runtime.track(subscription); - /// Returns the event `Subscription` for the current state of the - /// application. - /// - /// The messages produced by the `Subscription` will be handled by - /// [`update`](#tymethod.update). - /// - /// A `Subscription` will be kept alive as long as you keep returning it! - /// - /// By default, it returns an empty subscription. - fn subscription(&self) -> Subscription { - Subscription::none() - } + let mut title = application.title(); + let mut mode = application.mode(); - /// Returns the widgets to display in the [`Application`]. - /// - /// These widgets can produce __messages__ based on user interaction. - /// - /// [`Application`]: trait.Application.html - fn view( - &mut self, - ) -> Element< - '_, - Self::Message, - ::Renderer, - >; - - /// Returns the current [`Application`] mode. - /// - /// The runtime will automatically transition your application if a new mode - /// is returned. - /// - /// By default, an application will run in windowed mode. - /// - /// [`Application`]: trait.Application.html - fn mode(&self) -> Mode { - Mode::Windowed - } - - /// Runs the [`Application`] with the provided [`Settings`]. - /// - /// On native platforms, this method will take control of the current thread - /// and __will NOT return__. - /// - /// It should probably be that last thing you call in your `main` function. - /// - /// [`Application`]: trait.Application.html - /// [`Settings`]: struct.Settings.html - fn run( - settings: Settings, - backend_settings: ::Settings, - ) where - Self: 'static, - { - use glutin::{ - event::{self, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - ContextBuilder, - }; - use iced_graphics::window::GLCompositor as _; - - let mut debug = Debug::new(); - - debug.startup_started(); - let event_loop = EventLoop::with_user_event(); - let mut external_messages = Vec::new(); - - let mut runtime = { - let executor = Self::Executor::new().expect("Create executor"); - - Runtime::new(executor, Proxy::new(event_loop.create_proxy())) - }; - - let flags = settings.flags; - let (mut application, init_command) = - runtime.enter(|| Self::new(flags)); - runtime.spawn(init_command); - - let subscription = application.subscription(); - runtime.track(subscription); - - let mut title = application.title(); - let mut mode = application.mode(); - - let context = { - let window_builder = settings.window.into_builder( - &title, - mode, - event_loop.primary_monitor(), - ); - - let context = ContextBuilder::new() - .with_vsync(true) - .build_windowed(window_builder, &event_loop) - .expect("Open window"); - - #[allow(unsafe_code)] - unsafe { - context.make_current().expect("Make OpenGL context current") - } - }; - - let physical_size = context.window().inner_size(); - let mut viewport = Viewport::with_physical_size( - Size::new(physical_size.width, physical_size.height), - context.window().scale_factor(), + let context = { + let builder = settings.window.into_builder( + &title, + mode, + event_loop.primary_monitor(), ); - let mut resized = false; - let clipboard = Clipboard::new(&context.window()); + let context = ContextBuilder::new() + .with_vsync(true) + .build_windowed(builder, &event_loop) + .expect("Open window"); #[allow(unsafe_code)] - let (mut compositor, mut renderer) = unsafe { - Self::Compositor::new(backend_settings, |address| { - context.get_proc_address(address) - }) - }; + unsafe { + context.make_current().expect("Make OpenGL context current") + } + }; - let user_interface = build_user_interface( - &mut application, - Cache::default(), - &mut renderer, - viewport.logical_size(), - &mut debug, - ); + let clipboard = Clipboard::new(&context.window()); + let mut mouse_interaction = mouse::Interaction::default(); + let mut modifiers = glutin::event::ModifiersState::default(); - debug.draw_started(); - let mut primitive = user_interface.draw(&mut renderer); - debug.draw_finished(); + let physical_size = context.window().inner_size(); + let mut viewport = Viewport::with_physical_size( + Size::new(physical_size.width, physical_size.height), + context.window().scale_factor(), + ); + let mut resized = false; - let mut cache = Some(user_interface.into_cache()); - let mut events = Vec::new(); - let mut mouse_interaction = mouse::Interaction::default(); - let mut modifiers = glutin::event::ModifiersState::default(); - debug.startup_finished(); - - context.window().request_redraw(); - - event_loop.run(move |event, _, control_flow| match event { - event::Event::MainEventsCleared => { - if events.is_empty() && external_messages.is_empty() { - return; - } - - let mut user_interface = build_user_interface( - &mut application, - cache.take().unwrap(), - &mut renderer, - viewport.logical_size(), - &mut debug, - ); - - debug.event_processing_started(); - events - .iter() - .cloned() - .for_each(|event| runtime.broadcast(event)); - - let mut messages = user_interface.update( - events.drain(..), - clipboard - .as_ref() - .map(|c| c as &dyn iced_native::Clipboard), - &renderer, - ); - messages.extend(external_messages.drain(..)); - debug.event_processing_finished(); - - if messages.is_empty() { - debug.draw_started(); - primitive = user_interface.draw(&mut renderer); - debug.draw_finished(); - - cache = Some(user_interface.into_cache()); - } else { - // When there are messages, we are forced to rebuild twice - // for now :^) - let temp_cache = user_interface.into_cache(); - - for message in messages { - debug.log_message(&message); - - debug.update_started(); - let command = - runtime.enter(|| application.update(message)); - runtime.spawn(command); - debug.update_finished(); - } - - let subscription = application.subscription(); - runtime.track(subscription); - - // Update window title - let new_title = application.title(); - - if title != new_title { - context.window().set_title(&new_title); - - title = new_title; - } - - // Update window mode - let new_mode = application.mode(); - - if mode != new_mode { - context.window().set_fullscreen( - conversion::fullscreen( - context.window().current_monitor(), - new_mode, - ), - ); - - mode = new_mode; - } - - let user_interface = build_user_interface( - &mut application, - temp_cache, - &mut renderer, - viewport.logical_size(), - &mut debug, - ); - - debug.draw_started(); - primitive = user_interface.draw(&mut renderer); - debug.draw_finished(); - - cache = Some(user_interface.into_cache()); - } - - context.window().request_redraw(); - } - event::Event::UserEvent(message) => { - external_messages.push(message); - } - event::Event::RedrawRequested(_) => { - debug.render_started(); - - if resized { - let physical_size = viewport.physical_size(); - - context.resize(glutin::dpi::PhysicalSize { - width: physical_size.width, - height: physical_size.height, - }); - compositor.resize_viewport(physical_size); - - resized = false; - } - - let new_mouse_interaction = compositor.draw( - &mut renderer, - &viewport, - &primitive, - &debug.overlay(), - ); - - context.swap_buffers().expect("Swap buffers"); - debug.render_finished(); - - if new_mouse_interaction != mouse_interaction { - context.window().set_cursor_icon( - conversion::mouse_interaction(new_mouse_interaction), - ); - - mouse_interaction = new_mouse_interaction; - } - - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - event::Event::WindowEvent { - event: window_event, - .. - } => { - match window_event { - WindowEvent::Resized(new_size) => { - let size = Size::new(new_size.width, new_size.height); - - viewport = Viewport::with_physical_size( - size, - context.window().scale_factor(), - ); - resized = true; - } - WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } - WindowEvent::ModifiersChanged(new_modifiers) => { - modifiers = new_modifiers; - } - #[cfg(target_os = "macos")] - WindowEvent::KeyboardInput { - input: - glutin::event::KeyboardInput { - virtual_keycode: - Some(glutin::event::VirtualKeyCode::Q), - state: glutin::event::ElementState::Pressed, - .. - }, - .. - } if modifiers.logo() => { - *control_flow = ControlFlow::Exit; - } - #[cfg(feature = "debug")] - WindowEvent::KeyboardInput { - input: - glutin::event::KeyboardInput { - virtual_keycode: - Some(glutin::event::VirtualKeyCode::F12), - state: glutin::event::ElementState::Pressed, - .. - }, - .. - } => debug.toggle(), - _ => {} - } - - if let Some(event) = conversion::window_event( - &window_event, - viewport.scale_factor(), - modifiers, - ) { - events.push(event); - } - } - _ => { - *control_flow = ControlFlow::Wait; - } + #[allow(unsafe_code)] + let (mut compositor, mut renderer) = unsafe { + C::new(compositor_settings, |address| { + context.get_proc_address(address) }) - } -} - -fn build_user_interface<'a, A: Application>( - application: &'a mut A, - cache: Cache, - renderer: &mut ::Renderer, - size: Size, - debug: &mut Debug, -) -> UserInterface< - 'a, - A::Message, - ::Renderer, -> { - debug.view_started(); - let view = application.view(); - debug.view_finished(); - - debug.layout_started(); - let user_interface = UserInterface::build(view, size, cache, renderer); - debug.layout_finished(); - - user_interface + }; + + let mut state = program::State::new( + application, + viewport.logical_size(), + &mut renderer, + &mut debug, + ); + debug.startup_finished(); + + event_loop.run(move |event, _, control_flow| match event { + event::Event::MainEventsCleared => { + let command = runtime.enter(|| { + state.update( + clipboard.as_ref().map(|c| c as _), + viewport.logical_size(), + &mut renderer, + &mut debug, + ) + }); + + // If the application was updated + if let Some(command) = command { + runtime.spawn(command); + + let program = state.program(); + + // Update subscriptions + let subscription = program.subscription(); + runtime.track(subscription); + + // Update window title + let new_title = program.title(); + + if title != new_title { + context.window().set_title(&new_title); + + title = new_title; + } + + // Update window mode + let new_mode = program.mode(); + + if mode != new_mode { + context.window().set_fullscreen(conversion::fullscreen( + context.window().current_monitor(), + new_mode, + )); + + mode = new_mode; + } + } + + context.window().request_redraw(); + } + event::Event::UserEvent(message) => { + state.queue_message(message); + } + event::Event::RedrawRequested(_) => { + debug.render_started(); + + if resized { + let physical_size = viewport.physical_size(); + + context.resize(glutin::dpi::PhysicalSize::new( + physical_size.width, + physical_size.height, + )); + + compositor.resize_viewport(physical_size); + + resized = false; + } + + let new_mouse_interaction = compositor.draw( + &mut renderer, + &viewport, + state.primitive(), + &debug.overlay(), + ); + + context.swap_buffers().expect("Swap buffers"); + + debug.render_finished(); + + if new_mouse_interaction != mouse_interaction { + context.window().set_cursor_icon( + conversion::mouse_interaction(new_mouse_interaction), + ); + + mouse_interaction = new_mouse_interaction; + } + + // TODO: Handle animations! + // Maybe we can use `ControlFlow::WaitUntil` for this. + } + event::Event::WindowEvent { + event: window_event, + .. + } => { + application::handle_window_event( + &window_event, + context.window(), + control_flow, + &mut modifiers, + &mut viewport, + &mut resized, + &mut debug, + ); + + if let Some(event) = conversion::window_event( + &window_event, + viewport.scale_factor(), + modifiers, + ) { + state.queue_event(event.clone()); + runtime.broadcast(event); + } + } + _ => { + *control_flow = ControlFlow::Wait; + } + }) } diff --git a/glutin/src/lib.rs b/glutin/src/lib.rs index 2e2d03fc..829fe02a 100644 --- a/glutin/src/lib.rs +++ b/glutin/src/lib.rs @@ -7,7 +7,7 @@ #[doc(no_inline)] pub use iced_native::*; -mod application; +pub mod application; pub use application::Application; diff --git a/graphics/src/window/compositor.rs b/graphics/src/window/compositor.rs index 82faa6e1..d5920c95 100644 --- a/graphics/src/window/compositor.rs +++ b/graphics/src/window/compositor.rs @@ -5,7 +5,7 @@ use raw_window_handle::HasRawWindowHandle; /// A graphics compositor that can draw to windows. pub trait Compositor: Sized { /// The settings of the backend. - type Settings: Default + Clone; + type Settings: Default; /// The iced renderer of the backend. type Renderer: iced_native::Renderer; @@ -19,7 +19,7 @@ pub trait Compositor: Sized { /// Creates a new [`Backend`]. /// /// [`Backend`]: trait.Backend.html - fn new(settings: Self::Settings) -> Self; + fn new(settings: Self::Settings) -> (Self, Self::Renderer); /// Crates a new [`Surface`] for the given window. /// @@ -29,11 +29,6 @@ pub trait Compositor: Sized { window: &W, ) -> Self::Surface; - /// Crates a new [`Renderer`]. - /// - /// [`Renderer`]: #associatedtype.Renderer - fn create_renderer(&mut self, settings: Self::Settings) -> Self::Renderer; - /// Crates a new [`SwapChain`] for the given [`Surface`]. /// /// [`SwapChain`]: #associatedtype.SwapChain diff --git a/native/Cargo.toml b/native/Cargo.toml index 067b8708..75b4a56b 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -7,6 +7,9 @@ description = "A renderer-agnostic library for native GUIs" license = "MIT" repository = "https://github.com/hecrj/iced" +[features] +debug = [] + [dependencies] twox-hash = "1.5" unicode-segmentation = "1.6" diff --git a/winit/src/debug/basic.rs b/native/src/debug/basic.rs similarity index 100% rename from winit/src/debug/basic.rs rename to native/src/debug/basic.rs diff --git a/winit/src/debug/null.rs b/native/src/debug/null.rs similarity index 100% rename from winit/src/debug/null.rs rename to native/src/debug/null.rs diff --git a/native/src/lib.rs b/native/src/lib.rs index 9882803f..bea7d17e 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -34,7 +34,7 @@ //! [`window::Backend`]: window/trait.Backend.html //! [`UserInterface`]: struct.UserInterface.html //! [renderer]: renderer/index.html -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] @@ -42,6 +42,7 @@ pub mod keyboard; pub mod layout; pub mod mouse; +pub mod program; pub mod renderer; pub mod subscription; pub mod widget; @@ -54,6 +55,15 @@ mod hasher; mod runtime; mod user_interface; +// We disable debug capabilities on release builds unless the `debug` feature +// is explicitly enabled. +#[cfg(feature = "debug")] +#[path = "debug/basic.rs"] +mod debug; +#[cfg(not(feature = "debug"))] +#[path = "debug/null.rs"] +mod debug; + pub use iced_core::{ Align, Background, Color, Font, HorizontalAlignment, Length, Point, Rectangle, Size, Vector, VerticalAlignment, @@ -64,10 +74,12 @@ pub use iced_futures::{executor, futures, Command}; pub use executor::Executor; pub use clipboard::Clipboard; +pub use debug::Debug; pub use element::Element; pub use event::Event; pub use hasher::Hasher; pub use layout::Layout; +pub use program::Program; pub use renderer::Renderer; pub use runtime::Runtime; pub use subscription::Subscription; diff --git a/native/src/program.rs b/native/src/program.rs new file mode 100644 index 00000000..2652dee9 --- /dev/null +++ b/native/src/program.rs @@ -0,0 +1,39 @@ +//! Build interactive programs using The Elm Architecture. +use crate::{Command, Element, Renderer}; + +mod state; + +pub use state::State; + +/// An interactive, native cross-platform program. +pub trait Program: Sized { + /// The graphics backend to use to draw the [`Program`]. + /// + /// [`Program`]: trait.Program.html + type Renderer: Renderer; + + /// The type of __messages__ your [`Program`] will produce. + /// + /// [`Application`]: trait.Program.html + type Message: std::fmt::Debug + Send; + + /// Handles a __message__ and updates the state of the [`Program`]. + /// + /// 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 by shells. + /// + /// [`Program`]: trait.Application.html + /// [`Command`]: struct.Command.html + fn update(&mut self, message: Self::Message) -> Command; + + /// Returns the widgets to display in the [`Program`]. + /// + /// These widgets can produce __messages__ based on user interaction. + /// + /// [`Program`]: trait.Application.html + fn view(&mut self) -> Element<'_, Self::Message, Self::Renderer>; +} diff --git a/native/src/program/state.rs b/native/src/program/state.rs new file mode 100644 index 00000000..bcb7212d --- /dev/null +++ b/native/src/program/state.rs @@ -0,0 +1,154 @@ +use crate::{ + Cache, Clipboard, Command, Debug, Event, Program, Renderer, Size, + UserInterface, +}; + +#[allow(missing_debug_implementations)] +pub struct State

+where + P: Program + 'static, +{ + program: P, + cache: Option, + primitive: ::Output, + queued_events: Vec, + queued_messages: Vec, +} + +impl

State

+where + P: Program + 'static, +{ + pub fn new( + mut program: P, + bounds: Size, + renderer: &mut P::Renderer, + debug: &mut Debug, + ) -> Self { + let user_interface = build_user_interface( + &mut program, + Cache::default(), + renderer, + bounds, + debug, + ); + + debug.draw_started(); + let primitive = user_interface.draw(renderer); + debug.draw_finished(); + + let cache = Some(user_interface.into_cache()); + + State { + program, + cache, + primitive, + queued_events: Vec::new(), + queued_messages: Vec::new(), + } + } + + pub fn program(&self) -> &P { + &self.program + } + + pub fn primitive(&self) -> &::Output { + &self.primitive + } + + pub fn queue_event(&mut self, event: Event) { + self.queued_events.push(event); + } + + pub fn queue_message(&mut self, message: P::Message) { + self.queued_messages.push(message); + } + + pub fn update( + &mut self, + clipboard: Option<&dyn Clipboard>, + bounds: Size, + renderer: &mut P::Renderer, + debug: &mut Debug, + ) -> Option> { + if self.queued_events.is_empty() && self.queued_messages.is_empty() { + return None; + } + + let mut user_interface = build_user_interface( + &mut self.program, + self.cache.take().unwrap(), + renderer, + bounds, + debug, + ); + + debug.event_processing_started(); + let mut messages = user_interface.update( + self.queued_events.drain(..), + clipboard, + renderer, + ); + messages.extend(self.queued_messages.drain(..)); + debug.event_processing_finished(); + + if messages.is_empty() { + debug.draw_started(); + self.primitive = user_interface.draw(renderer); + debug.draw_finished(); + + self.cache = Some(user_interface.into_cache()); + + None + } else { + // When there are messages, we are forced to rebuild twice + // for now :^) + let temp_cache = user_interface.into_cache(); + + let commands = + Command::batch(messages.into_iter().map(|message| { + debug.log_message(&message); + + debug.update_started(); + let command = self.program.update(message); + debug.update_finished(); + + command + })); + + let user_interface = build_user_interface( + &mut self.program, + temp_cache, + renderer, + bounds, + debug, + ); + + debug.draw_started(); + self.primitive = user_interface.draw(renderer); + debug.draw_finished(); + + self.cache = Some(user_interface.into_cache()); + + Some(commands) + } + } +} + +fn build_user_interface<'a, P: Program>( + program: &'a mut P, + cache: Cache, + renderer: &mut P::Renderer, + size: Size, + debug: &mut Debug, +) -> UserInterface<'a, P::Message, P::Renderer> { + debug.view_started(); + let view = program.view(); + debug.view_finished(); + + debug.layout_started(); + let user_interface = UserInterface::build(view, size, cache, renderer); + debug.layout_finished(); + + user_interface +} diff --git a/src/application.rs b/src/application.rs index 644a4824..b6f2227e 100644 --- a/src/application.rs +++ b/src/application.rs @@ -198,10 +198,11 @@ pub trait Application: Sized { ..iced_glow::Settings::default() }; - as iced_glutin::Application>::run( - settings.into(), - glow_settings, - ); + iced_glutin::application::run::< + Instance, + Self::Executor, + iced_glow::window::Compositor, + >(settings.into(), glow_settings); } #[cfg(target_arch = "wasm32")] @@ -211,15 +212,29 @@ pub trait Application: Sized { struct Instance(A); +#[cfg(not(target_arch = "wasm32"))] +impl iced_glutin::Program for Instance +where + A: Application, +{ + type Renderer = iced_glow::Renderer; + type Message = A::Message; + + fn update(&mut self, message: Self::Message) -> Command { + self.0.update(message) + } + + fn view(&mut self) -> Element<'_, Self::Message> { + self.0.view() + } +} + #[cfg(not(target_arch = "wasm32"))] impl iced_glutin::Application for Instance where A: Application, { - type Compositor = iced_glow::window::Compositor; - type Executor = A::Executor; type Flags = A::Flags; - type Message = A::Message; fn new(flags: Self::Flags) -> (Self, Command) { let (app, command) = A::new(flags); @@ -238,17 +253,9 @@ where } } - fn update(&mut self, message: Self::Message) -> Command { - self.0.update(message) - } - fn subscription(&self) -> Subscription { self.0.subscription() } - - fn view(&mut self) -> Element<'_, Self::Message> { - self.0.view() - } } #[cfg(target_arch = "wasm32")] diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 7eba8924..600bc81c 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -18,8 +18,8 @@ impl iced_graphics::window::Compositor for Compositor { type Surface = wgpu::Surface; type SwapChain = wgpu::SwapChain; - fn new(settings: Self::Settings) -> Self { - let (device, queue) = futures::executor::block_on(async { + fn new(settings: Self::Settings) -> (Self, Renderer) { + let (mut device, queue) = futures::executor::block_on(async { let adapter = wgpu::Adapter::request( &wgpu::RequestAdapterOptions { power_preference: if settings.antialiasing.is_none() { @@ -44,15 +44,17 @@ impl iced_graphics::window::Compositor for Compositor { .await }); - Self { - device, - queue, - format: settings.format, - } - } + let renderer = + Renderer::new(crate::Backend::new(&mut device, settings)); - fn create_renderer(&mut self, settings: Settings) -> Renderer { - Renderer::new(crate::Backend::new(&mut self.device, settings)) + ( + Self { + device, + queue, + format: settings.format, + }, + renderer, + ) } fn create_surface( diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 1bf2d7b3..7fe83b96 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] -debug = [] +debug = ["iced_native/debug"] [dependencies] winit = "0.22" diff --git a/winit/src/application.rs b/winit/src/application.rs index ccab19f1..fcba47b3 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,9 +1,10 @@ use crate::{ - conversion, mouse, Cache, Clipboard, Command, Debug, Element, Executor, - Mode, Proxy, Runtime, Settings, Size, Subscription, UserInterface, + conversion, mouse, Clipboard, Command, Debug, Executor, Mode, Proxy, + Runtime, Settings, Size, Subscription, }; use iced_graphics::window; use iced_graphics::Viewport; +use iced_native::program::{self, Program}; /// An interactive, native cross-platform application. /// @@ -16,22 +17,7 @@ use iced_graphics::Viewport; /// /// When using an [`Application`] with the `debug` feature enabled, a debug view /// can be toggled by pressing `F12`. -pub trait Application: Sized { - /// The graphics backend to use to draw the [`Application`]. - /// - /// [`Application`]: trait.Application.html - type Compositor: window::Compositor; - - /// The [`Executor`] that will run commands and subscriptions. - /// - /// [`Executor`]: trait.Executor.html - type Executor: Executor; - - /// The type of __messages__ your [`Application`] will produce. - /// - /// [`Application`]: trait.Application.html - type Message: std::fmt::Debug + Send; - +pub trait Application: Program { /// The data needed to initialize your [`Application`]. /// /// [`Application`]: trait.Application.html @@ -60,18 +46,6 @@ pub trait Application: Sized { /// [`Application`]: trait.Application.html fn title(&self) -> String; - /// Handles a __message__ and updates the state of the [`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. - /// - /// [`Application`]: trait.Application.html - /// [`Command`]: struct.Command.html - fn update(&mut self, message: Self::Message) -> Command; - /// Returns the event `Subscription` for the current state of the /// application. /// @@ -85,19 +59,6 @@ pub trait Application: Sized { Subscription::none() } - /// Returns the widgets to display in the [`Application`]. - /// - /// These widgets can produce __messages__ based on user interaction. - /// - /// [`Application`]: trait.Application.html - fn view( - &mut self, - ) -> Element< - '_, - Self::Message, - ::Renderer, - >; - /// Returns the current [`Application`] mode. /// /// The runtime will automatically transition your application if a new mode @@ -109,309 +70,237 @@ pub trait Application: Sized { fn mode(&self) -> Mode { Mode::Windowed } +} - /// Runs the [`Application`] with the provided [`Settings`]. - /// - /// On native platforms, this method will take control of the current thread - /// and __will NOT return__. - /// - /// It should probably be that last thing you call in your `main` function. - /// - /// [`Application`]: trait.Application.html - /// [`Settings`]: struct.Settings.html - fn run( - settings: Settings, - backend_settings: ::Settings, - ) where - Self: 'static, - { - use iced_graphics::window::Compositor as _; - use winit::{ - event::{self, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - }; +pub fn run( + settings: Settings, + compositor_settings: C::Settings, +) where + A: Application + 'static, + E: Executor + 'static, + C: window::Compositor + 'static, +{ + use winit::{ + event, + event_loop::{ControlFlow, EventLoop}, + }; - let mut debug = Debug::new(); + let mut debug = Debug::new(); + debug.startup_started(); - debug.startup_started(); - let event_loop = EventLoop::with_user_event(); - let mut external_messages = Vec::new(); + let event_loop = EventLoop::with_user_event(); + let mut runtime = { + let executor = E::new().expect("Create executor"); + let proxy = Proxy::new(event_loop.create_proxy()); - let mut runtime = { - let executor = Self::Executor::new().expect("Create executor"); + Runtime::new(executor, proxy) + }; - Runtime::new(executor, Proxy::new(event_loop.create_proxy())) - }; + let flags = settings.flags; + let (application, init_command) = runtime.enter(|| A::new(flags)); + runtime.spawn(init_command); - let flags = settings.flags; - let (mut application, init_command) = - runtime.enter(|| Self::new(flags)); - runtime.spawn(init_command); + let subscription = application.subscription(); + runtime.track(subscription); - let subscription = application.subscription(); - runtime.track(subscription); + let mut title = application.title(); + let mut mode = application.mode(); - let mut title = application.title(); - let mut mode = application.mode(); + let window = settings + .window + .into_builder(&title, mode, event_loop.primary_monitor()) + .build(&event_loop) + .expect("Open window"); - let window = settings - .window - .into_builder(&title, mode, event_loop.primary_monitor()) - .build(&event_loop) - .expect("Open window"); + let clipboard = Clipboard::new(&window); + let mut mouse_interaction = mouse::Interaction::default(); + let mut modifiers = winit::event::ModifiersState::default(); - let physical_size = window.inner_size(); - let mut viewport = Viewport::with_physical_size( - Size::new(physical_size.width, physical_size.height), - window.scale_factor(), - ); - let mut resized = false; + let physical_size = window.inner_size(); + let mut viewport = Viewport::with_physical_size( + Size::new(physical_size.width, physical_size.height), + window.scale_factor(), + ); + let mut resized = false; - let clipboard = Clipboard::new(&window); - let mut compositor = Self::Compositor::new(backend_settings.clone()); + let (mut compositor, mut renderer) = C::new(compositor_settings); - let surface = compositor.create_surface(&window); - let mut renderer = compositor.create_renderer(backend_settings); + let surface = compositor.create_surface(&window); - let mut swap_chain = compositor.create_swap_chain( - &surface, - physical_size.width, - physical_size.height, - ); + let mut swap_chain = compositor.create_swap_chain( + &surface, + physical_size.width, + physical_size.height, + ); - let user_interface = build_user_interface( - &mut application, - Cache::default(), - &mut renderer, - viewport.logical_size(), - &mut debug, - ); + let mut state = program::State::new( + application, + viewport.logical_size(), + &mut renderer, + &mut debug, + ); + debug.startup_finished(); - debug.draw_started(); - let mut primitive = user_interface.draw(&mut renderer); - debug.draw_finished(); - - let mut cache = Some(user_interface.into_cache()); - let mut events = Vec::new(); - let mut mouse_interaction = mouse::Interaction::default(); - let mut modifiers = winit::event::ModifiersState::default(); - debug.startup_finished(); - - window.request_redraw(); - - event_loop.run(move |event, _, control_flow| match event { - event::Event::MainEventsCleared => { - if events.is_empty() && external_messages.is_empty() { - return; - } - - let mut user_interface = build_user_interface( - &mut application, - cache.take().unwrap(), - &mut renderer, + event_loop.run(move |event, _, control_flow| match event { + event::Event::MainEventsCleared => { + let command = runtime.enter(|| { + state.update( + clipboard.as_ref().map(|c| c as _), viewport.logical_size(), - &mut debug, - ); - - debug.event_processing_started(); - events - .iter() - .cloned() - .for_each(|event| runtime.broadcast(event)); - - let mut messages = user_interface.update( - events.drain(..), - clipboard - .as_ref() - .map(|c| c as &dyn iced_native::Clipboard), - &renderer, - ); - messages.extend(external_messages.drain(..)); - debug.event_processing_finished(); - - if messages.is_empty() { - debug.draw_started(); - primitive = user_interface.draw(&mut renderer); - debug.draw_finished(); - - cache = Some(user_interface.into_cache()); - } else { - // When there are messages, we are forced to rebuild twice - // for now :^) - let temp_cache = user_interface.into_cache(); - - for message in messages { - log::debug!("Updating"); - - debug.log_message(&message); - - debug.update_started(); - let command = - runtime.enter(|| application.update(message)); - runtime.spawn(command); - debug.update_finished(); - } - - let subscription = application.subscription(); - runtime.track(subscription); - - // Update window title - let new_title = application.title(); - - if title != new_title { - window.set_title(&new_title); - - title = new_title; - } - - // Update window mode - let new_mode = application.mode(); - - if mode != new_mode { - window.set_fullscreen(conversion::fullscreen( - window.current_monitor(), - new_mode, - )); - - mode = new_mode; - } - - let user_interface = build_user_interface( - &mut application, - temp_cache, - &mut renderer, - viewport.logical_size(), - &mut debug, - ); - - debug.draw_started(); - primitive = user_interface.draw(&mut renderer); - debug.draw_finished(); - - cache = Some(user_interface.into_cache()); - } - - window.request_redraw(); - } - event::Event::UserEvent(message) => { - external_messages.push(message); - } - event::Event::RedrawRequested(_) => { - debug.render_started(); - - if resized { - let physical_size = viewport.physical_size(); - - swap_chain = compositor.create_swap_chain( - &surface, - physical_size.width, - physical_size.height, - ); - - resized = false; - } - - let new_mouse_interaction = compositor.draw( &mut renderer, - &mut swap_chain, - &viewport, - &primitive, - &debug.overlay(), - ); + &mut debug, + ) + }); - debug.render_finished(); + // If the application was updated + if let Some(command) = command { + runtime.spawn(command); - if new_mouse_interaction != mouse_interaction { - window.set_cursor_icon(conversion::mouse_interaction( - new_mouse_interaction, + let program = state.program(); + + // Update subscriptions + let subscription = program.subscription(); + runtime.track(subscription); + + // Update window title + let new_title = program.title(); + + if title != new_title { + window.set_title(&new_title); + + title = new_title; + } + + // Update window mode + let new_mode = program.mode(); + + if mode != new_mode { + window.set_fullscreen(conversion::fullscreen( + window.current_monitor(), + new_mode, )); - mouse_interaction = new_mouse_interaction; - } - - // TODO: Handle animations! - // Maybe we can use `ControlFlow::WaitUntil` for this. - } - event::Event::WindowEvent { - event: window_event, - .. - } => { - match window_event { - WindowEvent::Resized(new_size) => { - let size = Size::new(new_size.width, new_size.height); - - viewport = Viewport::with_physical_size( - size, - window.scale_factor(), - ); - resized = true; - } - WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } - WindowEvent::ModifiersChanged(new_modifiers) => { - modifiers = new_modifiers; - } - #[cfg(target_os = "macos")] - WindowEvent::KeyboardInput { - input: - winit::event::KeyboardInput { - virtual_keycode: - Some(winit::event::VirtualKeyCode::Q), - state: winit::event::ElementState::Pressed, - .. - }, - .. - } if modifiers.logo() => { - *control_flow = ControlFlow::Exit; - } - #[cfg(feature = "debug")] - WindowEvent::KeyboardInput { - input: - winit::event::KeyboardInput { - virtual_keycode: - Some(winit::event::VirtualKeyCode::F12), - state: winit::event::ElementState::Pressed, - .. - }, - .. - } => debug.toggle(), - _ => {} - } - - if let Some(event) = conversion::window_event( - &window_event, - viewport.scale_factor(), - modifiers, - ) { - events.push(event); + mode = new_mode; } } - _ => { - *control_flow = ControlFlow::Wait; + + window.request_redraw(); + } + event::Event::UserEvent(message) => { + state.queue_message(message); + } + event::Event::RedrawRequested(_) => { + debug.render_started(); + + if resized { + let physical_size = viewport.physical_size(); + + swap_chain = compositor.create_swap_chain( + &surface, + physical_size.width, + physical_size.height, + ); + + resized = false; } - }) + + let new_mouse_interaction = compositor.draw( + &mut renderer, + &mut swap_chain, + &viewport, + state.primitive(), + &debug.overlay(), + ); + + debug.render_finished(); + + if new_mouse_interaction != mouse_interaction { + window.set_cursor_icon(conversion::mouse_interaction( + new_mouse_interaction, + )); + + mouse_interaction = new_mouse_interaction; + } + + // TODO: Handle animations! + // Maybe we can use `ControlFlow::WaitUntil` for this. + } + event::Event::WindowEvent { + event: window_event, + .. + } => { + handle_window_event( + &window_event, + &window, + control_flow, + &mut modifiers, + &mut viewport, + &mut resized, + &mut debug, + ); + + if let Some(event) = conversion::window_event( + &window_event, + viewport.scale_factor(), + modifiers, + ) { + state.queue_event(event.clone()); + runtime.broadcast(event); + } + } + _ => { + *control_flow = ControlFlow::Wait; + } + }) +} + +pub fn handle_window_event( + event: &winit::event::WindowEvent<'_>, + window: &winit::window::Window, + control_flow: &mut winit::event_loop::ControlFlow, + modifiers: &mut winit::event::ModifiersState, + viewport: &mut Viewport, + resized: &mut bool, + _debug: &mut Debug, +) { + use winit::{event::WindowEvent, event_loop::ControlFlow}; + + match event { + WindowEvent::Resized(new_size) => { + let size = Size::new(new_size.width, new_size.height); + + *viewport = + Viewport::with_physical_size(size, window.scale_factor()); + *resized = true; + } + WindowEvent::CloseRequested => { + *control_flow = ControlFlow::Exit; + } + WindowEvent::ModifiersChanged(new_modifiers) => { + *modifiers = *new_modifiers; + } + #[cfg(target_os = "macos")] + WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + virtual_keycode: Some(winit::event::VirtualKeyCode::Q), + state: winit::event::ElementState::Pressed, + .. + }, + .. + } if modifiers.logo() => { + *control_flow = ControlFlow::Exit; + } + #[cfg(feature = "debug")] + WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + virtual_keycode: Some(winit::event::VirtualKeyCode::F12), + state: winit::event::ElementState::Pressed, + .. + }, + .. + } => _debug.toggle(), + _ => {} } } - -fn build_user_interface<'a, A: Application>( - application: &'a mut A, - cache: Cache, - renderer: &mut ::Renderer, - size: Size, - debug: &mut Debug, -) -> UserInterface< - 'a, - A::Message, - ::Renderer, -> { - debug.view_started(); - let view = application.view(); - debug.view_finished(); - - debug.layout_started(); - let user_interface = UserInterface::build(view, size, cache, renderer); - debug.layout_finished(); - - user_interface -} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 9cf5dc0d..d3af9ae9 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -25,26 +25,18 @@ pub use iced_native::*; pub use winit; +pub mod application; pub mod conversion; pub mod settings; -mod application; mod clipboard; mod mode; mod proxy; -// We disable debug capabilities on release builds unless the `debug` feature -// is explicitly enabled. -#[cfg(feature = "debug")] -#[path = "debug/basic.rs"] -mod debug; -#[cfg(not(feature = "debug"))] -#[path = "debug/null.rs"] -mod debug; - pub use application::Application; pub use clipboard::Clipboard; -pub use debug::Debug; pub use mode::Mode; pub use proxy::Proxy; pub use settings::Settings; + +pub use iced_graphics::Viewport; From d54f17c6aa1cc90d97c63781e7d06af1b919ef0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 21 May 2020 04:54:26 +0200 Subject: [PATCH 15/40] Simplify `integration` example with `Program` --- examples/integration/src/controls.rs | 77 ++++++++++----------- examples/integration/src/main.rs | 100 ++++++++------------------- examples/integration/src/scene.rs | 6 +- 3 files changed, 67 insertions(+), 116 deletions(-) diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index 0999336b..81c35072 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -1,11 +1,11 @@ -use crate::Scene; - use iced_wgpu::Renderer; use iced_winit::{ - slider, Align, Color, Column, Element, Length, Row, Slider, Text, + slider, Align, Color, Column, Command, Element, Length, Program, Row, + Slider, Text, }; pub struct Controls { + background_color: Color, sliders: [slider::State; 3], } @@ -17,58 +17,55 @@ pub enum Message { impl Controls { pub fn new() -> Controls { Controls { + background_color: Color::BLACK, sliders: Default::default(), } } - pub fn update(&self, message: Message, scene: &mut Scene) { + pub fn background_color(&self) -> Color { + self.background_color + } +} + +impl Program for Controls { + type Renderer = Renderer; + type Message = Message; + + fn update(&mut self, message: Message) -> Command { match message { Message::BackgroundColorChanged(color) => { - scene.background_color = color; + self.background_color = color; } } + + Command::none() } - pub fn view(&mut self, scene: &Scene) -> Element { + fn view(&mut self) -> Element { let [r, g, b] = &mut self.sliders; - let background_color = scene.background_color; + let background_color = self.background_color; let sliders = Row::new() .width(Length::Units(500)) .spacing(20) - .push(Slider::new( - r, - 0.0..=1.0, - scene.background_color.r, - move |r| { - Message::BackgroundColorChanged(Color { - r, - ..background_color - }) - }, - )) - .push(Slider::new( - g, - 0.0..=1.0, - scene.background_color.g, - move |g| { - Message::BackgroundColorChanged(Color { - g, - ..background_color - }) - }, - )) - .push(Slider::new( - b, - 0.0..=1.0, - scene.background_color.b, - move |b| { - Message::BackgroundColorChanged(Color { - b, - ..background_color - }) - }, - )); + .push(Slider::new(r, 0.0..=1.0, background_color.r, move |r| { + Message::BackgroundColorChanged(Color { + r, + ..background_color + }) + })) + .push(Slider::new(g, 0.0..=1.0, background_color.g, move |g| { + Message::BackgroundColorChanged(Color { + g, + ..background_color + }) + })) + .push(Slider::new(b, 0.0..=1.0, background_color.b, move |b| { + Message::BackgroundColorChanged(Color { + b, + ..background_color + }) + })); Row::new() .width(Length::Fill) diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 8d16ed56..c1fe8f69 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -4,10 +4,8 @@ mod scene; use controls::Controls; use scene::Scene; -use iced_wgpu::{wgpu, Backend, Primitive, Renderer, Settings, Viewport}; -use iced_winit::{ - futures, mouse, winit, Cache, Clipboard, Size, UserInterface, -}; +use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport}; +use iced_winit::{futures, program, winit, Debug, Size}; use winit::{ event::{Event, ModifiersState, WindowEvent}, @@ -28,8 +26,7 @@ pub fn main() { ); let mut modifiers = ModifiersState::default(); - // Initialize WGPU - + // Initialize wgpu let surface = wgpu::Surface::create(&window); let (mut device, queue) = futures::executor::block_on(async { let adapter = wgpu::Adapter::request( @@ -70,17 +67,21 @@ pub fn main() { }; let mut resized = false; + // Initialize scene and GUI controls + let scene = Scene::new(&mut device); + let controls = Controls::new(); + // Initialize iced - let mut events = Vec::new(); - let mut cache = Some(Cache::default()); + let mut debug = Debug::new(); let mut renderer = Renderer::new(Backend::new(&mut device, Settings::default())); - let mut output = (Primitive::None, mouse::Interaction::default()); - let clipboard = Clipboard::new(&window); - // Initialize scene and GUI controls - let mut scene = Scene::new(&device); - let mut controls = Controls::new(); + let mut state = program::State::new( + controls, + viewport.logical_size(), + &mut renderer, + &mut debug, + ); // Run event loop event_loop.run(move |event, _, control_flow| { @@ -114,69 +115,18 @@ pub fn main() { window.scale_factor(), modifiers, ) { - events.push(event); + state.queue_event(event); } } Event::MainEventsCleared => { - // If no relevant events happened, we can simply skip this - if events.is_empty() { - return; - } - - // We need to: - // 1. Process events of our user interface. - // 2. Update state as a result of any interaction. - // 3. Generate a new output for our renderer. - - // First, we build our user interface. - let mut user_interface = UserInterface::build( - controls.view(&scene), + // We update iced + let _ = state.update( + None, viewport.logical_size(), - cache.take().unwrap(), &mut renderer, + &mut debug, ); - // Then, we process the events, obtaining messages in return. - let messages = user_interface.update( - events.drain(..), - clipboard.as_ref().map(|c| c as _), - &renderer, - ); - - let user_interface = if messages.is_empty() { - // If there are no messages, no interactions we care about have - // happened. We can simply leave our user interface as it is. - user_interface - } else { - // If there are messages, we need to update our state - // accordingly and rebuild our user interface. - // We can only do this if we drop our user interface first - // by turning it into its cache. - cache = Some(user_interface.into_cache()); - - // In this example, `Controls` is the only part that cares - // about messages, so updating our state is pretty - // straightforward. - for message in messages { - controls.update(message, &mut scene); - } - - // Once the state has been changed, we rebuild our updated - // user interface. - UserInterface::build( - controls.view(&scene), - viewport.logical_size(), - cache.take().unwrap(), - &mut renderer, - ) - }; - - // Finally, we just need to draw a new output for our renderer, - output = user_interface.draw(&mut renderer); - - // update our cache, - cache = Some(user_interface.into_cache()); - // and request a redraw window.request_redraw(); } @@ -203,7 +153,13 @@ pub fn main() { ); // We draw the scene first - scene.draw(&mut encoder, &frame.view); + let program = state.program(); + + scene.draw( + &mut encoder, + &frame.view, + program.background_color(), + ); // And then iced on top let mouse_interaction = renderer.backend_mut().draw( @@ -211,8 +167,8 @@ pub fn main() { &mut encoder, &frame.view, &viewport, - &output, - &["Some debug information!"], + state.primitive(), + &debug.overlay(), ); // Then we submit the work diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs index 22c6812a..b79a7ff4 100644 --- a/examples/integration/src/scene.rs +++ b/examples/integration/src/scene.rs @@ -2,7 +2,6 @@ use iced_wgpu::wgpu; use iced_winit::Color; pub struct Scene { - pub background_color: Color, pipeline: wgpu::RenderPipeline, bind_group: wgpu::BindGroup, } @@ -12,7 +11,6 @@ impl Scene { let (pipeline, bind_group) = build_pipeline(device); Scene { - background_color: Color::BLACK, pipeline, bind_group, } @@ -22,6 +20,7 @@ impl Scene { &self, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, + background_color: Color, ) { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { @@ -32,8 +31,7 @@ impl Scene { load_op: wgpu::LoadOp::Clear, store_op: wgpu::StoreOp::Store, clear_color: { - let [r, g, b, a] = - self.background_color.into_linear(); + let [r, g, b, a] = background_color.into_linear(); wgpu::Color { r: r as f64, From 60dcfc354e844757d2291bf44cb21c624bc270c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 21 May 2020 19:07:33 +0200 Subject: [PATCH 16/40] Draft `triangle` pipeline in `iced_glow` --- glow/src/lib.rs | 1 + glow/src/program.rs | 39 ++++++ glow/src/quad.rs | 41 +----- glow/src/shader/triangle.frag | 8 ++ glow/src/shader/triangle.vert | 13 ++ glow/src/triangle.rs | 251 ++++++++++++++++++++++++++++++++-- 6 files changed, 301 insertions(+), 52 deletions(-) create mode 100644 glow/src/program.rs create mode 100644 glow/src/shader/triangle.frag create mode 100644 glow/src/shader/triangle.vert diff --git a/glow/src/lib.rs b/glow/src/lib.rs index d40ed0ae..a32c787e 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -5,6 +5,7 @@ #![forbid(rust_2018_idioms)] mod backend; +mod program; mod quad; mod text; mod triangle; diff --git a/glow/src/program.rs b/glow/src/program.rs new file mode 100644 index 00000000..489a194f --- /dev/null +++ b/glow/src/program.rs @@ -0,0 +1,39 @@ +use glow::HasContext; + +pub unsafe fn create( + gl: &glow::Context, + shader_sources: &[(u32, &str)], +) -> ::Program { + let program = gl.create_program().expect("Cannot create program"); + + let mut shaders = Vec::with_capacity(shader_sources.len()); + + for (shader_type, shader_source) in shader_sources.iter() { + let shader = gl + .create_shader(*shader_type) + .expect("Cannot create shader"); + + gl.shader_source(shader, shader_source); + gl.compile_shader(shader); + + if !gl.get_shader_compile_status(shader) { + panic!(gl.get_shader_info_log(shader)); + } + + gl.attach_shader(program, shader); + + shaders.push(shader); + } + + gl.link_program(program); + if !gl.get_program_link_status(program) { + panic!(gl.get_program_info_log(program)); + } + + for shader in shaders { + gl.detach_shader(program, shader); + gl.delete_shader(shader); + } + + program +} diff --git a/glow/src/quad.rs b/glow/src/quad.rs index acac3219..3a051268 100644 --- a/glow/src/quad.rs +++ b/glow/src/quad.rs @@ -1,3 +1,4 @@ +use crate::program; use crate::Transformation; use glow::HasContext; use iced_graphics::layer; @@ -18,7 +19,7 @@ pub struct Pipeline { impl Pipeline { pub fn new(gl: &glow::Context) -> Pipeline { let program = unsafe { - create_program( + program::create( gl, &[ (glow::VERTEX_SHADER, include_str!("shader/quad.vert")), @@ -138,44 +139,6 @@ impl Pipeline { } } -unsafe fn create_program( - gl: &glow::Context, - shader_sources: &[(u32, &str)], -) -> ::Program { - let program = gl.create_program().expect("Cannot create program"); - - let mut shaders = Vec::with_capacity(shader_sources.len()); - - for (shader_type, shader_source) in shader_sources.iter() { - let shader = gl - .create_shader(*shader_type) - .expect("Cannot create shader"); - - gl.shader_source(shader, shader_source); - gl.compile_shader(shader); - - if !gl.get_shader_compile_status(shader) { - panic!(gl.get_shader_info_log(shader)); - } - - gl.attach_shader(program, shader); - - shaders.push(shader); - } - - gl.link_program(program); - if !gl.get_program_link_status(program) { - panic!(gl.get_program_info_log(program)); - } - - for shader in shaders { - gl.detach_shader(program, shader); - gl.delete_shader(shader); - } - - program -} - unsafe fn create_instance_buffer( gl: &glow::Context, size: usize, diff --git a/glow/src/shader/triangle.frag b/glow/src/shader/triangle.frag new file mode 100644 index 00000000..e39c45e7 --- /dev/null +++ b/glow/src/shader/triangle.frag @@ -0,0 +1,8 @@ +#version 450 + +layout(location = 0) in vec4 i_Color; +layout(location = 0) out vec4 o_Color; + +void main() { + o_Color = i_Color; +} diff --git a/glow/src/shader/triangle.vert b/glow/src/shader/triangle.vert new file mode 100644 index 00000000..cfa4e995 --- /dev/null +++ b/glow/src/shader/triangle.vert @@ -0,0 +1,13 @@ +#version 450 + +layout(location = 0) uniform mat4 u_Transform; + +layout(location = 0) in vec2 i_Position; +layout(location = 1) in vec4 i_Color; + +layout(location = 0) out vec4 o_Color; + +void main() { + gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); + o_Color = i_Color; +} diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs index 5836f0cf..3f4aaa1b 100644 --- a/glow/src/triangle.rs +++ b/glow/src/triangle.rs @@ -1,22 +1,106 @@ //! Draw meshes of triangles. -use crate::{settings, Transformation}; +use crate::program; +use crate::settings; +use crate::Transformation; +use glow::HasContext; use iced_graphics::layer; +use std::marker::PhantomData; -pub use iced_graphics::triangle::Mesh2D; +pub use iced_graphics::triangle::{Mesh2D, Vertex2D}; -const UNIFORM_BUFFER_SIZE: usize = 100; const VERTEX_BUFFER_SIZE: usize = 10_000; const INDEX_BUFFER_SIZE: usize = 10_000; #[derive(Debug)] -pub(crate) struct Pipeline {} +pub(crate) struct Pipeline { + program: ::Program, + vertex_array: ::VertexArray, + vertices: Buffer, + indices: Buffer, + current_transform: Transformation, +} impl Pipeline { pub fn new( gl: &glow::Context, antialiasing: Option, ) -> Pipeline { - Pipeline {} + let program = unsafe { + program::create( + gl, + &[ + (glow::VERTEX_SHADER, include_str!("shader/triangle.vert")), + ( + glow::FRAGMENT_SHADER, + include_str!("shader/triangle.frag"), + ), + ], + ) + }; + + unsafe { + gl.use_program(Some(program)); + + gl.uniform_matrix_4_f32_slice( + Some(0), + false, + &Transformation::identity().into(), + ); + + gl.use_program(None); + } + + let vertex_array = + unsafe { gl.create_vertex_array().expect("Create vertex array") }; + + unsafe { + gl.bind_vertex_array(Some(vertex_array)); + } + + let vertices = unsafe { + Buffer::new( + gl, + glow::ARRAY_BUFFER, + glow::DYNAMIC_DRAW, + VERTEX_BUFFER_SIZE, + ) + }; + + let indices = unsafe { + Buffer::new( + gl, + glow::ELEMENT_ARRAY_BUFFER, + glow::DYNAMIC_DRAW, + INDEX_BUFFER_SIZE, + ) + }; + + unsafe { + let stride = std::mem::size_of::() as i32; + + gl.enable_vertex_attrib_array(0); + gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0); + + gl.enable_vertex_attrib_array(1); + gl.vertex_attrib_pointer_f32( + 1, + 4, + glow::FLOAT, + false, + stride, + 4 * 2, + ); + + gl.bind_vertex_array(None); + } + + Pipeline { + program, + vertex_array, + vertices, + indices, + current_transform: Transformation::identity(), + } } pub fn draw( @@ -28,6 +112,106 @@ impl Pipeline { scale_factor: f32, meshes: &[layer::Mesh<'_>], ) { + unsafe { + gl.enable(glow::SCISSOR_TEST); + gl.use_program(Some(self.program)); + gl.bind_vertex_array(Some(self.vertex_array)); + } + + // This looks a bit crazy, but we are just counting how many vertices + // and indices we will need to handle. + // TODO: Improve readability + let (total_vertices, total_indices) = meshes + .iter() + .map(|layer::Mesh { buffers, .. }| { + (buffers.vertices.len(), buffers.indices.len()) + }) + .fold((0, 0), |(total_v, total_i), (v, i)| { + (total_v + v, total_i + i) + }); + + // Then we ensure the current buffers are big enough, resizing if + // necessary + unsafe { + self.vertices.bind(gl, total_vertices); + self.indices.bind(gl, total_indices); + } + + // We upload all the vertices and indices upfront + let mut last_vertex = 0; + let mut last_index = 0; + + for layer::Mesh { buffers, .. } in meshes { + unsafe { + gl.buffer_sub_data_u8_slice( + glow::ARRAY_BUFFER, + (last_vertex * std::mem::size_of::()) as i32, + bytemuck::cast_slice(&buffers.vertices), + ); + + gl.buffer_sub_data_u8_slice( + glow::ELEMENT_ARRAY_BUFFER, + (last_index * std::mem::size_of::()) as i32, + bytemuck::cast_slice(&buffers.indices), + ); + + last_vertex += buffers.vertices.len(); + last_index += buffers.indices.len(); + } + } + + // Then we draw each mesh using offsets + let mut last_vertex = 0; + let mut last_index = 0; + + for layer::Mesh { + buffers, + origin, + clip_bounds, + } in meshes + { + let transform = + transformation * Transformation::translate(origin.x, origin.y); + + let clip_bounds = (*clip_bounds * scale_factor).round(); + + unsafe { + if self.current_transform != transform { + gl.uniform_matrix_4_f32_slice( + Some(0), + false, + &transform.into(), + ); + + self.current_transform = transform; + } + + gl.scissor( + clip_bounds.x as i32, + (target_height - (clip_bounds.y + clip_bounds.height)) + as i32, + clip_bounds.width as i32, + clip_bounds.height as i32, + ); + + gl.draw_elements_base_vertex( + glow::TRIANGLES, + buffers.indices.len() as i32, + glow::UNSIGNED_INT, + (last_index * std::mem::size_of::()) as i32, + last_vertex as i32, + ); + + last_vertex += buffers.vertices.len(); + last_index += buffers.indices.len(); + } + } + + unsafe { + gl.bind_vertex_array(None); + gl.use_program(None); + gl.disable(glow::SCISSOR_TEST); + } } } @@ -35,18 +219,15 @@ impl Pipeline { #[derive(Debug, Clone, Copy)] struct Uniforms { transform: [f32; 16], - // We need to align this to 256 bytes to please `wgpu`... - // TODO: Be smarter and stop wasting memory! - _padding_a: [f32; 32], - _padding_b: [f32; 16], } +unsafe impl bytemuck::Zeroable for Uniforms {} +unsafe impl bytemuck::Pod for Uniforms {} + impl Default for Uniforms { fn default() -> Self { Self { transform: *Transformation::identity().as_ref(), - _padding_a: [0.0; 32], - _padding_b: [0.0; 16], } } } @@ -55,8 +236,52 @@ impl From for Uniforms { fn from(transformation: Transformation) -> Uniforms { Self { transform: transformation.into(), - _padding_a: [0.0; 32], - _padding_b: [0.0; 16], + } + } +} + +#[derive(Debug)] +struct Buffer { + raw: ::Buffer, + target: u32, + usage: u32, + size: usize, + phantom: PhantomData, +} + +impl Buffer { + pub unsafe fn new( + gl: &glow::Context, + target: u32, + usage: u32, + size: usize, + ) -> Self { + let raw = gl.create_buffer().expect("Create buffer"); + + let mut buffer = Buffer { + raw, + target, + usage, + size: 0, + phantom: PhantomData, + }; + + buffer.bind(gl, size); + + buffer + } + + pub unsafe fn bind(&mut self, gl: &glow::Context, size: usize) { + gl.bind_buffer(self.target, Some(self.raw)); + + if self.size < size { + gl.buffer_data_size( + self.target, + (size * std::mem::size_of::()) as i32, + self.usage, + ); + + self.size = size; } } } From 2798d4935e14a2453adc9e85c1037cac3b79a8c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 21 May 2020 19:08:04 +0200 Subject: [PATCH 17/40] Remove unused `overlay_font` in `iced_glow` --- glow/src/text.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/glow/src/text.rs b/glow/src/text.rs index 952fd2cd..0a11b64f 100644 --- a/glow/src/text.rs +++ b/glow/src/text.rs @@ -36,9 +36,13 @@ impl Pipeline { let (brush_builder, measure_brush) = load_glyph_brush(default_font) .unwrap_or_else(|_: glow_glyph::rusttype::Error| { - log::warn!("System font failed to load. Falling back to embedded font..."); + log::warn!( + "System font failed to load. \ + Falling back to embedded font..." + ); - load_glyph_brush(font::FALLBACK.to_vec()).expect("Load fallback font") + load_glyph_brush(font::FALLBACK.to_vec()) + .expect("Load fallback font") }); let draw_brush = @@ -52,10 +56,6 @@ impl Pipeline { } } - pub fn overlay_font(&self) -> glow_glyph::FontId { - glow_glyph::FontId(0) - } - pub fn queue(&mut self, section: glow_glyph::Section<'_>) { self.draw_brush.borrow_mut().queue(section); } From bbfb1c040c92e36b3d23a2167ad3432c819b9668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 21 May 2020 19:50:53 +0200 Subject: [PATCH 18/40] Update to latest `glow` --- glow/Cargo.toml | 5 ++++- glow/src/quad.rs | 23 +++++++++-------------- glow/src/triangle.rs | 14 ++++---------- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/glow/Cargo.toml b/glow/Cargo.toml index 148f4fd5..f57831bd 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -14,12 +14,15 @@ svg = [] [dependencies] euclid = "0.20" -glow = "0.4" bytemuck = "1.2" glam = "0.8" log = "0.4" glyph_brush = "0.6" +[dependencies.glow] +git = "https://github.com/grovesNL/glow" +rev = "722a850e972a69c3012fcb3687758eacbdac2823" + [dependencies.iced_native] version = "0.2" path = "../native" diff --git a/glow/src/quad.rs b/glow/src/quad.rs index 3a051268..c2fd08a2 100644 --- a/glow/src/quad.rs +++ b/glow/src/quad.rs @@ -31,13 +31,11 @@ impl Pipeline { unsafe { gl.use_program(Some(program)); - gl.uniform_matrix_4_f32_slice( - Some(0), - false, - &Transformation::identity().into(), - ); - gl.uniform_1_f32(Some(1), 1.0); - gl.uniform_1_f32(Some(2), 0.0); + let matrix: [f32; 16] = Transformation::identity().into(); + gl.uniform_matrix_4_f32_slice(Some(&0), false, &matrix); + + gl.uniform_1_f32(Some(&1), 1.0); + gl.uniform_1_f32(Some(&2), 0.0); gl.use_program(None); } @@ -80,11 +78,8 @@ impl Pipeline { if transformation != self.current_transform { unsafe { - gl.uniform_matrix_4_f32_slice( - Some(0), - false, - &transformation.into(), - ); + let matrix: [f32; 16] = transformation.into(); + gl.uniform_matrix_4_f32_slice(Some(&0), false, &matrix); self.current_transform = transformation; } @@ -92,7 +87,7 @@ impl Pipeline { if scale != self.current_scale { unsafe { - gl.uniform_1_f32(Some(1), scale); + gl.uniform_1_f32(Some(&1), scale); } self.current_scale = scale; @@ -100,7 +95,7 @@ impl Pipeline { if target_height != self.current_target_height { unsafe { - gl.uniform_1_f32(Some(2), target_height as f32); + gl.uniform_1_f32(Some(&2), target_height as f32); } self.current_target_height = target_height; diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs index 3f4aaa1b..489ceaff 100644 --- a/glow/src/triangle.rs +++ b/glow/src/triangle.rs @@ -41,11 +41,8 @@ impl Pipeline { unsafe { gl.use_program(Some(program)); - gl.uniform_matrix_4_f32_slice( - Some(0), - false, - &Transformation::identity().into(), - ); + let transform: [f32; 16] = Transformation::identity().into(); + gl.uniform_matrix_4_f32_slice(Some(&0), false, &transform); gl.use_program(None); } @@ -177,11 +174,8 @@ impl Pipeline { unsafe { if self.current_transform != transform { - gl.uniform_matrix_4_f32_slice( - Some(0), - false, - &transform.into(), - ); + let matrix: [f32; 16] = transform.into(); + gl.uniform_matrix_4_f32_slice(Some(&0), false, &matrix); self.current_transform = transform; } From c5545c7a7306b5312007ffa74a014db4992ff5f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 22 May 2020 01:14:31 +0200 Subject: [PATCH 19/40] Implement MSAA for triangle meshes in `iced_glow` --- glow/src/triangle.rs | 228 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 191 insertions(+), 37 deletions(-) diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs index 489ceaff..f350db98 100644 --- a/glow/src/triangle.rs +++ b/glow/src/triangle.rs @@ -4,6 +4,7 @@ use crate::settings; use crate::Transformation; use glow::HasContext; use iced_graphics::layer; +use iced_graphics::Size; use std::marker::PhantomData; pub use iced_graphics::triangle::{Mesh2D, Vertex2D}; @@ -18,6 +19,7 @@ pub(crate) struct Pipeline { vertices: Buffer, indices: Buffer, current_transform: Transformation, + antialias: Antialias, } impl Pipeline { @@ -97,6 +99,7 @@ impl Pipeline { vertices, indices, current_transform: Transformation::identity(), + antialias: Antialias::new(antialiasing), } } @@ -157,49 +160,57 @@ impl Pipeline { } } - // Then we draw each mesh using offsets - let mut last_vertex = 0; - let mut last_index = 0; + let Self { + antialias, + current_transform, + .. + } = self; - for layer::Mesh { - buffers, - origin, - clip_bounds, - } in meshes - { - let transform = - transformation * Transformation::translate(origin.x, origin.y); + // Then we draw each mesh using offsets with antialiasing + antialias.perform(gl, Size::new(target_width, target_height), |gl| { + let mut last_vertex = 0; + let mut last_index = 0; - let clip_bounds = (*clip_bounds * scale_factor).round(); + for layer::Mesh { + buffers, + origin, + clip_bounds, + } in meshes + { + let transform = transformation + * Transformation::translate(origin.x, origin.y); - unsafe { - if self.current_transform != transform { - let matrix: [f32; 16] = transform.into(); - gl.uniform_matrix_4_f32_slice(Some(&0), false, &matrix); + let clip_bounds = (*clip_bounds * scale_factor).round(); - self.current_transform = transform; + unsafe { + if *current_transform != transform { + let matrix: [f32; 16] = transform.into(); + gl.uniform_matrix_4_f32_slice(Some(&0), false, &matrix); + + *current_transform = transform; + } + + gl.scissor( + clip_bounds.x as i32, + (target_height - (clip_bounds.y + clip_bounds.height)) + as i32, + clip_bounds.width as i32, + clip_bounds.height as i32, + ); + + gl.draw_elements_base_vertex( + glow::TRIANGLES, + buffers.indices.len() as i32, + glow::UNSIGNED_INT, + (last_index * std::mem::size_of::()) as i32, + last_vertex as i32, + ); + + last_vertex += buffers.vertices.len(); + last_index += buffers.indices.len(); } - - gl.scissor( - clip_bounds.x as i32, - (target_height - (clip_bounds.y + clip_bounds.height)) - as i32, - clip_bounds.width as i32, - clip_bounds.height as i32, - ); - - gl.draw_elements_base_vertex( - glow::TRIANGLES, - buffers.indices.len() as i32, - glow::UNSIGNED_INT, - (last_index * std::mem::size_of::()) as i32, - last_vertex as i32, - ); - - last_vertex += buffers.vertices.len(); - last_index += buffers.indices.len(); } - } + }); unsafe { gl.bind_vertex_array(None); @@ -279,3 +290,146 @@ impl Buffer { } } } + +#[derive(Debug)] +pub struct Antialias { + renderbuffer: Option, + sample_count: u32, +} + +impl Antialias { + fn new(antialiasing: Option) -> Self { + Antialias { + renderbuffer: None, + sample_count: antialiasing + .map(settings::Antialiasing::sample_count) + .unwrap_or(1), + } + } + + fn perform( + &mut self, + gl: &glow::Context, + size: Size, + f: impl FnOnce(&glow::Context), + ) { + if self.sample_count == 1 { + return f(gl); + } + + let target = glow::DRAW_FRAMEBUFFER; + + let renderbuffer = if let Some(renderbuffer) = self.renderbuffer.take() + { + if size == renderbuffer.size { + renderbuffer + } else { + renderbuffer.destroy(gl); + + Renderbuffer::new(gl, target, self.sample_count, size) + } + } else { + Renderbuffer::new(gl, target, self.sample_count, size) + }; + + renderbuffer.bind(gl, target); + + unsafe { + gl.clear_color(0.0, 0.0, 0.0, 0.0); + gl.clear(glow::COLOR_BUFFER_BIT); + } + + f(gl); + + unsafe { + gl.bind_framebuffer(target, None); + gl.clear_color(1.0, 1.0, 1.0, 1.0); + } + + renderbuffer.blit(gl); + + self.renderbuffer = Some(renderbuffer); + } +} + +#[derive(Debug)] +pub struct Renderbuffer { + raw: ::Renderbuffer, + framebuffer: ::Framebuffer, + size: Size, +} + +impl Renderbuffer { + fn new( + gl: &glow::Context, + target: u32, + sample_count: u32, + size: Size, + ) -> Self { + let framebuffer = unsafe { + gl.create_framebuffer().expect("Create MSAA framebuffer") + }; + + let raw = unsafe { + gl.create_renderbuffer().expect("Create MSAA renderbuffer") + }; + + unsafe { + gl.bind_renderbuffer(glow::RENDERBUFFER, Some(raw)); + gl.renderbuffer_storage_multisample( + glow::RENDERBUFFER, + sample_count as i32, + glow::SRGB8_ALPHA8, + size.width as i32, + size.height as i32, + ); + + gl.bind_framebuffer(target, Some(framebuffer)); + gl.framebuffer_renderbuffer( + target, + glow::COLOR_ATTACHMENT0, + glow::RENDERBUFFER, + Some(raw), + ); + gl.bind_framebuffer(target, None); + } + + Self { + raw, + framebuffer, + size, + } + } + + fn bind(&self, gl: &glow::Context, target: u32) { + unsafe { + gl.bind_framebuffer(target, Some(self.framebuffer)); + } + } + + fn blit(&self, gl: &glow::Context) { + unsafe { + self.bind(gl, glow::READ_FRAMEBUFFER); + + gl.blit_framebuffer( + 0, + 0, + self.size.width as i32, + self.size.height as i32, + 0, + 0, + self.size.width as i32, + self.size.height as i32, + glow::COLOR_BUFFER_BIT, + glow::NEAREST, + ); + } + } + + fn destroy(self, gl: &glow::Context) { + unsafe { + gl.delete_renderbuffer(self.raw); + gl.delete_framebuffer(self.framebuffer); + } + } +} From 1b287cddaf1951bbd65e60996eea6d356c131c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 22 May 2020 05:35:36 +0200 Subject: [PATCH 20/40] Use git repository for `glow_glyph` --- glow/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/glow/Cargo.toml b/glow/Cargo.toml index f57831bd..d1f1e3d7 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -33,4 +33,5 @@ path = "../graphics" features = ["font-source", "font-fallback", "font-icons", "opengl"] [dependencies.glow_glyph] -path = "../../glow_glyph" +git = "https://github.com/hecrj/glow_glyph" +rev = "f027ffa49962d78ac85e282c635e848bef785ee9" From 6f71a8e3d5e47ab05653315b0d44b35af6a20338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 22 May 2020 05:52:11 +0200 Subject: [PATCH 21/40] Use `get_uniform_location` for wider compatibility --- glow/Cargo.toml | 2 +- glow/src/quad.rs | 41 ++++++++++++++++++++++++++++++----- glow/src/shader/quad.frag | 28 ++++++++++++------------ glow/src/shader/quad.vert | 34 ++++++++++++++--------------- glow/src/shader/triangle.frag | 9 ++++---- glow/src/shader/triangle.vert | 8 +++---- glow/src/triangle.rs | 19 ++++++++++++++-- 7 files changed, 93 insertions(+), 48 deletions(-) diff --git a/glow/Cargo.toml b/glow/Cargo.toml index d1f1e3d7..b0d244f0 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -34,4 +34,4 @@ features = ["font-source", "font-fallback", "font-icons", "opengl"] [dependencies.glow_glyph] git = "https://github.com/hecrj/glow_glyph" -rev = "f027ffa49962d78ac85e282c635e848bef785ee9" +rev = "8ec7982d9e0ce828769d4ba7abe73b0b0ec22db6" diff --git a/glow/src/quad.rs b/glow/src/quad.rs index c2fd08a2..a8fbb9e5 100644 --- a/glow/src/quad.rs +++ b/glow/src/quad.rs @@ -11,6 +11,9 @@ pub struct Pipeline { program: ::Program, vertex_array: ::VertexArray, instances: ::Buffer, + transform_location: ::UniformLocation, + scale_location: ::UniformLocation, + screen_height_location: ::UniformLocation, current_transform: Transformation, current_scale: f32, current_target_height: u32, @@ -28,14 +31,30 @@ impl Pipeline { ) }; + let transform_location = + unsafe { gl.get_uniform_location(program, "u_Transform") } + .expect("Get transform location"); + + let scale_location = + unsafe { gl.get_uniform_location(program, "u_Scale") } + .expect("Get scale location"); + + let screen_height_location = + unsafe { gl.get_uniform_location(program, "u_ScreenHeight") } + .expect("Get target height location"); + unsafe { gl.use_program(Some(program)); let matrix: [f32; 16] = Transformation::identity().into(); - gl.uniform_matrix_4_f32_slice(Some(&0), false, &matrix); + gl.uniform_matrix_4_f32_slice( + Some(&transform_location), + false, + &matrix, + ); - gl.uniform_1_f32(Some(&1), 1.0); - gl.uniform_1_f32(Some(&2), 0.0); + gl.uniform_1_f32(Some(&scale_location), 1.0); + gl.uniform_1_f32(Some(&screen_height_location), 0.0); gl.use_program(None); } @@ -47,6 +66,9 @@ impl Pipeline { program, vertex_array, instances, + transform_location, + scale_location, + screen_height_location, current_transform: Transformation::identity(), current_scale: 1.0, current_target_height: 0, @@ -79,7 +101,11 @@ impl Pipeline { if transformation != self.current_transform { unsafe { let matrix: [f32; 16] = transformation.into(); - gl.uniform_matrix_4_f32_slice(Some(&0), false, &matrix); + gl.uniform_matrix_4_f32_slice( + Some(&self.transform_location), + false, + &matrix, + ); self.current_transform = transformation; } @@ -87,7 +113,7 @@ impl Pipeline { if scale != self.current_scale { unsafe { - gl.uniform_1_f32(Some(&1), scale); + gl.uniform_1_f32(Some(&self.scale_location), scale); } self.current_scale = scale; @@ -95,7 +121,10 @@ impl Pipeline { if target_height != self.current_target_height { unsafe { - gl.uniform_1_f32(Some(&2), target_height as f32); + gl.uniform_1_f32( + Some(&self.screen_height_location), + target_height as f32, + ); } self.current_target_height = target_height; diff --git a/glow/src/shader/quad.frag b/glow/src/shader/quad.frag index 17e7216f..cea36bdc 100644 --- a/glow/src/shader/quad.frag +++ b/glow/src/shader/quad.frag @@ -1,15 +1,15 @@ -#version 450 +#version 330 -layout(location = 2) uniform float u_Screen_Height; +uniform float u_ScreenHeight; -layout(location = 0) in vec4 v_Color; -layout(location = 1) in vec4 v_BorderColor; -layout(location = 2) in vec2 v_Pos; -layout(location = 3) in vec2 v_Scale; -layout(location = 4) in float v_BorderRadius; -layout(location = 5) in float v_BorderWidth; +in vec4 v_Color; +in vec4 v_BorderColor; +in vec2 v_Pos; +in vec2 v_Scale; +in float v_BorderRadius; +in float v_BorderWidth; -layout(location = 0) out vec4 o_Color; +out vec4 o_Color; float distance(in vec2 frag_coord, in vec2 position, in vec2 size, float radius) { @@ -22,8 +22,8 @@ float distance(in vec2 frag_coord, in vec2 position, in vec2 size, float radius) vec2 bottom_right_distance = frag_coord - bottom_right; vec2 distance = vec2( - max(max(top_left_distance.x, bottom_right_distance.x), 0), - max(max(top_left_distance.y, bottom_right_distance.y), 0) + max(max(top_left_distance.x, bottom_right_distance.x), 0.0), + max(max(top_left_distance.y, bottom_right_distance.y), 0.0) ); return sqrt(distance.x * distance.x + distance.y * distance.y); @@ -32,11 +32,11 @@ float distance(in vec2 frag_coord, in vec2 position, in vec2 size, float radius) void main() { vec4 mixed_color; - vec2 fragCoord = vec2(gl_FragCoord.x, u_Screen_Height - gl_FragCoord.y); + vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y); // TODO: Remove branching (?) if(v_BorderWidth > 0) { - float internal_border = max(v_BorderRadius - v_BorderWidth, 0); + float internal_border = max(v_BorderRadius - v_BorderWidth, 0.0); float internal_distance = distance( fragCoord, @@ -64,7 +64,7 @@ void main() { ); float radius_alpha = - 1.0 - smoothstep(max(v_BorderRadius - 0.5, 0), v_BorderRadius + 0.5, d); + 1.0 - smoothstep(max(v_BorderRadius - 0.5, 0.0), v_BorderRadius + 0.5, d); o_Color = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); } diff --git a/glow/src/shader/quad.vert b/glow/src/shader/quad.vert index 2d2ebc3d..d37b5c8d 100644 --- a/glow/src/shader/quad.vert +++ b/glow/src/shader/quad.vert @@ -1,7 +1,7 @@ -#version 450 +#version 330 -layout(location = 0) uniform mat4 u_Transform; -layout(location = 1) uniform float u_Scale; +uniform mat4 u_Transform; +uniform float u_Scale; layout(location = 0) in vec2 i_Pos; layout(location = 1) in vec2 i_Scale; @@ -10,12 +10,12 @@ layout(location = 3) in vec4 i_BorderColor; layout(location = 4) in float i_BorderRadius; layout(location = 5) in float i_BorderWidth; -layout(location = 0) out vec4 o_Color; -layout(location = 1) out vec4 o_BorderColor; -layout(location = 2) out vec2 o_Pos; -layout(location = 3) out vec2 o_Scale; -layout(location = 4) out float o_BorderRadius; -layout(location = 5) out float o_BorderWidth; +out vec4 v_Color; +out vec4 v_BorderColor; +out vec2 v_Pos; +out vec2 v_Scale; +out float v_BorderRadius; +out float v_BorderWidth; const vec2 positions[4] = vec2[]( vec2(0.0, 0.0), @@ -25,7 +25,7 @@ const vec2 positions[4] = vec2[]( ); void main() { - vec2 v_Pos = positions[gl_VertexID]; + vec2 q_Pos = positions[gl_VertexID]; vec2 p_Pos = i_Pos * u_Scale; vec2 p_Scale = i_Scale * u_Scale; @@ -36,12 +36,12 @@ void main() { vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0) ); - o_Color = i_Color; - o_BorderColor = i_BorderColor; - o_Pos = p_Pos; - o_Scale = p_Scale; - o_BorderRadius = i_BorderRadius * u_Scale; - o_BorderWidth = i_BorderWidth * u_Scale; + v_Color = i_Color; + v_BorderColor = i_BorderColor; + v_Pos = p_Pos; + v_Scale = p_Scale; + v_BorderRadius = i_BorderRadius * u_Scale; + v_BorderWidth = i_BorderWidth * u_Scale; - gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0); + gl_Position = u_Transform * i_Transform * vec4(q_Pos, 0.0, 1.0); } diff --git a/glow/src/shader/triangle.frag b/glow/src/shader/triangle.frag index e39c45e7..d186784a 100644 --- a/glow/src/shader/triangle.frag +++ b/glow/src/shader/triangle.frag @@ -1,8 +1,9 @@ -#version 450 +#version 330 -layout(location = 0) in vec4 i_Color; -layout(location = 0) out vec4 o_Color; +in vec4 v_Color; + +out vec4 o_Color; void main() { - o_Color = i_Color; + o_Color = v_Color; } diff --git a/glow/src/shader/triangle.vert b/glow/src/shader/triangle.vert index cfa4e995..5723436a 100644 --- a/glow/src/shader/triangle.vert +++ b/glow/src/shader/triangle.vert @@ -1,13 +1,13 @@ -#version 450 +#version 330 -layout(location = 0) uniform mat4 u_Transform; +uniform mat4 u_Transform; layout(location = 0) in vec2 i_Position; layout(location = 1) in vec4 i_Color; -layout(location = 0) out vec4 o_Color; +out vec4 v_Color; void main() { gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); - o_Color = i_Color; + v_Color = i_Color; } diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs index f350db98..8bafa9c6 100644 --- a/glow/src/triangle.rs +++ b/glow/src/triangle.rs @@ -18,6 +18,7 @@ pub(crate) struct Pipeline { vertex_array: ::VertexArray, vertices: Buffer, indices: Buffer, + transform_location: ::UniformLocation, current_transform: Transformation, antialias: Antialias, } @@ -40,11 +41,19 @@ impl Pipeline { ) }; + let transform_location = + unsafe { gl.get_uniform_location(program, "u_Transform") } + .expect("Get transform location"); + unsafe { gl.use_program(Some(program)); let transform: [f32; 16] = Transformation::identity().into(); - gl.uniform_matrix_4_f32_slice(Some(&0), false, &transform); + gl.uniform_matrix_4_f32_slice( + Some(&transform_location), + false, + &transform, + ); gl.use_program(None); } @@ -98,6 +107,7 @@ impl Pipeline { vertex_array, vertices, indices, + transform_location, current_transform: Transformation::identity(), antialias: Antialias::new(antialiasing), } @@ -163,6 +173,7 @@ impl Pipeline { let Self { antialias, current_transform, + transform_location, .. } = self; @@ -185,7 +196,11 @@ impl Pipeline { unsafe { if *current_transform != transform { let matrix: [f32; 16] = transform.into(); - gl.uniform_matrix_4_f32_slice(Some(&0), false, &matrix); + gl.uniform_matrix_4_f32_slice( + Some(transform_location), + false, + &matrix, + ); *current_transform = transform; } From 1dd79c4697ce39589bea84142334b3cbd242fb59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 22 May 2020 19:15:39 +0200 Subject: [PATCH 22/40] Use built-in OpenGL multisampling in `iced_glow` --- glow/src/backend.rs | 6 +- glow/src/triangle.rs | 244 +++++---------------------- glow/src/window/compositor.rs | 12 +- glutin/src/application.rs | 1 + graphics/src/window/gl_compositor.rs | 2 + wgpu/src/triangle.rs | 6 +- 6 files changed, 61 insertions(+), 210 deletions(-) diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 8c578d5e..c98aa5fe 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -26,8 +26,7 @@ impl Backend { pub fn new(gl: &glow::Context, settings: Settings) -> Self { let text_pipeline = text::Pipeline::new(gl, settings.default_font); let quad_pipeline = quad::Pipeline::new(gl); - let triangle_pipeline = - triangle::Pipeline::new(gl, settings.antialiasing); + let triangle_pipeline = triangle::Pipeline::new(gl); Self { quad_pipeline, @@ -56,7 +55,6 @@ impl Backend { scale_factor, projection, &layer, - viewport_size.width, viewport_size.height, ); } @@ -70,7 +68,6 @@ impl Backend { scale_factor: f32, transformation: Transformation, layer: &Layer<'_>, - target_width: u32, target_height: u32, ) { let mut bounds = (layer.bounds * scale_factor).round(); @@ -93,7 +90,6 @@ impl Backend { self.triangle_pipeline.draw( gl, - target_width, target_height, scaled, scale_factor, diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs index 8bafa9c6..325359de 100644 --- a/glow/src/triangle.rs +++ b/glow/src/triangle.rs @@ -1,10 +1,8 @@ //! Draw meshes of triangles. use crate::program; -use crate::settings; use crate::Transformation; use glow::HasContext; use iced_graphics::layer; -use iced_graphics::Size; use std::marker::PhantomData; pub use iced_graphics::triangle::{Mesh2D, Vertex2D}; @@ -20,14 +18,10 @@ pub(crate) struct Pipeline { indices: Buffer, transform_location: ::UniformLocation, current_transform: Transformation, - antialias: Antialias, } impl Pipeline { - pub fn new( - gl: &glow::Context, - antialiasing: Option, - ) -> Pipeline { + pub fn new(gl: &glow::Context) -> Pipeline { let program = unsafe { program::create( gl, @@ -109,20 +103,19 @@ impl Pipeline { indices, transform_location, current_transform: Transformation::identity(), - antialias: Antialias::new(antialiasing), } } pub fn draw( &mut self, gl: &glow::Context, - target_width: u32, target_height: u32, transformation: Transformation, scale_factor: f32, meshes: &[layer::Mesh<'_>], ) { unsafe { + gl.enable(glow::MULTISAMPLE); gl.enable(glow::SCISSOR_TEST); gl.use_program(Some(self.program)); gl.bind_vertex_array(Some(self.vertex_array)); @@ -170,67 +163,59 @@ impl Pipeline { } } - let Self { - antialias, - current_transform, - transform_location, - .. - } = self; + // Then we draw each mesh using offsets + let mut last_vertex = 0; + let mut last_index = 0; - // Then we draw each mesh using offsets with antialiasing - antialias.perform(gl, Size::new(target_width, target_height), |gl| { - let mut last_vertex = 0; - let mut last_index = 0; + for layer::Mesh { + buffers, + origin, + clip_bounds, + } in meshes + { + let transform = + transformation * Transformation::translate(origin.x, origin.y); - for layer::Mesh { - buffers, - origin, - clip_bounds, - } in meshes - { - let transform = transformation - * Transformation::translate(origin.x, origin.y); + let clip_bounds = (*clip_bounds * scale_factor).round(); - let clip_bounds = (*clip_bounds * scale_factor).round(); - - unsafe { - if *current_transform != transform { - let matrix: [f32; 16] = transform.into(); - gl.uniform_matrix_4_f32_slice( - Some(transform_location), - false, - &matrix, - ); - - *current_transform = transform; - } - - gl.scissor( - clip_bounds.x as i32, - (target_height - (clip_bounds.y + clip_bounds.height)) - as i32, - clip_bounds.width as i32, - clip_bounds.height as i32, + unsafe { + if self.current_transform != transform { + let matrix: [f32; 16] = transform.into(); + gl.uniform_matrix_4_f32_slice( + Some(&self.transform_location), + false, + &matrix, ); - gl.draw_elements_base_vertex( - glow::TRIANGLES, - buffers.indices.len() as i32, - glow::UNSIGNED_INT, - (last_index * std::mem::size_of::()) as i32, - last_vertex as i32, - ); - - last_vertex += buffers.vertices.len(); - last_index += buffers.indices.len(); + self.current_transform = transform; } + + gl.scissor( + clip_bounds.x as i32, + (target_height - (clip_bounds.y + clip_bounds.height)) + as i32, + clip_bounds.width as i32, + clip_bounds.height as i32, + ); + + gl.draw_elements_base_vertex( + glow::TRIANGLES, + buffers.indices.len() as i32, + glow::UNSIGNED_INT, + (last_index * std::mem::size_of::()) as i32, + last_vertex as i32, + ); + + last_vertex += buffers.vertices.len(); + last_index += buffers.indices.len(); } - }); + } unsafe { gl.bind_vertex_array(None); gl.use_program(None); gl.disable(glow::SCISSOR_TEST); + gl.disable(glow::MULTISAMPLE); } } } @@ -305,146 +290,3 @@ impl Buffer { } } } - -#[derive(Debug)] -pub struct Antialias { - renderbuffer: Option, - sample_count: u32, -} - -impl Antialias { - fn new(antialiasing: Option) -> Self { - Antialias { - renderbuffer: None, - sample_count: antialiasing - .map(settings::Antialiasing::sample_count) - .unwrap_or(1), - } - } - - fn perform( - &mut self, - gl: &glow::Context, - size: Size, - f: impl FnOnce(&glow::Context), - ) { - if self.sample_count == 1 { - return f(gl); - } - - let target = glow::DRAW_FRAMEBUFFER; - - let renderbuffer = if let Some(renderbuffer) = self.renderbuffer.take() - { - if size == renderbuffer.size { - renderbuffer - } else { - renderbuffer.destroy(gl); - - Renderbuffer::new(gl, target, self.sample_count, size) - } - } else { - Renderbuffer::new(gl, target, self.sample_count, size) - }; - - renderbuffer.bind(gl, target); - - unsafe { - gl.clear_color(0.0, 0.0, 0.0, 0.0); - gl.clear(glow::COLOR_BUFFER_BIT); - } - - f(gl); - - unsafe { - gl.bind_framebuffer(target, None); - gl.clear_color(1.0, 1.0, 1.0, 1.0); - } - - renderbuffer.blit(gl); - - self.renderbuffer = Some(renderbuffer); - } -} - -#[derive(Debug)] -pub struct Renderbuffer { - raw: ::Renderbuffer, - framebuffer: ::Framebuffer, - size: Size, -} - -impl Renderbuffer { - fn new( - gl: &glow::Context, - target: u32, - sample_count: u32, - size: Size, - ) -> Self { - let framebuffer = unsafe { - gl.create_framebuffer().expect("Create MSAA framebuffer") - }; - - let raw = unsafe { - gl.create_renderbuffer().expect("Create MSAA renderbuffer") - }; - - unsafe { - gl.bind_renderbuffer(glow::RENDERBUFFER, Some(raw)); - gl.renderbuffer_storage_multisample( - glow::RENDERBUFFER, - sample_count as i32, - glow::SRGB8_ALPHA8, - size.width as i32, - size.height as i32, - ); - - gl.bind_framebuffer(target, Some(framebuffer)); - gl.framebuffer_renderbuffer( - target, - glow::COLOR_ATTACHMENT0, - glow::RENDERBUFFER, - Some(raw), - ); - gl.bind_framebuffer(target, None); - } - - Self { - raw, - framebuffer, - size, - } - } - - fn bind(&self, gl: &glow::Context, target: u32) { - unsafe { - gl.bind_framebuffer(target, Some(self.framebuffer)); - } - } - - fn blit(&self, gl: &glow::Context) { - unsafe { - self.bind(gl, glow::READ_FRAMEBUFFER); - - gl.blit_framebuffer( - 0, - 0, - self.size.width as i32, - self.size.height as i32, - 0, - 0, - self.size.width as i32, - self.size.height as i32, - glow::COLOR_BUFFER_BIT, - glow::NEAREST, - ); - } - } - - fn destroy(self, gl: &glow::Context) { - unsafe { - gl.delete_renderbuffer(self.raw); - gl.delete_framebuffer(self.framebuffer); - } - } -} diff --git a/glow/src/window/compositor.rs b/glow/src/window/compositor.rs index 1117166f..2f504ff7 100644 --- a/glow/src/window/compositor.rs +++ b/glow/src/window/compositor.rs @@ -2,7 +2,7 @@ use crate::{Backend, Renderer, Settings, Viewport}; use core::ffi::c_void; use glow::HasContext; -use iced_graphics::Size; +use iced_graphics::{Antialiasing, Size}; use iced_native::mouse; /// A window graphics backend for iced powered by `glow`. @@ -30,11 +30,21 @@ impl iced_graphics::window::GLCompositor for Compositor { gl.enable(glow::BLEND); gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); + // Disable multisampling by default + gl.disable(glow::MULTISAMPLE); + let renderer = Renderer::new(Backend::new(&gl, settings)); (Self { gl }, renderer) } + fn sample_count(settings: &Settings) -> u32 { + settings + .antialiasing + .map(Antialiasing::sample_count) + .unwrap_or(0) + } + fn resize_viewport(&mut self, physical_size: Size) { unsafe { self.gl.viewport( diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 3bb7478a..2e6f34af 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -51,6 +51,7 @@ pub fn run( let context = ContextBuilder::new() .with_vsync(true) + .with_multisampling(C::sample_count(&compositor_settings) as u16) .build_windowed(builder, &event_loop) .expect("Open window"); diff --git a/graphics/src/window/gl_compositor.rs b/graphics/src/window/gl_compositor.rs index 979d891e..aea898e3 100644 --- a/graphics/src/window/gl_compositor.rs +++ b/graphics/src/window/gl_compositor.rs @@ -12,6 +12,8 @@ pub trait GLCompositor: Sized { loader_function: impl FnMut(&str) -> *const c_void, ) -> (Self, Self::Renderer); + fn sample_count(settings: &Self::Settings) -> u32; + fn resize_viewport(&mut self, physical_size: Size); fn draw>( diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index dc58a52a..22a27143 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -170,9 +170,9 @@ impl Pipeline { ], }], }, - sample_count: antialiasing - .map(|a| a.sample_count()) - .unwrap_or(1), + sample_count: u32::from( + antialiasing.map(|a| a.sample_count()).unwrap_or(1), + ), sample_mask: !0, alpha_to_coverage_enabled: false, }); From d6bf8955dbca03898e379aae376d91677bb4d223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 22 May 2020 19:17:07 +0200 Subject: [PATCH 23/40] Use published `glow` and `glow_glyph` versions --- glow/Cargo.toml | 10 ++-------- glow/src/quad.rs | 12 ++++++------ glow/src/triangle.rs | 4 ++-- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/glow/Cargo.toml b/glow/Cargo.toml index b0d244f0..53952608 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -13,16 +13,14 @@ image = [] svg = [] [dependencies] +glow = "0.4" +glow_glyph = "0.1" euclid = "0.20" bytemuck = "1.2" glam = "0.8" log = "0.4" glyph_brush = "0.6" -[dependencies.glow] -git = "https://github.com/grovesNL/glow" -rev = "722a850e972a69c3012fcb3687758eacbdac2823" - [dependencies.iced_native] version = "0.2" path = "../native" @@ -31,7 +29,3 @@ path = "../native" version = "0.1" path = "../graphics" features = ["font-source", "font-fallback", "font-icons", "opengl"] - -[dependencies.glow_glyph] -git = "https://github.com/hecrj/glow_glyph" -rev = "8ec7982d9e0ce828769d4ba7abe73b0b0ec22db6" diff --git a/glow/src/quad.rs b/glow/src/quad.rs index a8fbb9e5..3a65338a 100644 --- a/glow/src/quad.rs +++ b/glow/src/quad.rs @@ -48,13 +48,13 @@ impl Pipeline { let matrix: [f32; 16] = Transformation::identity().into(); gl.uniform_matrix_4_f32_slice( - Some(&transform_location), + Some(transform_location), false, &matrix, ); - gl.uniform_1_f32(Some(&scale_location), 1.0); - gl.uniform_1_f32(Some(&screen_height_location), 0.0); + gl.uniform_1_f32(Some(scale_location), 1.0); + gl.uniform_1_f32(Some(screen_height_location), 0.0); gl.use_program(None); } @@ -102,7 +102,7 @@ impl Pipeline { unsafe { let matrix: [f32; 16] = transformation.into(); gl.uniform_matrix_4_f32_slice( - Some(&self.transform_location), + Some(self.transform_location), false, &matrix, ); @@ -113,7 +113,7 @@ impl Pipeline { if scale != self.current_scale { unsafe { - gl.uniform_1_f32(Some(&self.scale_location), scale); + gl.uniform_1_f32(Some(self.scale_location), scale); } self.current_scale = scale; @@ -122,7 +122,7 @@ impl Pipeline { if target_height != self.current_target_height { unsafe { gl.uniform_1_f32( - Some(&self.screen_height_location), + Some(self.screen_height_location), target_height as f32, ); } diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs index 325359de..ee7faf83 100644 --- a/glow/src/triangle.rs +++ b/glow/src/triangle.rs @@ -44,7 +44,7 @@ impl Pipeline { let transform: [f32; 16] = Transformation::identity().into(); gl.uniform_matrix_4_f32_slice( - Some(&transform_location), + Some(transform_location), false, &transform, ); @@ -182,7 +182,7 @@ impl Pipeline { if self.current_transform != transform { let matrix: [f32; 16] = transform.into(); gl.uniform_matrix_4_f32_slice( - Some(&self.transform_location), + Some(self.transform_location), false, &matrix, ); From 334dd098170d56c004d7a97bdfbbffe6f63f0ebd Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Tue, 26 May 2020 16:11:49 -0500 Subject: [PATCH 24/40] Pane Grid spacing applied prior to rounding On low-DPI screens, the rounding order of operations made it impossible to produce an odd-pixel spacing. Specifying 1, for instance, produced zero space between panes. This approach subtracts half the spacing from the first pane prior to rounding and uses the whole spacing for the second pane size and coordinate. --- native/src/widget/pane_grid/axis.rs | 24 +++++++++++++----------- native/src/widget/pane_grid/node.rs | 22 ++++++++++------------ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs index f0e3f362..7181f9bf 100644 --- a/native/src/widget/pane_grid/axis.rs +++ b/native/src/widget/pane_grid/axis.rs @@ -14,37 +14,39 @@ impl Axis { &self, rectangle: &Rectangle, ratio: f32, - halved_spacing: f32, + spacing: f32, ) -> (Rectangle, Rectangle) { match self { Axis::Horizontal => { - let height_top = (rectangle.height * ratio).round(); - let height_bottom = rectangle.height - height_top; + let height_top = + (rectangle.height * ratio - spacing / 2.0).round(); + let height_bottom = rectangle.height - height_top - spacing; ( Rectangle { - height: height_top - halved_spacing, + height: height_top, ..*rectangle }, Rectangle { - y: rectangle.y + height_top + halved_spacing, - height: height_bottom - halved_spacing, + y: rectangle.y + height_top + spacing, + height: height_bottom, ..*rectangle }, ) } Axis::Vertical => { - let width_left = (rectangle.width * ratio).round(); - let width_right = rectangle.width - width_left; + let width_left = + (rectangle.width * ratio - spacing / 2.0).round(); + let width_right = rectangle.width - width_left - spacing; ( Rectangle { - width: width_left - halved_spacing, + width: width_left, ..*rectangle }, Rectangle { - x: rectangle.x + width_left + halved_spacing, - width: width_right - halved_spacing, + x: rectangle.x + width_left + spacing, + width: width_right, ..*rectangle }, ) diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs index 723ec393..b13c5e26 100644 --- a/native/src/widget/pane_grid/node.rs +++ b/native/src/widget/pane_grid/node.rs @@ -56,7 +56,7 @@ impl Node { let mut regions = HashMap::new(); self.compute_regions( - spacing / 2.0, + spacing, &Rectangle { x: 0.0, y: 0.0, @@ -83,7 +83,7 @@ impl Node { let mut splits = HashMap::new(); self.compute_splits( - spacing / 2.0, + spacing, &Rectangle { x: 0.0, y: 0.0, @@ -185,7 +185,7 @@ impl Node { fn compute_regions( &self, - halved_spacing: f32, + spacing: f32, current: &Rectangle, regions: &mut HashMap, ) { @@ -193,11 +193,10 @@ impl Node { Node::Split { axis, ratio, a, b, .. } => { - let (region_a, region_b) = - axis.split(current, *ratio, halved_spacing); + let (region_a, region_b) = axis.split(current, *ratio, spacing); - a.compute_regions(halved_spacing, ®ion_a, regions); - b.compute_regions(halved_spacing, ®ion_b, regions); + a.compute_regions(spacing, ®ion_a, regions); + b.compute_regions(spacing, ®ion_b, regions); } Node::Pane(pane) => { let _ = regions.insert(*pane, *current); @@ -207,7 +206,7 @@ impl Node { fn compute_splits( &self, - halved_spacing: f32, + spacing: f32, current: &Rectangle, splits: &mut HashMap, ) { @@ -219,13 +218,12 @@ impl Node { b, id, } => { - let (region_a, region_b) = - axis.split(current, *ratio, halved_spacing); + let (region_a, region_b) = axis.split(current, *ratio, spacing); let _ = splits.insert(*id, (*axis, *current, *ratio)); - a.compute_splits(halved_spacing, ®ion_a, splits); - b.compute_splits(halved_spacing, ®ion_b, splits); + a.compute_splits(spacing, ®ion_a, splits); + b.compute_splits(spacing, ®ion_b, splits); } Node::Pane(_) => {} } From 9079014974e7dab83d70d2c08adb1dc50a49629c Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Tue, 26 May 2020 16:47:29 -0500 Subject: [PATCH 25/40] Tests for axis split --- native/src/widget/pane_grid/axis.rs | 87 +++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs index 7181f9bf..f8c7b661 100644 --- a/native/src/widget/pane_grid/axis.rs +++ b/native/src/widget/pane_grid/axis.rs @@ -54,3 +54,90 @@ impl Axis { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn split_horizontal() { + let a = Axis::Horizontal; + // rectangle height, spacing, top height, bottom y, bottom height + let cases = vec![ + // Even height, even spacing + (10.0, 2.0, 4.0, 6.0, 4.0), + // Odd height, even spacing + (9.0, 2.0, 4.0, 6.0, 3.0), + // Even height, odd spacing + (10.0, 1.0, 5.0, 6.0, 4.0), + // Odd height, odd spacing + (9.0, 1.0, 4.0, 5.0, 4.0), + ]; + for case in cases { + let (h0, spacing, h1_top, y_bottom, h1_bottom) = case; + let r = Rectangle { + x: 0.0, + y: 0.0, + width: 10.0, + height: h0, + }; + let (top, bottom) = a.split(&r, 0.5, spacing); + assert_eq!( + top, + Rectangle { + height: h1_top, + ..r + } + ); + assert_eq!( + bottom, + Rectangle { + y: y_bottom, + height: h1_bottom, + ..r + } + ); + } + } + + #[test] + fn split_vertical() { + let a = Axis::Vertical; + // rectangle width, spacing, left width, right x, right width + let cases = vec![ + // Even width, even spacing + (10.0, 2.0, 4.0, 6.0, 4.0), + // Odd width, even spacing + (9.0, 2.0, 4.0, 6.0, 3.0), + // Even width, odd spacing + (10.0, 1.0, 5.0, 6.0, 4.0), + // Odd width, odd spacing + (9.0, 1.0, 4.0, 5.0, 4.0), + ]; + for case in cases { + let (w0, spacing, w1_left, x_right, w1_right) = case; + let r = Rectangle { + x: 0.0, + y: 0.0, + width: w0, + height: 10.0, + }; + let (left, right) = a.split(&r, 0.5, spacing); + assert_eq!( + left, + Rectangle { + width: w1_left, + ..r + } + ); + assert_eq!( + right, + Rectangle { + x: x_right, + width: w1_right, + ..r + } + ); + } + } +} From fbe3aa3cf4898bf2c2c3858d10d62285b0dbae9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 27 May 2020 00:23:42 +0200 Subject: [PATCH 26/40] Align quads to physical pixels in `iced_wgpu` --- wgpu/src/shader/quad.vert | 12 ++++++------ wgpu/src/shader/quad.vert.spv | Bin 3372 -> 3348 bytes 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/wgpu/src/shader/quad.vert b/wgpu/src/shader/quad.vert index 1d9a4fd2..11f95eeb 100644 --- a/wgpu/src/shader/quad.vert +++ b/wgpu/src/shader/quad.vert @@ -21,14 +21,14 @@ layout(location = 4) out float o_BorderRadius; layout(location = 5) out float o_BorderWidth; void main() { - vec2 p_Pos = i_Pos * u_Scale; - vec2 p_Scale = i_Scale * u_Scale; + vec2 p_Pos = floor(i_Pos * u_Scale); + vec2 p_Scale = floor(i_Scale * u_Scale); mat4 i_Transform = mat4( - vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), - vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), + vec4(p_Scale.x, 0.0, 0.0, 0.0), + vec4(0.0, p_Scale.y, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), - vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0) + vec4(p_Pos, 0.0, 1.0) ); o_Color = i_Color; @@ -36,7 +36,7 @@ void main() { o_Pos = p_Pos; o_Scale = p_Scale; o_BorderRadius = i_BorderRadius * u_Scale; - o_BorderWidth = i_BorderWidth * u_Scale; + o_BorderWidth = floor(i_BorderWidth * u_Scale); gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0); } diff --git a/wgpu/src/shader/quad.vert.spv b/wgpu/src/shader/quad.vert.spv index 7059b51bc4575638f541d299aa07e0a12e4f6599..b3025a93396f709bb29e9cee5ab8779873115751 100644 GIT binary patch literal 3348 zcmZXVU2{}L5QY!=*nl92$cKt?Lr_#8q5`4_1cD|=z(6qI2U(Y72}d`3;_k+>yz<6d ztMn)NtGuzw=Q%sQo{B@g>F&3CdU|^LETbdS6Ir$^+s(i2Y_JYyBQROEH>;I$esOtm zrq^GeIe+1t5&N^zGSHX<*`ZP&Ytbj-0F<1o~z>TB) z8$td>;_+;ZfB)sw-h;i>SSor*ZO_Ro_5tkqcJZXy?ooM-+6HU8@u1u6^qv;oP1y00 zxAyJE@>;Xqf*H*Yrdad7jJ`GWjg@!FhxT|jg+H(4bdPr}8goUv=$5s4?|f+2${rMr z8%1}$)g456hg#W}{D1sd*{h;*H~Ur$Yp9hyjC}W->-jcKF`s>nd{y3FNBqNlz5fi` zc>5pmRlWtk(QYiYx+|@2zx6XF(X~w8rLXAa{k-VFPL#YoE&06DU(SCiL&N90;+t#d zTQ~Dwzu8%94fi=s`nKEcq0cvQE?oB4`S>aIV?Mq8B%Eh1V8>gJm-gU|QL}0PuolP? zF${e6LWMJjakF5abB(iJ&v7nJ09V+)zf0RWw~mN+4zykRoda#}sO`59b!+=wXgllL zek0nE&v&ox_uHw)``+5#+x@7;>kM#@(5JMP1*Cm9##*(V zO*#FaBh4Fr?c0fq_&eCfYwN$8{O(^$zKP`5c6K9P+u4otzD#=bPTN@y|3h@^3BR_p zE$8{hJKK?8zq76XIR15{d+paGnse^mK` zGuZ*2dy(%+-xzS2_iq@#MPr@kJs^6d?f)%d*U_hveT8`WeZU#NP8R3wFmOhlLu0(p z5g`9NG444E+$X<}2)Sdx8a-40RjRWV`8exUbmuqDI*z{ctk>Y=<1D{#_sPduucOCV z`aR2g%A5Nn@(mz24dNbeCVq*EeXF;C+^Gs;|2uH*_kCmkX>|G6e+J#X@{uF%`YxRJ zzehdx@EnkH1@|7hoOg+w@1q;5&zxsL#D9Qp&930iqsy5ixC@DMR)V{jIA&FL6@uMyOOwSzN?9==DP-GKU|S-7G18I?|R~@ z`EDf6e9_xEbbDye?2$bQ-%WJ;;r+d@_Y2>r=-$n{Si7}`?=y7koCVh5x%&Km=YeOM z+Z@I^t5ZmQ&Wdxm2>dP7cPrW9`vUuJ^4&pd$Bf-acgEaz52+o#2k6ePK7SXr^*M`Q zfkz;|!>`eIeuv+{$^Vw-_gi%L$(wTpDHk~#=&K-deuus@XA@37ay~(KpS<6;wXK0& zz~2%5p7j_hZ|)rVJuvq(;Q99Y2OuAI8{Izt$sYZiz!-UBCXfZ_fS9eV#36>-2H literal 3372 zcmZXVU2{}L5QY!=*nlXA2t>u05EYdeQ2|ke1cD|?jDcWKQ8!Dn#DklhxVu5iD=o`g ztK=v7tGuzw=Q%sQoQjj`o$j}L-s$P-IT;+79LchM*+{lO`zh;L8UZd3^^BTGJ#%}#~yRqH5U$nPC zhfCbtck3(bjaCz6FgucBHCJtv*dkyIC}|yUg30&XD#Y;MXP9+xq0rqZ`aBi z6!mLGd!yOzMR|r=S(p5O_)uA^qH;FtR`heIl{E~$TaAr;m!imLU4yTRua)b2``^ho zy5D0PU)DeEU&S}^Z?@{o&Gu@u-EIDiNpvj}cjzlRc{eY%K}Sm5nwEIJ-CfClX&M(k z?-k!%E8n@Ece;)3^=98^H|g7Lwfa8q#J+G?U;E=>s>gh~`!O*0T)>XI9xmpc>6%xVZ42y?HRRw3z4_B z??T&N*Y=HQ2cP#|UGLke#(Uq|p4;_nd+bKCYs}Ul?$5X52+SIYIR|DAV&)#0x>zsY z!ICd>^^VNNag-B?Pq5lG>c`Q{V;ngFgZH4VZyb9+vzVg&1he^QUrKi5==!I?U2iU< z{2pQ!(;nm3dlc*KIaJ=cdx^_u4|u;~$FQGa9!K2sOtSsDX`9cp%*M(W`FQ`~_w45C zp7M<|D-QX_nI|+D@||KfUOxTKuOM0W>rB|EOZ)Nq*Rl5@Q>^r?(}*#OYn@~5S#-wC z6!$)Y@-`x-xc?1i??xWaqiz3+>Hmz`y~3}(l(2|j#x`DCzy0hQ*RL?^b8r3H_IJc< z+uyO@mr0NMYTNJO{~BHH@N3)uV)7fmh6KO<2BQBY{`<_XwT5G8o6Md~OrK)jejDY` zFR!-WA~9=gpFBVu#{a-4`IbLL+*58jjJ3~wkNWH%`}K2V3DLKh?C{;f_M52BZ>4th z-52Qgoj$*d+Tpu{ZlCL0W!BbLWBvxYi})sd|KB2e-|BY>|0DHp9ou!{a{65r3(kA! z4J0_%(f4w0fQbiZ6Ww*BG#g~;&$aUW~Cg@}i}jcz?3u||J^7$a`X2=kAK z-_z)!cEXtYSSMljRorbC-B^9`7XM-&?`#)wFJsL22J=HCN6gprXdCx0-FgxE5AM_J A Date: Wed, 27 May 2020 05:05:13 +0200 Subject: [PATCH 27/40] Introduce feature flags to enable `iced_glow` Also keep `iced_wgpu` as the default renderer for the time being. --- Cargo.toml | 21 +++++++++++++++------ glow/Cargo.toml | 1 + src/application.rs | 24 ++++++++++++------------ src/element.rs | 2 +- src/lib.rs | 29 +++++++++++++++++++++++------ src/settings.rs | 8 ++++---- src/widget.rs | 15 +++++++++------ wgpu/src/widget.rs | 7 +++++++ 8 files changed, 72 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9339f4ed..4dd7d1e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,14 +12,21 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] [features] +default = ["wgpu"] +# Enables the `iced_wgpu` renderer +wgpu = ["iced_wgpu"] # Enables the `Image` widget -image = ["iced_glow/image"] +image = ["iced_wgpu/image"] # Enables the `Svg` widget -svg = ["iced_glow/svg"] +svg = ["iced_wgpu/svg"] # Enables the `Canvas` widget -canvas = ["iced_glow/canvas"] +canvas = ["iced_wgpu/canvas"] +# Enables the `iced_glow` renderer. Overrides `iced_wgpu` +glow = ["iced_glow", "iced_glutin"] +# Enables the `Canvas` widget for `iced_glow` +glow_canvas = ["iced_glow/canvas"] # Enables a debug view in native platforms (press F12) -debug = ["iced_glutin/debug"] +debug = ["iced_winit/debug"] # Enables `tokio` as the `executor::Default` on native platforms tokio = ["iced_futures/tokio"] # Enables `async-std` as the `executor::Default` on native platforms @@ -68,8 +75,10 @@ iced_core = { version = "0.2", path = "core" } iced_futures = { version = "0.1", path = "futures" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_glutin = { version = "0.1", path = "glutin" } -iced_glow = { version = "0.1", path = "glow" } +iced_winit = { version = "0.1", path = "winit" } +iced_glutin = { version = "0.1", path = "glutin", optional = true } +iced_wgpu = { version = "0.2", path = "wgpu", optional = true } +iced_glow = { version = "0.1", path = "glow", optional = true} [target.'cfg(target_arch = "wasm32")'.dependencies] iced_web = { version = "0.2", path = "web" } diff --git a/glow/Cargo.toml b/glow/Cargo.toml index 53952608..dd6bbefc 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/hecrj/iced" [features] canvas = ["iced_graphics/canvas"] +# Not supported yet! image = [] svg = [] diff --git a/src/application.rs b/src/application.rs index b6f2227e..19cab7da 100644 --- a/src/application.rs +++ b/src/application.rs @@ -188,21 +188,21 @@ pub trait Application: Sized { { #[cfg(not(target_arch = "wasm32"))] { - let glow_settings = iced_glow::Settings { + let renderer_settings = crate::renderer::Settings { default_font: settings.default_font, antialiasing: if settings.antialiasing { - Some(iced_glow::settings::Antialiasing::MSAAx4) + Some(crate::renderer::settings::Antialiasing::MSAAx4) } else { None }, - ..iced_glow::Settings::default() + ..crate::renderer::Settings::default() }; - iced_glutin::application::run::< + crate::runtime::application::run::< Instance, Self::Executor, - iced_glow::window::Compositor, - >(settings.into(), glow_settings); + crate::renderer::window::Compositor, + >(settings.into(), renderer_settings); } #[cfg(target_arch = "wasm32")] @@ -213,11 +213,11 @@ pub trait Application: Sized { struct Instance(A); #[cfg(not(target_arch = "wasm32"))] -impl iced_glutin::Program for Instance +impl iced_winit::Program for Instance where A: Application, { - type Renderer = iced_glow::Renderer; + type Renderer = crate::renderer::Renderer; type Message = A::Message; fn update(&mut self, message: Self::Message) -> Command { @@ -230,7 +230,7 @@ where } #[cfg(not(target_arch = "wasm32"))] -impl iced_glutin::Application for Instance +impl crate::runtime::Application for Instance where A: Application, { @@ -246,10 +246,10 @@ where self.0.title() } - fn mode(&self) -> iced_glutin::Mode { + fn mode(&self) -> iced_winit::Mode { match self.0.mode() { - window::Mode::Windowed => iced_glutin::Mode::Windowed, - window::Mode::Fullscreen => iced_glutin::Mode::Fullscreen, + window::Mode::Windowed => iced_winit::Mode::Windowed, + window::Mode::Fullscreen => iced_winit::Mode::Fullscreen, } } diff --git a/src/element.rs b/src/element.rs index e7504615..6f47c701 100644 --- a/src/element.rs +++ b/src/element.rs @@ -3,7 +3,7 @@ /// This is an alias of an `iced_native` element with a default `Renderer`. #[cfg(not(target_arch = "wasm32"))] pub type Element<'a, Message> = - iced_glutin::Element<'a, Message, iced_glow::Renderer>; + crate::runtime::Element<'a, Message, crate::renderer::Renderer>; #[cfg(target_arch = "wasm32")] pub use iced_web::Element; diff --git a/src/lib.rs b/src/lib.rs index 58cc141d..d08b39cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -197,6 +197,29 @@ pub mod window; #[cfg_attr(docsrs, doc(cfg(any(feature = "tokio", feature = "async-std"))))] pub mod time; +#[cfg(all( + not(target_arch = "wasm32"), + not(feature = "glow"), + feature = "wgpu" +))] +use iced_winit as runtime; + +#[cfg(all(not(target_arch = "wasm32"), feature = "glow"))] +use iced_glutin as runtime; + +#[cfg(all( + not(target_arch = "wasm32"), + not(feature = "glow"), + feature = "wgpu" +))] +use iced_wgpu as renderer; + +#[cfg(all(not(target_arch = "wasm32"), feature = "glow"))] +use iced_glow as renderer; + +#[cfg(target_arch = "wasm32")] +use iced_web as runtime; + #[doc(no_inline)] pub use widget::*; @@ -206,12 +229,6 @@ pub use executor::Executor; pub use sandbox::Sandbox; pub use settings::Settings; -#[cfg(not(target_arch = "wasm32"))] -use iced_glutin as runtime; - -#[cfg(target_arch = "wasm32")] -use iced_web as runtime; - pub use runtime::{ futures, Align, Background, Color, Command, Font, HorizontalAlignment, Length, Point, Rectangle, Size, Subscription, Vector, VerticalAlignment, diff --git a/src/settings.rs b/src/settings.rs index 36685763..01ad0ee0 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -51,10 +51,10 @@ impl Settings { } #[cfg(not(target_arch = "wasm32"))] -impl From> for iced_glutin::Settings { - fn from(settings: Settings) -> iced_glutin::Settings { - iced_glutin::Settings { - window: iced_glutin::settings::Window { +impl From> for iced_winit::Settings { + fn from(settings: Settings) -> iced_winit::Settings { + iced_winit::Settings { + window: iced_winit::settings::Window { size: settings.window.size, resizable: settings.window.resizable, decorations: settings.window.decorations, diff --git a/src/widget.rs b/src/widget.rs index eebf5f2a..3e4d4788 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -18,25 +18,28 @@ //! [`text_input::State`]: text_input/struct.State.html #[cfg(not(target_arch = "wasm32"))] mod platform { - pub use iced_glow::widget::{ + pub use crate::renderer::widget::{ button, checkbox, container, pane_grid, progress_bar, radio, scrollable, slider, text_input, Column, Row, Space, Text, }; - #[cfg(feature = "canvas")] - #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] - pub use iced_glow::widget::canvas; + #[cfg(any(feature = "canvas", feature = "glow_canvas"))] + #[cfg_attr( + docsrs, + doc(cfg(any(feature = "canvas", feature = "glow_canvas"))) + )] + pub use crate::renderer::widget::canvas; #[cfg_attr(docsrs, doc(cfg(feature = "image")))] pub mod image { //! Display images in your user interface. - pub use iced_glutin::image::{Handle, Image}; + pub use crate::runtime::image::{Handle, Image}; } #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] pub mod svg { //! Display vector graphics in your user interface. - pub use iced_glutin::svg::{Handle, Svg}; + pub use crate::runtime::svg::{Handle, Svg}; } #[doc(no_inline)] diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index 32ccad17..ac741118 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -7,6 +7,8 @@ //! ``` //! use iced_wgpu::{button, Button}; //! ``` +use crate::Renderer; + pub mod button; pub mod checkbox; pub mod container; @@ -47,3 +49,8 @@ pub mod canvas; #[cfg(feature = "canvas")] #[doc(no_inline)] pub use canvas::Canvas; + +pub use iced_native::Space; + +pub type Column<'a, Message> = iced_native::Column<'a, Message, Renderer>; +pub type Row<'a, Message> = iced_native::Row<'a, Message, Renderer>; From 858eafe22e6f17962c935a3d58749dbd839c46ae Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Wed, 27 May 2020 14:24:33 -0500 Subject: [PATCH 28/40] Structured test cases --- native/src/widget/pane_grid/axis.rs | 205 +++++++++++++++++++--------- 1 file changed, 138 insertions(+), 67 deletions(-) diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs index f8c7b661..b3a306d5 100644 --- a/native/src/widget/pane_grid/axis.rs +++ b/native/src/widget/pane_grid/axis.rs @@ -59,85 +59,156 @@ impl Axis { mod tests { use super::*; - #[test] - fn split_horizontal() { - let a = Axis::Horizontal; - // rectangle height, spacing, top height, bottom y, bottom height - let cases = vec![ - // Even height, even spacing - (10.0, 2.0, 4.0, 6.0, 4.0), - // Odd height, even spacing - (9.0, 2.0, 4.0, 6.0, 3.0), - // Even height, odd spacing - (10.0, 1.0, 5.0, 6.0, 4.0), - // Odd height, odd spacing - (9.0, 1.0, 4.0, 5.0, 4.0), - ]; - for case in cases { - let (h0, spacing, h1_top, y_bottom, h1_bottom) = case; - let r = Rectangle { - x: 0.0, - y: 0.0, - width: 10.0, - height: h0, - }; - let (top, bottom) = a.split(&r, 0.5, spacing); - assert_eq!( - top, - Rectangle { - height: h1_top, - ..r - } - ); - assert_eq!( - bottom, - Rectangle { - y: y_bottom, - height: h1_bottom, - ..r - } - ); - } + enum Case { + Horizontal { + overall_height: f32, + spacing: f32, + top_height: f32, + bottom_y: f32, + bottom_height: f32, + }, + Vertical { + overall_width: f32, + spacing: f32, + left_width: f32, + right_x: f32, + right_width: f32, + }, } #[test] - fn split_vertical() { - let a = Axis::Vertical; - // rectangle width, spacing, left width, right x, right width + fn split() { let cases = vec![ + // Even height, even spacing + Case::Horizontal { + overall_height: 10.0, + spacing: 2.0, + top_height: 4.0, + bottom_y: 6.0, + bottom_height: 4.0, + }, + // Odd height, even spacing + Case::Horizontal { + overall_height: 9.0, + spacing: 2.0, + top_height: 4.0, + bottom_y: 6.0, + bottom_height: 3.0, + }, + // Even height, odd spacing + Case::Horizontal { + overall_height: 10.0, + spacing: 1.0, + top_height: 5.0, + bottom_y: 6.0, + bottom_height: 4.0, + }, + // Odd height, odd spacing + Case::Horizontal { + overall_height: 9.0, + spacing: 1.0, + top_height: 4.0, + bottom_y: 5.0, + bottom_height: 4.0, + }, // Even width, even spacing - (10.0, 2.0, 4.0, 6.0, 4.0), + Case::Vertical { + overall_width: 10.0, + spacing: 2.0, + left_width: 4.0, + right_x: 6.0, + right_width: 4.0, + }, // Odd width, even spacing - (9.0, 2.0, 4.0, 6.0, 3.0), + Case::Vertical { + overall_width: 9.0, + spacing: 2.0, + left_width: 4.0, + right_x: 6.0, + right_width: 3.0, + }, // Even width, odd spacing - (10.0, 1.0, 5.0, 6.0, 4.0), + Case::Vertical { + overall_width: 10.0, + spacing: 1.0, + left_width: 5.0, + right_x: 6.0, + right_width: 4.0, + }, // Odd width, odd spacing - (9.0, 1.0, 4.0, 5.0, 4.0), + Case::Vertical { + overall_width: 9.0, + spacing: 1.0, + left_width: 4.0, + right_x: 5.0, + right_width: 4.0, + }, ]; for case in cases { - let (w0, spacing, w1_left, x_right, w1_right) = case; - let r = Rectangle { - x: 0.0, - y: 0.0, - width: w0, - height: 10.0, - }; - let (left, right) = a.split(&r, 0.5, spacing); - assert_eq!( - left, - Rectangle { - width: w1_left, - ..r + match case { + Case::Horizontal { + overall_height, + spacing, + top_height, + bottom_y, + bottom_height, + } => { + let a = Axis::Horizontal; + let r = Rectangle { + x: 0.0, + y: 0.0, + width: 10.0, + height: overall_height, + }; + let (top, bottom) = a.split(&r, 0.5, spacing); + assert_eq!( + top, + Rectangle { + height: top_height, + ..r + } + ); + assert_eq!( + bottom, + Rectangle { + y: bottom_y, + height: bottom_height, + ..r + } + ); } - ); - assert_eq!( - right, - Rectangle { - x: x_right, - width: w1_right, - ..r + Case::Vertical { + overall_width, + spacing, + left_width, + right_x, + right_width, + } => { + let a = Axis::Vertical; + let r = Rectangle { + x: 0.0, + y: 0.0, + width: overall_width, + height: 10.0, + }; + let (left, right) = a.split(&r, 0.5, spacing); + assert_eq!( + left, + Rectangle { + width: left_width, + ..r + } + ); + assert_eq!( + right, + Rectangle { + x: right_x, + width: right_width, + ..r + } + ); } - ); + } } } } From 823ea1573245b849a0696543838a7ad1d0f914d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 27 May 2020 23:09:27 +0200 Subject: [PATCH 29/40] Update `glyph_brush` and `glow_glyph` --- glow/Cargo.toml | 4 +- glow/src/backend.rs | 23 ++++++----- glow/src/text.rs | 69 ++++++++++++------------------- graphics/src/backend.rs | 2 - graphics/src/widget/text_input.rs | 9 +--- wgpu/src/backend.rs | 4 -- wgpu/src/text.rs | 16 ------- 7 files changed, 41 insertions(+), 86 deletions(-) diff --git a/glow/Cargo.toml b/glow/Cargo.toml index dd6bbefc..a18fffe1 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -15,12 +15,12 @@ svg = [] [dependencies] glow = "0.4" -glow_glyph = "0.1" +glow_glyph = "0.2" +glyph_brush = "0.7" euclid = "0.20" bytemuck = "1.2" glam = "0.8" log = "0.4" -glyph_brush = "0.6" [dependencies.iced_native] version = "0.2" diff --git a/glow/src/backend.rs b/glow/src/backend.rs index c98aa5fe..6bd443ad 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -101,7 +101,6 @@ impl Backend { for text in layer.text.iter() { // Target physical coordinates directly to avoid blurry text let text = glow_glyph::Section { - text: text.content, // TODO: We `round` here to avoid rerasterizing text when // its position changes slightly. This can make text feel a // bit "jumpy". We may be able to do better once we improve @@ -123,12 +122,18 @@ impl Backend { (text.bounds.width * scale_factor).ceil(), (text.bounds.height * scale_factor).ceil(), ), - scale: glow_glyph::Scale { - x: text.size * scale_factor, - y: text.size * scale_factor, - }, - color: text.color, - font_id: self.text_pipeline.find_font(text.font), + text: vec![glow_glyph::Text { + text: text.content, + scale: glow_glyph::ab_glyph::PxScale { + x: text.size * scale_factor, + y: text.size * scale_factor, + }, + font_id: self.text_pipeline.find_font(text.font), + extra: glow_glyph::Extra { + color: text.color, + z: 0.0, + }, + }], layout: glow_glyph::Layout::default() .h_align(match text.horizontal_alignment { HorizontalAlignment::Left => { @@ -191,10 +196,6 @@ impl backend::Text for Backend { ) -> (f32, f32) { self.text_pipeline.measure(contents, size, font, bounds) } - - fn space_width(&self, size: f32) -> f32 { - self.text_pipeline.space_width(size) - } } #[cfg(feature = "image")] diff --git a/glow/src/text.rs b/glow/src/text.rs index 0a11b64f..6dc7882c 100644 --- a/glow/src/text.rs +++ b/glow/src/text.rs @@ -1,13 +1,13 @@ use crate::Transformation; +use glow_glyph::ab_glyph; use iced_graphics::font; use std::{cell::RefCell, collections::HashMap}; #[derive(Debug)] pub struct Pipeline { - draw_brush: RefCell>, + draw_brush: RefCell, draw_font_map: RefCell>, - - measure_brush: RefCell>, + measure_brush: RefCell>, } impl Pipeline { @@ -22,36 +22,29 @@ impl Pipeline { .unwrap_or_else(|_| font::FALLBACK.to_vec()) }); - let load_glyph_brush = |font: Vec| { - let builder = - glow_glyph::GlyphBrushBuilder::using_fonts_bytes(vec![ - font.clone() - ])?; - - Ok(( - builder, - glyph_brush::GlyphBrushBuilder::using_font_bytes(font).build(), - )) - }; - - let (brush_builder, measure_brush) = load_glyph_brush(default_font) - .unwrap_or_else(|_: glow_glyph::rusttype::Error| { + let font = ab_glyph::FontArc::try_from_vec(default_font) + .unwrap_or_else(|_| { log::warn!( - "System font failed to load. \ - Falling back to embedded font..." + "System font failed to load. Falling back to \ + embedded font..." ); - load_glyph_brush(font::FALLBACK.to_vec()) + ab_glyph::FontArc::try_from_slice(font::FALLBACK) .expect("Load fallback font") }); let draw_brush = - brush_builder.initial_cache_size((2048, 2048)).build(gl); + glow_glyph::GlyphBrushBuilder::using_font(font.clone()) + .initial_cache_size((2048, 2048)) + .draw_cache_multithread(false) // TODO: Expose as a configuration flag + .build(&gl); + + let measure_brush = + glyph_brush::GlyphBrushBuilder::using_font(font).build(); Pipeline { draw_brush: RefCell::new(draw_brush), draw_font_map: RefCell::new(HashMap::new()), - measure_brush: RefCell::new(measure_brush), } } @@ -88,10 +81,13 @@ impl Pipeline { let glow_glyph::FontId(font_id) = self.find_font(font); let section = glow_glyph::Section { - text: content, - scale: glow_glyph::Scale { x: size, y: size }, bounds: (bounds.width, bounds.height), - font_id: glow_glyph::FontId(font_id), + text: vec![glow_glyph::Text { + text: content, + scale: size.into(), + font_id: glow_glyph::FontId(font_id), + extra: glow_glyph::Extra::default(), + }], ..Default::default() }; @@ -104,20 +100,6 @@ impl Pipeline { } } - pub fn space_width(&self, size: f32) -> f32 { - use glow_glyph::GlyphCruncher; - - let glyph_brush = self.measure_brush.borrow(); - - // TODO: Select appropriate font - let font = &glyph_brush.fonts()[0]; - - font.glyph(' ') - .scaled(glow_glyph::Scale { x: size, y: size }) - .h_metrics() - .advance_width - } - pub fn trim_measurement_cache(&mut self) { // TODO: We should probably use a `GlyphCalculator` for this. However, // it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop. @@ -150,11 +132,12 @@ impl Pipeline { return *font_id; } - // TODO: Find a way to share font data - let _ = self.measure_brush.borrow_mut().add_font_bytes(bytes); + let font = ab_glyph::FontArc::try_from_slice(bytes) + .expect("Load font"); - let font_id = - self.draw_brush.borrow_mut().add_font_bytes(bytes); + let _ = self.measure_brush.borrow_mut().add_font(font.clone()); + + let font_id = self.draw_brush.borrow_mut().add_font(font); let _ = self .draw_font_map diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 3fcb42f7..ee4eca0a 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -17,8 +17,6 @@ pub trait Text { font: Font, bounds: Size, ) -> (f32, f32); - - fn space_width(&self, size: f32) -> f32; } pub trait Image { diff --git a/graphics/src/widget/text_input.rs b/graphics/src/widget/text_input.rs index 023bdd7f..893197d1 100644 --- a/graphics/src/widget/text_input.rs +++ b/graphics/src/widget/text_input.rs @@ -37,16 +37,9 @@ where fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 { let backend = self.backend(); - let (mut width, _) = + let (width, _) = backend.measure(value, f32::from(size), font, Size::INFINITY); - let spaces_around = value.len() - value.trim().len(); - - if spaces_around > 0 { - let space_width = backend.space_width(size as f32); - width += spaces_around as f32 * space_width; - } - width } diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 1e62cf07..04eb0b28 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -250,10 +250,6 @@ impl backend::Text for Backend { ) -> (f32, f32) { self.text_pipeline.measure(contents, size, font, bounds) } - - fn space_width(&self, size: f32) -> f32 { - self.text_pipeline.space_width(size) - } } #[cfg(feature = "image")] diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 17d3f6dd..d65c0385 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -6,7 +6,6 @@ use std::{cell::RefCell, collections::HashMap}; pub struct Pipeline { draw_brush: RefCell>, draw_font_map: RefCell>, - measure_brush: RefCell>, } @@ -52,7 +51,6 @@ impl Pipeline { Pipeline { draw_brush: RefCell::new(draw_brush), draw_font_map: RefCell::new(HashMap::new()), - measure_brush: RefCell::new(measure_brush), } } @@ -109,20 +107,6 @@ impl Pipeline { } } - pub fn space_width(&self, size: f32) -> f32 { - use wgpu_glyph::GlyphCruncher; - - let glyph_brush = self.measure_brush.borrow(); - - // TODO: Select appropriate font - let font = &glyph_brush.fonts()[0]; - - font.glyph(' ') - .scaled(wgpu_glyph::Scale { x: size, y: size }) - .h_metrics() - .advance_width - } - pub fn trim_measurement_cache(&mut self) { // TODO: We should probably use a `GlyphCalculator` for this. However, // it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop. From 45511a442f707e93fe6e568d2100756b63af7362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 27 May 2020 23:17:21 +0200 Subject: [PATCH 30/40] Target physical pixels for quads in `iced_glow` --- examples/tour/src/main.rs | 4 ++-- glow/src/shader/quad.vert | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index ca7a4f5d..c0bd2efe 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -772,7 +772,7 @@ mod style { Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), })), border_radius: 12, - shadow_offset: Vector::new(1.0, 1.0), + shadow_offset: Vector::new(0.0, 1.0), text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE), ..button::Style::default() } @@ -781,7 +781,7 @@ mod style { fn hovered(&self) -> button::Style { button::Style { text_color: Color::WHITE, - shadow_offset: Vector::new(1.0, 2.0), + shadow_offset: Vector::new(0.0, 2.0), ..self.active() } } diff --git a/glow/src/shader/quad.vert b/glow/src/shader/quad.vert index d37b5c8d..ce816550 100644 --- a/glow/src/shader/quad.vert +++ b/glow/src/shader/quad.vert @@ -26,14 +26,14 @@ const vec2 positions[4] = vec2[]( void main() { vec2 q_Pos = positions[gl_VertexID]; - vec2 p_Pos = i_Pos * u_Scale; - vec2 p_Scale = i_Scale * u_Scale; + vec2 p_Pos = floor(i_Pos * u_Scale); + vec2 p_Scale = floor(i_Scale * u_Scale); mat4 i_Transform = mat4( - vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), - vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), + vec4(p_Scale.x, 0.0, 0.0, 0.0), + vec4(0.0, p_Scale.y, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), - vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0) + vec4(p_Pos, 0.0, 1.0) ); v_Color = i_Color; @@ -41,7 +41,7 @@ void main() { v_Pos = p_Pos; v_Scale = p_Scale; v_BorderRadius = i_BorderRadius * u_Scale; - v_BorderWidth = i_BorderWidth * u_Scale; + v_BorderWidth = floor(i_BorderWidth * u_Scale); gl_Position = u_Transform * i_Transform * vec4(q_Pos, 0.0, 1.0); } From 2ca7e3c4b0cb293adebf9a9bf9a26191069d495d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 28 May 2020 01:37:59 +0200 Subject: [PATCH 31/40] Write documentation for `iced_graphics` --- graphics/src/antialiasing.rs | 3 + graphics/src/backend.rs | 22 +++++ graphics/src/font.rs | 11 +++ graphics/src/font/source.rs | 8 ++ graphics/src/layer.rs | 115 ++++++++++++++++++++++++++- graphics/src/lib.rs | 10 +++ graphics/src/renderer.rs | 18 ++++- graphics/src/triangle.rs | 5 ++ graphics/src/viewport.rs | 22 +++++ graphics/src/widget/column.rs | 1 + graphics/src/widget/image.rs | 1 + graphics/src/widget/row.rs | 1 + graphics/src/widget/svg.rs | 1 + graphics/src/window.rs | 1 + graphics/src/window/gl_compositor.rs | 41 ++++++++++ wgpu/src/backend.rs | 3 +- wgpu/src/lib.rs | 10 ++- wgpu/src/widget.rs | 10 ++- wgpu/src/widget/text.rs | 7 -- 19 files changed, 271 insertions(+), 19 deletions(-) delete mode 100644 wgpu/src/widget/text.rs diff --git a/graphics/src/antialiasing.rs b/graphics/src/antialiasing.rs index f92b3692..34d94711 100644 --- a/graphics/src/antialiasing.rs +++ b/graphics/src/antialiasing.rs @@ -12,6 +12,9 @@ pub enum Antialiasing { } impl Antialiasing { + /// Returns the amount of samples of the [`Antialiasing`]. + /// + /// [`Antialiasing`]: enum.Antialiasing.html pub fn sample_count(self) -> u32 { match self { Antialiasing::MSAAx2 => 2, diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index ee4eca0a..83510311 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -1,15 +1,33 @@ +//! Write a graphics backend. use iced_native::image; use iced_native::svg; use iced_native::{Font, Size}; +/// The graphics backend of a [`Renderer`]. +/// +/// [`Renderer`]: ../struct.Renderer.html pub trait Backend { + /// Trims the measurements cache. + /// + /// This method is currently necessary to properly trim the text cache in + /// `iced_wgpu` and `iced_glow` because of limitations in the text rendering + /// pipeline. It will be removed in the future. fn trim_measurements(&mut self) {} } +/// A graphics backend that supports text rendering. pub trait Text { + /// The icon font of the backend. const ICON_FONT: Font; + + /// The `char` representing a ✔ icon in the [`ICON_FONT`]. + /// + /// [`ICON_FONT`]: #associatedconst.ICON_FONt const CHECKMARK_ICON: char; + /// Measures the text contents with the given size and font, + /// returning the size of a laid out paragraph that fits in the provided + /// bounds. fn measure( &self, contents: &str, @@ -19,10 +37,14 @@ pub trait Text { ) -> (f32, f32); } +/// A graphics backend that supports image rendering. pub trait Image { + /// Returns the dimensions of the provided image. fn dimensions(&self, handle: &image::Handle) -> (u32, u32); } +/// A graphics backend that supports SVG rendering. pub trait Svg { + /// Returns the viewport dimensions of the provided SVG. fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32); } diff --git a/graphics/src/font.rs b/graphics/src/font.rs index a490e609..bcc28857 100644 --- a/graphics/src/font.rs +++ b/graphics/src/font.rs @@ -1,22 +1,33 @@ +//! Find system fonts or use the built-in ones. #[cfg(feature = "font-source")] mod source; #[cfg(feature = "font-source")] +#[cfg_attr(docsrs, doc(cfg(feature = "font-source")))] pub use source::Source; #[cfg(feature = "font-source")] +#[cfg_attr(docsrs, doc(cfg(feature = "font-source")))] pub use font_kit::{ error::SelectionError as LoadError, family_name::FamilyName as Family, }; +/// A built-in fallback font, for convenience. #[cfg(feature = "font-fallback")] +#[cfg_attr(docsrs, doc(cfg(feature = "font-fallback")))] pub const FALLBACK: &[u8] = include_bytes!("../fonts/Lato-Regular.ttf"); +/// A built-in icon font, for convenience. #[cfg(feature = "font-icons")] +#[cfg_attr(docsrs, doc(cfg(feature = "font-icons")))] pub const ICONS: iced_native::Font = iced_native::Font::External { name: "iced_wgpu icons", bytes: include_bytes!("../fonts/Icons.ttf"), }; +/// The `char` representing a ✔ icon in the built-in [`ICONS`] font. +/// +/// [`ICONS`]: const.ICONS.html #[cfg(feature = "font-icons")] +#[cfg_attr(docsrs, doc(cfg(feature = "font-icons")))] pub const CHECKMARK_ICON: char = '\u{F00C}'; diff --git a/graphics/src/font/source.rs b/graphics/src/font/source.rs index 6855aa93..917291ff 100644 --- a/graphics/src/font/source.rs +++ b/graphics/src/font/source.rs @@ -1,16 +1,24 @@ use crate::font::{Family, LoadError}; +/// A font source that can find and load system fonts. +#[allow(missing_debug_implementations)] pub struct Source { raw: font_kit::source::SystemSource, } impl Source { + /// Creates a new [`Source`]. + /// + /// [`Source`]: struct.Source.html pub fn new() -> Self { Source { raw: font_kit::source::SystemSource::new(), } } + /// Finds and loads a font matching the set of provided family priorities. + /// + /// [`Source`]: struct.Source.html pub fn load(&self, families: &[Family]) -> Result, LoadError> { let font = self.raw.select_best_match( families, diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index 59b792f0..6aca738e 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -1,3 +1,4 @@ +//! Organize rendering primitives into a flattened list of layers. use crate::image; use crate::svg; use crate::triangle; @@ -6,16 +7,39 @@ use crate::{ Vector, VerticalAlignment, Viewport, }; +/// A group of primitives that should be clipped together. #[derive(Debug, Clone)] pub struct Layer<'a> { + /// The clipping bounds of the [`Layer`]. + /// + /// [`Layer`]: struct.Layer.html pub bounds: Rectangle, + + /// The quads of the [`Layer`]. + /// + /// [`Layer`]: struct.Layer.html pub quads: Vec, + + /// The triangle meshes of the [`Layer`]. + /// + /// [`Layer`]: struct.Layer.html pub meshes: Vec>, + + /// The text of the [`Layer`]. + /// + /// [`Layer`]: struct.Layer.html pub text: Vec>, + + /// The images of the [`Layer`]. + /// + /// [`Layer`]: struct.Layer.html pub images: Vec, } impl<'a> Layer<'a> { + /// Creates a new [`Layer`] with the given clipping bounds. + /// + /// [`Layer`]: struct.Layer.html pub fn new(bounds: Rectangle) -> Self { Self { bounds, @@ -26,6 +50,11 @@ impl<'a> Layer<'a> { } } + /// Creates a new [`Layer`] for the provided overlay text. + /// + /// This can be useful for displaying debug information. + /// + /// [`Layer`]: struct.Layer.html pub fn overlay(lines: &'a [impl AsRef], viewport: &Viewport) -> Self { let mut overlay = Layer::new(Rectangle::with_size(viewport.logical_size())); @@ -56,6 +85,10 @@ impl<'a> Layer<'a> { overlay } + /// Distributes the given [`Primitive`] and generates a list of layers based + /// on its contents. + /// + /// [`Primitive`]: ../enum.Primitive.html pub fn generate( primitive: &'a Primitive, viewport: &Viewport, @@ -119,7 +152,7 @@ impl<'a> Layer<'a> { bounds.x + translation.x, bounds.y + translation.y, ], - scale: [bounds.width, bounds.height], + size: [bounds.width, bounds.height], color: match background { Background::Color(color) => color.into_linear(), }, @@ -203,46 +236,124 @@ impl<'a> Layer<'a> { } } +/// A colored rectangle with a border. +/// +/// This type can be directly uploaded to GPU memory. #[derive(Debug, Clone, Copy)] #[repr(C)] pub struct Quad { + /// The position of the [`Quad`]. + /// + /// [`Quad`]: struct.Quad.html pub position: [f32; 2], - pub scale: [f32; 2], + + /// The size of the [`Quad`]. + /// + /// [`Quad`]: struct.Quad.html + pub size: [f32; 2], + + /// The color of the [`Quad`], in __linear RGB__. + /// + /// [`Quad`]: struct.Quad.html pub color: [f32; 4], + + /// The border color of the [`Quad`], in __linear RGB__. + /// + /// [`Quad`]: struct.Quad.html pub border_color: [f32; 4], + + /// The border radius of the [`Quad`]. + /// + /// [`Quad`]: struct.Quad.html pub border_radius: f32, + + /// The border width of the [`Quad`]. + /// + /// [`Quad`]: struct.Quad.html pub border_width: f32, } +/// A mesh of triangles. #[derive(Debug, Clone, Copy)] pub struct Mesh<'a> { + /// The origin of the vertices of the [`Mesh`]. + /// + /// [`Mesh`]: struct.Mesh.html pub origin: Point, + + /// The vertex and index buffers of the [`Mesh`]. + /// + /// [`Mesh`]: struct.Mesh.html pub buffers: &'a triangle::Mesh2D, + + /// The clipping bounds of the [`Mesh`]. + /// + /// [`Mesh`]: struct.Mesh.html pub clip_bounds: Rectangle, } +/// A paragraph of text. #[derive(Debug, Clone, Copy)] pub struct Text<'a> { + /// The content of the [`Text`]. + /// + /// [`Text`]: struct.Text.html pub content: &'a str, + + /// The layout bounds of the [`Text`]. + /// + /// [`Text`]: struct.Text.html pub bounds: Rectangle, + + /// The color of the [`Text`], in __linear RGB_. + /// + /// [`Text`]: struct.Text.html pub color: [f32; 4], + + /// The size of the [`Text`]. + /// + /// [`Text`]: struct.Text.html pub size: f32, + + /// The font of the [`Text`]. + /// + /// [`Text`]: struct.Text.html pub font: Font, + + /// The horizontal alignment of the [`Text`]. + /// + /// [`Text`]: struct.Text.html pub horizontal_alignment: HorizontalAlignment, + + /// The vertical alignment of the [`Text`]. + /// + /// [`Text`]: struct.Text.html pub vertical_alignment: VerticalAlignment, } +/// A raster or vector image. #[derive(Debug, Clone)] pub enum Image { + /// A raster image. Raster { + /// The handle of a raster image. handle: image::Handle, + + /// The bounds of the image. bounds: Rectangle, }, + /// A vector image. Vector { + /// The handle of a vector image. handle: svg::Handle, + + /// The bounds of the image. bounds: Rectangle, }, } +#[allow(unsafe_code)] unsafe impl bytemuck::Zeroable for Quad {} + +#[allow(unsafe_code)] unsafe impl bytemuck::Pod for Quad {} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 2de9d21c..b6dda132 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -1,3 +1,13 @@ +//! A bunch of backend-agnostic types that can be leveraged to build a renderer +//! for [`iced`]. +//! +//! [`iced`]: https://github.com/hecrj/iced +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![deny(unused_results)] +#![deny(unsafe_code)] +#![forbid(rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg))] mod antialiasing; mod defaults; mod primitive; diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index f16e04b1..c9360f3a 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -3,19 +3,33 @@ use iced_native::layout::{self, Layout}; use iced_native::mouse; use iced_native::{Background, Color, Element, Point, Widget}; -pub struct Renderer { +/// A backend-agnostic renderer that supports all the built-in widgets. +#[derive(Debug)] +pub struct Renderer { backend: B, } -impl Renderer { +impl Renderer { + /// Creates a new [`Renderer`] from the given [`Backend`]. + /// + /// [`Renderer`]: struct.Renderer.html + /// [`Backend`]: backend/trait.Backend.html pub fn new(backend: B) -> Self { Self { backend } } + /// Returns a reference to the [`Backend`] of the [`Renderer`]. + /// + /// [`Renderer`]: struct.Renderer.html + /// [`Backend`]: backend/trait.Backend.html pub fn backend(&self) -> &B { &self.backend } + /// Returns a mutable reference to the [`Backend`] of the [`Renderer`]. + /// + /// [`Renderer`]: struct.Renderer.html + /// [`Backend`]: backend/trait.Backend.html pub fn backend_mut(&mut self) -> &mut B { &mut self.backend } diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs index 474f69b8..ce879ffc 100644 --- a/graphics/src/triangle.rs +++ b/graphics/src/triangle.rs @@ -1,3 +1,5 @@ +//! Draw geometry using meshes of triangles. + /// A set of [`Vertex2D`] and indices representing a list of triangles. /// /// [`Vertex2D`]: struct.Vertex2D.html @@ -23,5 +25,8 @@ pub struct Vertex2D { pub color: [f32; 4], } +#[allow(unsafe_code)] unsafe impl bytemuck::Zeroable for Vertex2D {} + +#[allow(unsafe_code)] unsafe impl bytemuck::Pod for Vertex2D {} diff --git a/graphics/src/viewport.rs b/graphics/src/viewport.rs index 745ef339..66122e6d 100644 --- a/graphics/src/viewport.rs +++ b/graphics/src/viewport.rs @@ -26,22 +26,44 @@ impl Viewport { } } + /// Returns the physical size of the [`Viewport`]. + /// + /// [`Viewport`]: struct.Viewport.html pub fn physical_size(&self) -> Size { self.physical_size } + /// Returns the physical width of the [`Viewport`]. + /// + /// [`Viewport`]: struct.Viewport.html + pub fn physical_width(&self) -> u32 { + self.physical_size.height + } + + /// Returns the physical height of the [`Viewport`]. + /// + /// [`Viewport`]: struct.Viewport.html pub fn physical_height(&self) -> u32 { self.physical_size.height } + /// Returns the logical size of the [`Viewport`]. + /// + /// [`Viewport`]: struct.Viewport.html pub fn logical_size(&self) -> Size { self.logical_size } + /// Returns the scale factor of the [`Viewport`]. + /// + /// [`Viewport`]: struct.Viewport.html pub fn scale_factor(&self) -> f64 { self.scale_factor } + /// Returns the projection transformation of the [`Viewport`]. + /// + /// [`Viewport`]: struct.Viewport.html pub fn projection(&self) -> Transformation { self.projection } diff --git a/graphics/src/widget/column.rs b/graphics/src/widget/column.rs index 9183d2ee..6c7235c7 100644 --- a/graphics/src/widget/column.rs +++ b/graphics/src/widget/column.rs @@ -3,6 +3,7 @@ use iced_native::column; use iced_native::mouse; use iced_native::{Element, Layout, Point}; +/// A container that distributes its contents vertically. pub type Column<'a, Message, Backend> = iced_native::Column<'a, Message, Renderer>; diff --git a/graphics/src/widget/image.rs b/graphics/src/widget/image.rs index ea49febe..30f446e8 100644 --- a/graphics/src/widget/image.rs +++ b/graphics/src/widget/image.rs @@ -1,3 +1,4 @@ +//! Display images in your user interface. use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::image; diff --git a/graphics/src/widget/row.rs b/graphics/src/widget/row.rs index 9865d0de..4c1dbadc 100644 --- a/graphics/src/widget/row.rs +++ b/graphics/src/widget/row.rs @@ -3,6 +3,7 @@ use iced_native::mouse; use iced_native::row; use iced_native::{Element, Layout, Point}; +/// A container that distributes its contents horizontally. pub type Row<'a, Message, Backend> = iced_native::Row<'a, Message, Renderer>; diff --git a/graphics/src/widget/svg.rs b/graphics/src/widget/svg.rs index 8c681478..8b5ed66a 100644 --- a/graphics/src/widget/svg.rs +++ b/graphics/src/widget/svg.rs @@ -1,3 +1,4 @@ +//! Display vector graphics in your application. use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::{mouse, svg, Layout}; diff --git a/graphics/src/window.rs b/graphics/src/window.rs index 380efb8c..3e74db5f 100644 --- a/graphics/src/window.rs +++ b/graphics/src/window.rs @@ -1,3 +1,4 @@ +//! Draw graphics to window surfaces. mod compositor; #[cfg(feature = "opengl")] diff --git a/graphics/src/window/gl_compositor.rs b/graphics/src/window/gl_compositor.rs index aea898e3..542213b5 100644 --- a/graphics/src/window/gl_compositor.rs +++ b/graphics/src/window/gl_compositor.rs @@ -3,19 +3,60 @@ use iced_native::mouse; use core::ffi::c_void; +/// A basic OpenGL compositor. +/// +/// A compositor is responsible for initializing a renderer and managing window +/// surfaces. +/// +/// For now, this compositor only deals with a single global surface +/// for drawing. However, the trait will most likely change in the near future +/// to handle multiple surfaces at once. +/// +/// If you implement an OpenGL renderer, you can implement this trait to ease +/// integration with existing windowing shells, like `iced_glutin`. pub trait GLCompositor: Sized { + /// The renderer of the [`Compositor`]. + /// + /// This should point to your renderer type, which could be a type alias + /// of the [`Renderer`] provided in this crate with with a specific + /// [`Backend`]. + /// + /// [`Compositor`]: trait.Compositor.html + /// [`Renderer`]: ../struct.Renderer.html + /// [`Backend`]: ../backend/trait.Backend.html type Renderer: iced_native::Renderer; + + /// The settings of the [`Compositor`]. + /// + /// It's up to you to decide the configuration supported by your renderer! type Settings: Default; + /// Creates a new [`Compositor`] and [`Renderer`] with the given + /// [`Settings`] and an OpenGL address loader function. + /// + /// [`Compositor`]: trait.Compositor.html + /// [`Renderer`]: #associatedtype.Renderer + /// [`Backend`]: ../backend/trait.Backend.html + #[allow(unsafe_code)] unsafe fn new( settings: Self::Settings, loader_function: impl FnMut(&str) -> *const c_void, ) -> (Self, Self::Renderer); + /// Returns the amount of samples that should be used when configuring + /// an OpenGL context for this [`Compositor`]. + /// + /// [`Compositor`]: trait.Compositor.html fn sample_count(settings: &Self::Settings) -> u32; + /// Resizes the viewport of the [`Compositor`]. + /// + /// [`Compositor`]: trait.Compositor.html fn resize_viewport(&mut self, physical_size: Size); + /// Draws the provided output with the given [`Renderer`]. + /// + /// [`Compositor`]: trait.Compositor.html fn draw>( &mut self, renderer: &mut Self::Renderer, diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 04eb0b28..88529ddd 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -12,9 +12,10 @@ use iced_native::{Font, HorizontalAlignment, Size, VerticalAlignment}; #[cfg(any(feature = "image", feature = "svg"))] use crate::image; -/// A [`wgpu`] renderer. +/// A [`wgpu`] graphics backend for [`iced`]. /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs +/// [`iced`]: https://github.com/hecrj/iced #[derive(Debug)] pub struct Backend { quad_pipeline: quad::Pipeline, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index b0eee0a0..0c351eea 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -20,7 +20,7 @@ //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [WebGPU API]: https://gpuweb.github.io/gpuweb/ //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] @@ -47,7 +47,11 @@ pub use widget::*; pub(crate) use iced_graphics::Transformation; -pub type Renderer = iced_graphics::Renderer; - #[cfg(any(feature = "image", feature = "svg"))] mod image; + +/// A [`wgpu`] graphics renderer for [`iced`]. +/// +/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs +/// [`iced`]: https://github.com/hecrj/iced +pub type Renderer = iced_graphics::Renderer; diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index ac741118..d17b7a5d 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -19,8 +19,6 @@ pub mod scrollable; pub mod slider; pub mod text_input; -mod text; - #[doc(no_inline)] pub use button::Button; #[doc(no_inline)] @@ -40,8 +38,6 @@ pub use slider::Slider; #[doc(no_inline)] pub use text_input::TextInput; -pub use text::Text; - #[cfg(feature = "canvas")] #[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] pub mod canvas; @@ -52,5 +48,11 @@ pub use canvas::Canvas; pub use iced_native::Space; +/// A container that distributes its contents vertically. pub type Column<'a, Message> = iced_native::Column<'a, Message, Renderer>; + +/// A container that distributes its contents horizontally. pub type Row<'a, Message> = iced_native::Row<'a, Message, Renderer>; + +/// A paragraph of text. +pub type Text = iced_native::Text; diff --git a/wgpu/src/widget/text.rs b/wgpu/src/widget/text.rs deleted file mode 100644 index 1053ea97..00000000 --- a/wgpu/src/widget/text.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Write some text for your users to read. -use crate::Renderer; - -/// A paragraph of text. -/// -/// This is an alias of an `iced_native` text with an `iced_wgpu::Renderer`. -pub type Text = iced_native::Text; From b9d42a45a8ce491e5fa21a86db0799bcd731d0dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 28 May 2020 01:46:17 +0200 Subject: [PATCH 32/40] Write documentation for `iced_glow` --- glow/src/lib.rs | 17 +++++++++++------ glow/src/settings.rs | 4 +--- glow/src/widget.rs | 5 +++++ graphics/Cargo.toml | 4 ++++ wgpu/src/settings.rs | 4 +--- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/glow/src/lib.rs b/glow/src/lib.rs index a32c787e..9e9564ca 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -1,7 +1,10 @@ -//#![deny(missing_docs)] +//! A [`glow`] renderer for [`iced_native`]. +//! +//! [`glow`]: https://github.com/grovesNL/glow +//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] -//#![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] mod backend; @@ -19,15 +22,17 @@ pub use settings::Settings; pub(crate) use backend::Backend; pub(crate) use iced_graphics::Transformation; -pub type Renderer = iced_graphics::Renderer; - #[doc(no_inline)] pub use widget::*; -pub type Element<'a, Message> = iced_native::Element<'a, Message, Renderer>; - pub use iced_graphics::Viewport; pub use iced_native::{ Background, Color, Command, HorizontalAlignment, Length, Vector, VerticalAlignment, }; + +/// A [`glow`] graphics renderer for [`iced`]. +/// +/// [`glow`]: https://github.com/grovesNL/glow +/// [`iced`]: https://github.com/hecrj/iced +pub type Renderer = iced_graphics::Renderer; diff --git a/glow/src/settings.rs b/glow/src/settings.rs index 07b36938..dce30029 100644 --- a/glow/src/settings.rs +++ b/glow/src/settings.rs @@ -1,6 +1,4 @@ -//! Configure a [`Renderer`]. -//! -//! [`Renderer`]: struct.Renderer.html +//! Configure a renderer. pub use iced_graphics::Antialiasing; /// The settings of a [`Renderer`]. diff --git a/glow/src/widget.rs b/glow/src/widget.rs index 362465f4..9968092b 100644 --- a/glow/src/widget.rs +++ b/glow/src/widget.rs @@ -48,6 +48,11 @@ pub use canvas::Canvas; pub use iced_native::{Image, Space}; +/// A container that distributes its contents vertically. pub type Column<'a, Message> = iced_native::Column<'a, Message, Renderer>; + +/// A container that distributes its contents horizontally. pub type Row<'a, Message> = iced_native::Row<'a, Message, Renderer>; + +/// A paragraph of text. pub type Text = iced_native::Text; diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 675bcb60..8e078d75 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -31,3 +31,7 @@ optional = true [dependencies.font-kit] version = "0.6" optional = true + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] +all-features = true diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 05d301e7..8378d734 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,6 +1,4 @@ -//! Configure a [`Renderer`]. -//! -//! [`Renderer`]: struct.Renderer.html +//! Configure a renderer. pub use iced_graphics::Antialiasing; /// The settings of a [`Renderer`]. From 508128436c5da88d4b4f384a9fe73288320eb9ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 28 May 2020 02:04:31 +0200 Subject: [PATCH 33/40] Write documentation for new `iced_native` API --- native/src/debug/basic.rs | 5 +++++ native/src/lib.rs | 2 +- native/src/program.rs | 4 ++-- native/src/program/state.rs | 31 +++++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/native/src/debug/basic.rs b/native/src/debug/basic.rs index d46edba6..5338d0d9 100644 --- a/native/src/debug/basic.rs +++ b/native/src/debug/basic.rs @@ -1,5 +1,7 @@ +#![allow(missing_docs)] use std::{collections::VecDeque, time}; +/// A bunch of time measurements for debugging purposes. #[derive(Debug)] pub struct Debug { is_enabled: bool, @@ -30,6 +32,9 @@ pub struct Debug { } impl Debug { + /// Creates a new [`Debug`]. + /// + /// [`Debug`]: struct.Debug.html pub fn new() -> Self { let now = time::Instant::now(); diff --git a/native/src/lib.rs b/native/src/lib.rs index bea7d17e..f9a8c477 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -34,7 +34,7 @@ //! [`window::Backend`]: window/trait.Backend.html //! [`UserInterface`]: struct.UserInterface.html //! [renderer]: renderer/index.html -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] diff --git a/native/src/program.rs b/native/src/program.rs index 2652dee9..a28ff2a3 100644 --- a/native/src/program.rs +++ b/native/src/program.rs @@ -14,7 +14,7 @@ pub trait Program: Sized { /// The type of __messages__ your [`Program`] will produce. /// - /// [`Application`]: trait.Program.html + /// [`Program`]: trait.Program.html type Message: std::fmt::Debug + Send; /// Handles a __message__ and updates the state of the [`Program`]. @@ -34,6 +34,6 @@ pub trait Program: Sized { /// /// These widgets can produce __messages__ based on user interaction. /// - /// [`Program`]: trait.Application.html + /// [`Program`]: trait.Program.html fn view(&mut self) -> Element<'_, Self::Message, Self::Renderer>; } diff --git a/native/src/program/state.rs b/native/src/program/state.rs index bcb7212d..8716d8b9 100644 --- a/native/src/program/state.rs +++ b/native/src/program/state.rs @@ -3,6 +3,10 @@ use crate::{ UserInterface, }; +/// The execution state of a [`Program`]. It leverages caching, event +/// processing, and rendering primitive storage. +/// +/// [`Program`]: trait.Program.html #[allow(missing_debug_implementations)] pub struct State

where @@ -19,6 +23,11 @@ impl

State

where P: Program + 'static, { + /// Creates a new [`State`] with the provided [`Program`], initializing its + /// primitive with the given logical bounds and renderer. + /// + /// [`State`]: struct.State.html + /// [`Program`]: trait.Program.html pub fn new( mut program: P, bounds: Size, @@ -48,22 +57,44 @@ where } } + /// Returns a reference to the [`Program`] of the [`State`]. + /// + /// [`Program`]: trait.Program.html + /// [`State`]: struct.State.html pub fn program(&self) -> &P { &self.program } + /// Returns a reference to the current rendering primitive of the [`State`]. + /// + /// [`State`]: struct.State.html pub fn primitive(&self) -> &::Output { &self.primitive } + /// Queues an event in the [`State`] for processing during an [`update`]. + /// + /// [`State`]: struct.State.html + /// [`update`]: #method.update pub fn queue_event(&mut self, event: Event) { self.queued_events.push(event); } + /// Queues a message in the [`State`] for processing during an [`update`]. + /// + /// [`State`]: struct.State.html + /// [`update`]: #method.update pub fn queue_message(&mut self, message: P::Message) { self.queued_messages.push(message); } + /// Processes all the queued events and messages, rebuilding and redrawing + /// the widgets of the linked [`Program`] if necessary. + /// + /// Returns the [`Command`] obtained from [`Program`] after updating it, + /// only if an update was necessary. + /// + /// [`Program`]: trait.Program.html pub fn update( &mut self, clipboard: Option<&dyn Clipboard>, From ef28347f1c816d8ad9e772303467de489efb802b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 28 May 2020 02:49:32 +0200 Subject: [PATCH 34/40] Write documentation for new `iced_winit` API --- native/src/debug/null.rs | 1 + winit/src/application.rs | 7 +++++++ winit/src/lib.rs | 2 +- winit/src/proxy.rs | 4 ++++ winit/src/settings.rs | 1 + 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/native/src/debug/null.rs b/native/src/debug/null.rs index 2a9430cd..60e6122d 100644 --- a/native/src/debug/null.rs +++ b/native/src/debug/null.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] #[derive(Debug)] pub struct Debug; diff --git a/winit/src/application.rs b/winit/src/application.rs index fcba47b3..df6e4eec 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,3 +1,4 @@ +//! Create interactive, native cross-platform applications. use crate::{ conversion, mouse, Clipboard, Command, Debug, Executor, Mode, Proxy, Runtime, Settings, Size, Subscription, @@ -72,6 +73,10 @@ pub trait Application: Program { } } +/// Runs an [`Application`] with an executor, compositor, and the provided +/// settings. +/// +/// [`Application`]: trait.Application.html pub fn run( settings: Settings, compositor_settings: C::Settings, @@ -254,6 +259,8 @@ pub fn run( }) } +/// Handles a `WindowEvent` and mutates the provided control flow, keyboard +/// modifiers, viewport, and resized flag accordingly. pub fn handle_window_event( event: &winit::event::WindowEvent<'_>, window: &winit::window::Window, diff --git a/winit/src/lib.rs b/winit/src/lib.rs index d3af9ae9..bdab3ed7 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -15,7 +15,7 @@ //! [`winit`]: https://github.com/rust-windowing/winit //! [`Application`]: trait.Application.html //! [`conversion`]: conversion -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] diff --git a/winit/src/proxy.rs b/winit/src/proxy.rs index ee96614a..532f8c56 100644 --- a/winit/src/proxy.rs +++ b/winit/src/proxy.rs @@ -5,6 +5,7 @@ use iced_native::futures::{ }; use std::pin::Pin; +/// An event loop proxy that implements `Sink`. #[derive(Debug)] pub struct Proxy { raw: winit::event_loop::EventLoopProxy, @@ -19,6 +20,9 @@ impl Clone for Proxy { } impl Proxy { + /// Creates a new [`Proxy`] from an `EventLoopProxy`. + /// + /// [`Proxy`]: struct.Proxy.html pub fn new(raw: winit::event_loop::EventLoopProxy) -> Self { Self { raw } } diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 751f5071..37cb832f 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -44,6 +44,7 @@ pub struct Window { } impl Window { + /// Converts the window settings into a `WindowBuilder` from `winit`. pub fn into_builder( self, title: &str, From 4aa0d7a13a6fbf04e3fa24c444d562c2f1085b5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 28 May 2020 02:57:03 +0200 Subject: [PATCH 35/40] Write documentation for `iced_glutin` --- glutin/src/application.rs | 8 +++++++- glutin/src/lib.rs | 16 ++++++++++++---- native/src/program.rs | 2 +- winit/src/application.rs | 4 +++- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 2e6f34af..c777a13b 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -1,12 +1,18 @@ +//! Create interactive, native cross-platform applications. use crate::{mouse, Executor, Runtime, Size}; use iced_graphics::window; use iced_graphics::Viewport; use iced_winit::application; use iced_winit::conversion; -use iced_winit::{program, Clipboard, Debug, Proxy, Settings}; +use iced_winit::{Clipboard, Debug, Proxy, Settings}; pub use iced_winit::Application; +pub use iced_winit::{program, Program}; +/// Runs an [`Application`] with an executor, compositor, and the provided +/// settings. +/// +/// [`Application`]: trait.Application.html pub fn run( settings: Settings, compositor_settings: C::Settings, diff --git a/glutin/src/lib.rs b/glutin/src/lib.rs index 829fe02a..b0e0bdd4 100644 --- a/glutin/src/lib.rs +++ b/glutin/src/lib.rs @@ -1,15 +1,23 @@ -//#![deny(missing_docs)] +//! A windowing shell for [`iced`], on top of [`glutin`]. +//! +//! [`iced`]: https://github.com/hecrj/iced +//! [`glutin`]: https://github.com/rust-windowing/glutin +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![deny(unsafe_code)] #![forbid(rust_2018_idioms)] +pub use glutin; #[doc(no_inline)] pub use iced_native::*; pub mod application; -pub use application::Application; - -pub use iced_winit::settings::{self, Settings}; +pub use iced_winit::settings; pub use iced_winit::Mode; + +#[doc(no_inline)] +pub use application::Application; +#[doc(no_inline)] +pub use settings::Settings; diff --git a/native/src/program.rs b/native/src/program.rs index a28ff2a3..14afcd84 100644 --- a/native/src/program.rs +++ b/native/src/program.rs @@ -5,7 +5,7 @@ mod state; pub use state::State; -/// An interactive, native cross-platform program. +/// The core of a user interface application following The Elm Architecture. pub trait Program: Sized { /// The graphics backend to use to draw the [`Program`]. /// diff --git a/winit/src/application.rs b/winit/src/application.rs index df6e4eec..73ac72b2 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -18,6 +18,8 @@ use iced_native::program::{self, Program}; /// /// When using an [`Application`] with the `debug` feature enabled, a debug view /// can be toggled by pressing `F12`. +/// +/// [`Application`]: trait.Application.html pub trait Application: Program { /// The data needed to initialize your [`Application`]. /// @@ -36,7 +38,7 @@ pub trait Application: Program { /// /// [`Application`]: trait.Application.html /// [`run`]: #method.run.html - /// [`Settings`]: struct.Settings.html + /// [`Settings`]: ../settings/struct.Settings.html fn new(flags: Self::Flags) -> (Self, Command); /// Returns the current title of the [`Application`]. From 16c1261d8265265bc618f03cccc11cddb1f70697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 28 May 2020 04:03:51 +0200 Subject: [PATCH 36/40] Enable `doc_cfg` for `docs.rs` in `iced_glow` --- glow/Cargo.toml | 4 ++++ glow/src/lib.rs | 1 + 2 files changed, 5 insertions(+) diff --git a/glow/Cargo.toml b/glow/Cargo.toml index a18fffe1..262f0264 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -30,3 +30,7 @@ path = "../native" version = "0.1" path = "../graphics" features = ["font-source", "font-fallback", "font-icons", "opengl"] + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] +all-features = true diff --git a/glow/src/lib.rs b/glow/src/lib.rs index 9e9564ca..c427d13a 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -6,6 +6,7 @@ #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg))] mod backend; mod program; From 709ed1f3f7ad8cf67a176763e394aaae4e808e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 28 May 2020 21:30:33 +0200 Subject: [PATCH 37/40] Fix `iced_native` mention of old `window::Backend` --- native/src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/native/src/lib.rs b/native/src/lib.rs index f9a8c477..b67ff2a1 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -9,14 +9,11 @@ //! - Event handling for all the built-in widgets //! - A renderer-agnostic API //! -//! To achieve this, it introduces a bunch of reusable interfaces: +//! To achieve this, it introduces a couple of reusable interfaces: //! //! - A [`Widget`] trait, which is used to implement new widgets: from layout //! requirements to event and drawing logic. //! - A bunch of `Renderer` traits, meant to keep the crate renderer-agnostic. -//! - A [`window::Backend`] trait, leveraging [`raw-window-handle`], which can be -//! implemented by graphical renderers that target _windows_. Window-based -//! shells (like [`iced_winit`]) can use this trait to stay renderer-agnostic. //! //! # Usage //! The strategy to use this crate depends on your particular use case. If you @@ -31,7 +28,6 @@ //! [`druid`]: https://github.com/xi-editor/druid //! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle //! [`Widget`]: widget/trait.Widget.html -//! [`window::Backend`]: window/trait.Backend.html //! [`UserInterface`]: struct.UserInterface.html //! [renderer]: renderer/index.html #![deny(missing_docs)] From 7f9813d4b3cf60563db602414f6e49abdd634ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 28 May 2020 22:44:52 +0200 Subject: [PATCH 38/40] Revert "Merge pull request #362 from hecrj/fix/target-quad-pixels" This reverts commit 40501f630d8a5aa234ea23b7eaae37060e0e08a5, reversing changes made to 5324eb10242a7dd33f5271dc6fc9eeb09eb2cb50. --- wgpu/src/shader/quad.vert | 12 ++++++------ wgpu/src/shader/quad.vert.spv | Bin 3348 -> 3372 bytes 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/wgpu/src/shader/quad.vert b/wgpu/src/shader/quad.vert index 11f95eeb..1d9a4fd2 100644 --- a/wgpu/src/shader/quad.vert +++ b/wgpu/src/shader/quad.vert @@ -21,14 +21,14 @@ layout(location = 4) out float o_BorderRadius; layout(location = 5) out float o_BorderWidth; void main() { - vec2 p_Pos = floor(i_Pos * u_Scale); - vec2 p_Scale = floor(i_Scale * u_Scale); + vec2 p_Pos = i_Pos * u_Scale; + vec2 p_Scale = i_Scale * u_Scale; mat4 i_Transform = mat4( - vec4(p_Scale.x, 0.0, 0.0, 0.0), - vec4(0.0, p_Scale.y, 0.0, 0.0), + vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), + vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), - vec4(p_Pos, 0.0, 1.0) + vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0) ); o_Color = i_Color; @@ -36,7 +36,7 @@ void main() { o_Pos = p_Pos; o_Scale = p_Scale; o_BorderRadius = i_BorderRadius * u_Scale; - o_BorderWidth = floor(i_BorderWidth * u_Scale); + o_BorderWidth = i_BorderWidth * u_Scale; gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0); } diff --git a/wgpu/src/shader/quad.vert.spv b/wgpu/src/shader/quad.vert.spv index b3025a93396f709bb29e9cee5ab8779873115751..7059b51bc4575638f541d299aa07e0a12e4f6599 100644 GIT binary patch literal 3372 zcmZXVU2{}L5QY!=*nlXA2t>u05EYdeQ2|ke1cD|?jDcWKQ8!Dn#DklhxVu5iD=o`g ztK=v7tGuzw=Q%sQoQjj`o$j}L-s$P-IT;+79LchM*+{lO`zh;L8UZd3^^BTGJ#%}#~yRqH5U$nPC zhfCbtck3(bjaCz6FgucBHCJtv*dkyIC}|yUg30&XD#Y;MXP9+xq0rqZ`aBi z6!mLGd!yOzMR|r=S(p5O_)uA^qH;FtR`heIl{E~$TaAr;m!imLU4yTRua)b2``^ho zy5D0PU)DeEU&S}^Z?@{o&Gu@u-EIDiNpvj}cjzlRc{eY%K}Sm5nwEIJ-CfClX&M(k z?-k!%E8n@Ece;)3^=98^H|g7Lwfa8q#J+G?U;E=>s>gh~`!O*0T)>XI9xmpc>6%xVZ42y?HRRw3z4_B z??T&N*Y=HQ2cP#|UGLke#(Uq|p4;_nd+bKCYs}Ul?$5X52+SIYIR|DAV&)#0x>zsY z!ICd>^^VNNag-B?Pq5lG>c`Q{V;ngFgZH4VZyb9+vzVg&1he^QUrKi5==!I?U2iU< z{2pQ!(;nm3dlc*KIaJ=cdx^_u4|u;~$FQGa9!K2sOtSsDX`9cp%*M(W`FQ`~_w45C zp7M<|D-QX_nI|+D@||KfUOxTKuOM0W>rB|EOZ)Nq*Rl5@Q>^r?(}*#OYn@~5S#-wC z6!$)Y@-`x-xc?1i??xWaqiz3+>Hmz`y~3}(l(2|j#x`DCzy0hQ*RL?^b8r3H_IJc< z+uyO@mr0NMYTNJO{~BHH@N3)uV)7fmh6KO<2BQBY{`<_XwT5G8o6Md~OrK)jejDY` zFR!-WA~9=gpFBVu#{a-4`IbLL+*58jjJ3~wkNWH%`}K2V3DLKh?C{;f_M52BZ>4th z-52Qgoj$*d+Tpu{ZlCL0W!BbLWBvxYi})sd|KB2e-|BY>|0DHp9ou!{a{65r3(kA! z4J0_%(f4w0fQbiZ6Ww*BG#g~;&$aUW~Cg@}i}jcz?3u||J^7$a`X2=kAK z-_z)!cEXtYSSMljRorbC-B^9`7XM-&?`#)wFJsL22J=HCN6gprXdCx0-FgxE5AM_J Ayz<6d ztMn)NtGuzw=Q%sQo{B@g>F&3CdU|^LETbdS6Ir$^+s(i2Y_JYyBQROEH>;I$esOtm zrq^GeIe+1t5&N^zGSHX<*`ZP&Ytbj-0F<1o~z>TB) z8$td>;_+;ZfB)sw-h;i>SSor*ZO_Ro_5tkqcJZXy?ooM-+6HU8@u1u6^qv;oP1y00 zxAyJE@>;Xqf*H*Yrdad7jJ`GWjg@!FhxT|jg+H(4bdPr}8goUv=$5s4?|f+2${rMr z8%1}$)g456hg#W}{D1sd*{h;*H~Ur$Yp9hyjC}W->-jcKF`s>nd{y3FNBqNlz5fi` zc>5pmRlWtk(QYiYx+|@2zx6XF(X~w8rLXAa{k-VFPL#YoE&06DU(SCiL&N90;+t#d zTQ~Dwzu8%94fi=s`nKEcq0cvQE?oB4`S>aIV?Mq8B%Eh1V8>gJm-gU|QL}0PuolP? zF${e6LWMJjakF5abB(iJ&v7nJ09V+)zf0RWw~mN+4zykRoda#}sO`59b!+=wXgllL zek0nE&v&ox_uHw)``+5#+x@7;>kM#@(5JMP1*Cm9##*(V zO*#FaBh4Fr?c0fq_&eCfYwN$8{O(^$zKP`5c6K9P+u4otzD#=bPTN@y|3h@^3BR_p zE$8{hJKK?8zq76XIR15{d+paGnse^mK` zGuZ*2dy(%+-xzS2_iq@#MPr@kJs^6d?f)%d*U_hveT8`WeZU#NP8R3wFmOhlLu0(p z5g`9NG444E+$X<}2)Sdx8a-40RjRWV`8exUbmuqDI*z{ctk>Y=<1D{#_sPduucOCV z`aR2g%A5Nn@(mz24dNbeCVq*EeXF;C+^Gs;|2uH*_kCmkX>|G6e+J#X@{uF%`YxRJ zzehdx@EnkH1@|7hoOg+w@1q;5&zxsL#D9Qp&930iqsy5ixC@DMR)V{jIA&FL6@uMyOOwSzN?9==DP-GKU|S-7G18I?|R~@ z`EDf6e9_xEbbDye?2$bQ-%WJ;;r+d@_Y2>r=-$n{Si7}`?=y7koCVh5x%&Km=YeOM z+Z@I^t5ZmQ&Wdxm2>dP7cPrW9`vUuJ^4&pd$Bf-acgEaz52+o#2k6ePK7SXr^*M`Q zfkz;|!>`eIeuv+{$^Vw-_gi%L$(wTpDHk~#=&K-deuus@XA@37ay~(KpS<6;wXK0& zz~2%5p7j_hZ|)rVJuvq(;Q99Y2OuAI8{Izt$sYZiz!-UBCXfZ_fS9eV#36>-2H From 6de0a2c37129b735dac3c6aec461f18a0c1bcbc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 28 May 2020 22:50:40 +0200 Subject: [PATCH 39/40] Use `Sandbox` in `tour` example --- examples/tour/src/main.rs | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index c0bd2efe..ffadb14f 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -1,8 +1,7 @@ use iced::{ - button, executor, scrollable, slider, text_input, Application, Button, - Checkbox, Color, Column, Command, Container, Element, HorizontalAlignment, - Image, Length, Radio, Row, Scrollable, Settings, Slider, Space, Text, - TextInput, + button, scrollable, slider, text_input, Button, Checkbox, Color, Column, + Container, Element, HorizontalAlignment, Image, Length, Radio, Row, + Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput, }; pub fn main() { @@ -19,29 +18,24 @@ pub struct Tour { debug: bool, } -impl Application for Tour { - type Executor = executor::Null; +impl Sandbox for Tour { type Message = Message; - type Flags = (); - fn new(_flags: ()) -> (Tour, Command) { - ( - Tour { - steps: Steps::new(), - scroll: scrollable::State::new(), - back_button: button::State::new(), - next_button: button::State::new(), - debug: false, - }, - Command::none(), - ) + fn new() -> Tour { + Tour { + steps: Steps::new(), + scroll: scrollable::State::new(), + back_button: button::State::new(), + next_button: button::State::new(), + debug: false, + } } fn title(&self) -> String { format!("{} - Iced", self.steps.title()) } - fn update(&mut self, event: Message) -> Command { + fn update(&mut self, event: Message) { match event { Message::BackPressed => { self.steps.go_back(); @@ -53,8 +47,6 @@ impl Application for Tour { self.steps.update(step_msg, &mut self.debug); } } - - Command::none() } fn view(&mut self) -> Element { From e11b5c614f5bf73c137b8b4f24f56047617527eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 28 May 2020 22:57:30 +0200 Subject: [PATCH 40/40] Revert "Target physical pixels for quads in `iced_glow`" This reverts commit 45511a442f707e93fe6e568d2100756b63af7362. --- examples/tour/src/main.rs | 4 ++-- glow/src/shader/quad.vert | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index ffadb14f..c9678b9d 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -764,7 +764,7 @@ mod style { Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), })), border_radius: 12, - shadow_offset: Vector::new(0.0, 1.0), + shadow_offset: Vector::new(1.0, 1.0), text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE), ..button::Style::default() } @@ -773,7 +773,7 @@ mod style { fn hovered(&self) -> button::Style { button::Style { text_color: Color::WHITE, - shadow_offset: Vector::new(0.0, 2.0), + shadow_offset: Vector::new(1.0, 2.0), ..self.active() } } diff --git a/glow/src/shader/quad.vert b/glow/src/shader/quad.vert index ce816550..d37b5c8d 100644 --- a/glow/src/shader/quad.vert +++ b/glow/src/shader/quad.vert @@ -26,14 +26,14 @@ const vec2 positions[4] = vec2[]( void main() { vec2 q_Pos = positions[gl_VertexID]; - vec2 p_Pos = floor(i_Pos * u_Scale); - vec2 p_Scale = floor(i_Scale * u_Scale); + vec2 p_Pos = i_Pos * u_Scale; + vec2 p_Scale = i_Scale * u_Scale; mat4 i_Transform = mat4( - vec4(p_Scale.x, 0.0, 0.0, 0.0), - vec4(0.0, p_Scale.y, 0.0, 0.0), + vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), + vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), - vec4(p_Pos, 0.0, 1.0) + vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0) ); v_Color = i_Color; @@ -41,7 +41,7 @@ void main() { v_Pos = p_Pos; v_Scale = p_Scale; v_BorderRadius = i_BorderRadius * u_Scale; - v_BorderWidth = floor(i_BorderWidth * u_Scale); + v_BorderWidth = i_BorderWidth * u_Scale; gl_Position = u_Transform * i_Transform * vec4(q_Pos, 0.0, 1.0); }