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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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 22ced3485eb6f295faaab1e31d8d1b8d61fc422b 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 05:05:13 +0200 Subject: [PATCH 24/33] 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 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 25/33] 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 26/33] 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 27/33] 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 28/33] 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 29/33] 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 30/33] 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 31/33] 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 32/33] 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 33/33] 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)]