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 - } -}