Merge branch 'master' into improvement/update-wgpu_glyph

This commit is contained in:
Héctor Ramón Jiménez 2020-05-29 02:00:28 +02:00
commit 0cde20b355
135 changed files with 4506 additions and 1800 deletions

View File

@ -12,12 +12,19 @@ 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_wgpu/image"]
# Enables the `Svg` widget
svg = ["iced_wgpu/svg"]
# Enables the `Canvas` widget
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_winit/debug"]
# Enables `tokio` as the `executor::Default` on native platforms
@ -34,6 +41,9 @@ maintenance = { status = "actively-developed" }
members = [
"core",
"futures",
"graphics",
"glow",
"glutin",
"native",
"style",
"web",
@ -66,7 +76,9 @@ 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", 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" }

View File

@ -125,17 +125,29 @@ impl Rectangle<f32> {
None
}
}
/// Rounds the [`Rectangle`] to __unsigned__ integer coordinates.
///
/// [`Rectangle`]: struct.Rectangle.html
pub fn round(self) -> Rectangle<u32> {
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<f32> for Rectangle<u32> {
impl std::ops::Mul<f32> for Rectangle<f32> {
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<Rectangle<u32>> for Rectangle<f32> {
}
}
impl From<Rectangle<f32>> for Rectangle<u32> {
fn from(rectangle: Rectangle<f32>) -> Rectangle<u32> {
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<T> std::ops::Add<Vector<T>> for Rectangle<T>
where
T: std::ops::Add<Output = T>,

View File

@ -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<T = f32> {
/// The width.
pub width: f32,
pub width: T,
/// The height.
pub height: f32,
pub height: T,
}
impl<T> Size<T> {
/// 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

View File

@ -8,4 +8,4 @@ publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }
iced_wgpu = { path = "../../wgpu" }
iced_graphics = { path = "../../graphics" }

View File

@ -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<Message> Widget<Message, Renderer> for Circle {
impl<Message, B> Widget<Message, Renderer<B>> for Circle
where
B: Backend,
{
fn width(&self) -> Length {
Length::Shrink
}
@ -36,7 +39,7 @@ mod circle {
fn layout(
&self,
_renderer: &Renderer,
_renderer: &Renderer<B>,
_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<B>,
_defaults: &Defaults,
layout: Layout<'_>,
_cursor_position: Point,
@ -71,8 +74,11 @@ mod circle {
}
}
impl<'a, Message> Into<Element<'a, Message, Renderer>> for Circle {
fn into(self) -> Element<'a, Message, Renderer> {
impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for Circle
where
B: Backend,
{
fn into(self) -> Element<'a, Message, Renderer<B>> {
Element::new(self)
}
}

View File

@ -8,4 +8,4 @@ publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }
iced_wgpu = { path = "../../wgpu" }
iced_graphics = { path = "../../graphics" }

View File

@ -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<Message> Widget<Message, Renderer> for Rainbow {
impl<Message, B> Widget<Message, Renderer<B>> for Rainbow
where
B: Backend,
{
fn width(&self) -> Length {
Length::Fill
}
@ -38,7 +41,7 @@ mod rainbow {
fn layout(
&self,
_renderer: &Renderer,
_renderer: &Renderer<B>,
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<B>,
_defaults: &Defaults,
layout: Layout<'_>,
cursor_position: Point,
@ -146,8 +149,11 @@ mod rainbow {
}
}
impl<'a, Message> Into<Element<'a, Message, Renderer>> for Rainbow {
fn into(self) -> Element<'a, Message, Renderer> {
impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for Rainbow
where
B: Backend,
{
fn into(self) -> Element<'a, Message, Renderer<B>> {
Element::new(self)
}
}

View File

@ -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<Message> {
match message {
Message::BackgroundColorChanged(color) => {
scene.background_color = color;
self.background_color = color;
}
}
Command::none()
}
pub fn view(&mut self, scene: &Scene) -> Element<Message, Renderer> {
fn view(&mut self) -> Element<Message, Renderer> {
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)

View File

@ -4,12 +4,8 @@ mod scene;
use controls::Controls;
use scene::Scene;
use iced_wgpu::{
wgpu, window::SwapChain, Primitive, Renderer, Settings, Target,
};
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},
@ -22,12 +18,15 @@ 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
// Initialize wgpu
let surface = wgpu::Surface::create(&window);
let (mut device, queue) = futures::executor::block_on(async {
let adapter = wgpu::Adapter::request(
@ -55,20 +54,34 @@ 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;
// Initialize iced
let mut events = Vec::new();
let mut cache = Some(Cache::default());
let mut renderer = Renderer::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 scene = Scene::new(&mut device);
let controls = Controls::new();
// Initialize iced
let mut debug = Debug::new();
let mut renderer =
Renderer::new(Backend::new(&mut device, Settings::default()));
let mut state = program::State::new(
controls,
viewport.logical_size(),
&mut renderer,
&mut debug,
);
// Run event loop
event_loop.run(move |event, _, control_flow| {
@ -82,8 +95,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 => {
@ -99,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),
Size::new(logical_size.width, logical_size.height),
cache.take().unwrap(),
// We update iced
let _ = state.update(
None,
viewport.logical_size(),
&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),
Size::new(logical_size.width, logical_size.height),
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();
}
@ -169,36 +134,41 @@ 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 },
);
// 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.draw(
let mouse_interaction = renderer.backend_mut().draw(
&mut device,
&mut encoder,
Target {
texture: &frame.view,
viewport,
},
&output,
window.scale_factor(),
&["Some debug information!"],
&frame.view,
&viewport,
state.primitive(),
&debug.overlay(),
);
// Then we submit the work

View File

@ -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,

View File

@ -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"

36
glow/Cargo.toml Normal file
View File

@ -0,0 +1,36 @@
[package]
name = "iced_glow"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
description = "A glow renderer for iced"
license = "MIT AND OFL-1.1"
repository = "https://github.com/hecrj/iced"
[features]
canvas = ["iced_graphics/canvas"]
# Not supported yet!
image = []
svg = []
[dependencies]
glow = "0.4"
glow_glyph = "0.2"
glyph_brush = "0.7"
euclid = "0.20"
bytemuck = "1.2"
glam = "0.8"
log = "0.4"
[dependencies.iced_native]
version = "0.2"
path = "../native"
[dependencies.iced_graphics]
version = "0.1"
path = "../graphics"
features = ["font-source", "font-fallback", "font-icons", "opengl"]
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true

216
glow/src/backend.rs Normal file
View File

@ -0,0 +1,216 @@
use crate::quad;
use crate::text;
use crate::triangle;
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::{Font, HorizontalAlignment, Size, VerticalAlignment};
/// A [`glow`] renderer.
///
/// [`glow`]: https://github.com/grovesNL/glow
#[derive(Debug)]
pub struct Backend {
quad_pipeline: quad::Pipeline,
text_pipeline: text::Pipeline,
triangle_pipeline: triangle::Pipeline,
}
impl Backend {
/// 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);
Self {
quad_pipeline,
text_pipeline,
triangle_pipeline,
}
}
pub fn draw<T: AsRef<str>>(
&mut self,
gl: &glow::Context,
viewport: &Viewport,
(primitive, mouse_interaction): &(Primitive, mouse::Interaction),
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);
layers.push(Layer::overlay(overlay_text, viewport));
for layer in layers {
self.flush(
gl,
scale_factor,
projection,
&layer,
viewport_size.height,
);
}
*mouse_interaction
}
fn flush(
&mut self,
gl: &glow::Context,
scale_factor: f32,
transformation: Transformation,
layer: &Layer<'_>,
target_height: u32,
) {
let mut bounds = (layer.bounds * scale_factor).round();
bounds.height = bounds.height.min(target_height);
if !layer.quads.is_empty() {
self.quad_pipeline.draw(
gl,
target_height,
&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_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.bounds.x * scale_factor).round(),
(text.bounds.y * 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.width * scale_factor).ceil(),
(text.bounds.height * scale_factor).ceil(),
),
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 => {
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);
}
self.text_pipeline.draw_queued(
gl,
transformation,
glow_glyph::Region {
x: bounds.x,
y: target_height - (bounds.y + bounds.height),
width: bounds.width,
height: bounds.height,
},
);
}
}
}
impl iced_graphics::Backend for Backend {
fn trim_measurements(&mut self) {
self.text_pipeline.trim_measurement_cache()
}
}
impl backend::Text for Backend {
const ICON_FONT: Font = font::ICONS;
const CHECKMARK_ICON: char = font::CHECKMARK_ICON;
fn measure(
&self,
contents: &str,
size: f32,
font: Font,
bounds: Size,
) -> (f32, f32) {
self.text_pipeline.measure(contents, size, font, bounds)
}
}
#[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)
}
}

39
glow/src/lib.rs Normal file
View File

@ -0,0 +1,39 @@
//! 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(rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg))]
mod backend;
mod program;
mod quad;
mod text;
mod triangle;
pub mod settings;
pub mod widget;
pub mod window;
pub use settings::Settings;
pub(crate) use backend::Backend;
pub(crate) use iced_graphics::Transformation;
#[doc(no_inline)]
pub use widget::*;
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<Backend>;

39
glow/src/program.rs Normal file
View File

@ -0,0 +1,39 @@
use glow::HasContext;
pub unsafe fn create(
gl: &glow::Context,
shader_sources: &[(u32, &str)],
) -> <glow::Context as HasContext>::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
}

235
glow/src/quad.rs Normal file
View File

@ -0,0 +1,235 @@
use crate::program;
use crate::Transformation;
use glow::HasContext;
use iced_graphics::layer;
use iced_native::Rectangle;
const MAX_INSTANCES: usize = 100_000;
#[derive(Debug)]
pub struct Pipeline {
program: <glow::Context as HasContext>::Program,
vertex_array: <glow::Context as HasContext>::VertexArray,
instances: <glow::Context as HasContext>::Buffer,
transform_location: <glow::Context as HasContext>::UniformLocation,
scale_location: <glow::Context as HasContext>::UniformLocation,
screen_height_location: <glow::Context as HasContext>::UniformLocation,
current_transform: Transformation,
current_scale: f32,
current_target_height: u32,
}
impl Pipeline {
pub fn new(gl: &glow::Context) -> Pipeline {
let program = unsafe {
program::create(
gl,
&[
(glow::VERTEX_SHADER, include_str!("shader/quad.vert")),
(glow::FRAGMENT_SHADER, include_str!("shader/quad.frag")),
],
)
};
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(transform_location),
false,
&matrix,
);
gl.uniform_1_f32(Some(scale_location), 1.0);
gl.uniform_1_f32(Some(screen_height_location), 0.0);
gl.use_program(None);
}
let (vertex_array, instances) =
unsafe { create_instance_buffer(gl, MAX_INSTANCES) };
Pipeline {
program,
vertex_array,
instances,
transform_location,
scale_location,
screen_height_location,
current_transform: Transformation::identity(),
current_scale: 1.0,
current_target_height: 0,
}
}
pub fn draw(
&mut self,
gl: &glow::Context,
target_height: u32,
instances: &[layer::Quad],
transformation: Transformation,
scale: f32,
bounds: Rectangle<u32>,
) {
unsafe {
gl.enable(glow::SCISSOR_TEST);
gl.scissor(
bounds.x as i32,
(target_height - (bounds.y + bounds.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 {
let matrix: [f32; 16] = transformation.into();
gl.uniform_matrix_4_f32_slice(
Some(self.transform_location),
false,
&matrix,
);
self.current_transform = transformation;
}
}
if scale != self.current_scale {
unsafe {
gl.uniform_1_f32(Some(self.scale_location), scale);
}
self.current_scale = scale;
}
if target_height != self.current_target_height {
unsafe {
gl.uniform_1_f32(
Some(self.screen_height_location),
target_height as f32,
);
}
self.current_target_height = target_height;
}
let mut i = 0;
let total = instances.len();
while i < total {
let end = (i + MAX_INSTANCES).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 += MAX_INSTANCES;
}
unsafe {
gl.bind_vertex_array(None);
gl.use_program(None);
gl.disable(glow::SCISSOR_TEST);
}
}
}
unsafe fn create_instance_buffer(
gl: &glow::Context,
size: usize,
) -> (
<glow::Context as HasContext>::VertexArray,
<glow::Context as HasContext>::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::<layer::Quad>()) as i32,
glow::DYNAMIC_DRAW,
);
let stride = std::mem::size_of::<layer::Quad>() 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)
}

25
glow/src/settings.rs Normal file
View File

@ -0,0 +1,25 @@
//! Configure a renderer.
pub use iced_graphics::Antialiasing;
/// 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<Antialiasing>,
}
impl Default for Settings {
fn default() -> Settings {
Settings {
default_font: None,
antialiasing: None,
}
}
}

70
glow/src/shader/quad.frag Normal file
View File

@ -0,0 +1,70 @@
#version 330
uniform float u_ScreenHeight;
in vec4 v_Color;
in vec4 v_BorderColor;
in vec2 v_Pos;
in vec2 v_Scale;
in float v_BorderRadius;
in float v_BorderWidth;
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.0),
max(max(top_left_distance.y, bottom_right_distance.y), 0.0)
);
return sqrt(distance.x * distance.x + distance.y * distance.y);
}
void main() {
vec4 mixed_color;
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.0);
float internal_distance = distance(
fragCoord,
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(
fragCoord,
v_Pos,
v_Scale,
v_BorderRadius
);
float radius_alpha =
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);
}

47
glow/src/shader/quad.vert Normal file
View File

@ -0,0 +1,47 @@
#version 330
uniform mat4 u_Transform;
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;
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),
vec2(0.0, 1.0),
vec2(1.0, 0.0),
vec2(1.0, 1.0)
);
void main() {
vec2 q_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)
);
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(q_Pos, 0.0, 1.0);
}

View File

@ -0,0 +1,9 @@
#version 330
in vec4 v_Color;
out vec4 o_Color;
void main() {
o_Color = v_Color;
}

View File

@ -0,0 +1,13 @@
#version 330
uniform mat4 u_Transform;
layout(location = 0) in vec2 i_Position;
layout(location = 1) in vec4 i_Color;
out vec4 v_Color;
void main() {
gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0);
v_Color = i_Color;
}

151
glow/src/text.rs Normal file
View File

@ -0,0 +1,151 @@
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<glow_glyph::GlyphBrush>,
draw_font_map: RefCell<HashMap<String, glow_glyph::FontId>>,
measure_brush: RefCell<glyph_brush::GlyphBrush<()>>,
}
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(|_| font::FALLBACK.to_vec())
});
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..."
);
ab_glyph::FontArc::try_from_slice(font::FALLBACK)
.expect("Load fallback font")
});
let draw_brush =
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),
}
}
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 {
bounds: (bounds.width, bounds.height),
text: vec![glow_glyph::Text {
text: content,
scale: size.into(),
font_id: glow_glyph::FontId(font_id),
extra: glow_glyph::Extra::default(),
}],
..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 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
// 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;
}
let font = ab_glyph::FontArc::try_from_slice(bytes)
.expect("Load font");
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
.borrow_mut()
.insert(String::from(name), font_id);
font_id
}
}
}
}

292
glow/src/triangle.rs Normal file
View File

@ -0,0 +1,292 @@
//! Draw meshes of triangles.
use crate::program;
use crate::Transformation;
use glow::HasContext;
use iced_graphics::layer;
use std::marker::PhantomData;
pub use iced_graphics::triangle::{Mesh2D, Vertex2D};
const VERTEX_BUFFER_SIZE: usize = 10_000;
const INDEX_BUFFER_SIZE: usize = 10_000;
#[derive(Debug)]
pub(crate) struct Pipeline {
program: <glow::Context as HasContext>::Program,
vertex_array: <glow::Context as HasContext>::VertexArray,
vertices: Buffer<Vertex2D>,
indices: Buffer<u32>,
transform_location: <glow::Context as HasContext>::UniformLocation,
current_transform: Transformation,
}
impl Pipeline {
pub fn new(gl: &glow::Context) -> Pipeline {
let program = unsafe {
program::create(
gl,
&[
(glow::VERTEX_SHADER, include_str!("shader/triangle.vert")),
(
glow::FRAGMENT_SHADER,
include_str!("shader/triangle.frag"),
),
],
)
};
let transform_location =
unsafe { gl.get_uniform_location(program, "u_Transform") }
.expect("Get transform location");
unsafe {
gl.use_program(Some(program));
let transform: [f32; 16] = Transformation::identity().into();
gl.uniform_matrix_4_f32_slice(
Some(transform_location),
false,
&transform,
);
gl.use_program(None);
}
let vertex_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::<Vertex2D>() 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,
transform_location,
current_transform: Transformation::identity(),
}
}
pub fn draw(
&mut self,
gl: &glow::Context,
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));
}
// 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::<Vertex2D>()) as i32,
bytemuck::cast_slice(&buffers.vertices),
);
gl.buffer_sub_data_u8_slice(
glow::ELEMENT_ARRAY_BUFFER,
(last_index * std::mem::size_of::<u32>()) 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 {
let matrix: [f32; 16] = transform.into();
gl.uniform_matrix_4_f32_slice(
Some(self.transform_location),
false,
&matrix,
);
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::<u32>()) 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);
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct Uniforms {
transform: [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(),
}
}
}
impl From<Transformation> for Uniforms {
fn from(transformation: Transformation) -> Uniforms {
Self {
transform: transformation.into(),
}
}
}
#[derive(Debug)]
struct Buffer<T> {
raw: <glow::Context as HasContext>::Buffer,
target: u32,
usage: u32,
size: usize,
phantom: PhantomData<T>,
}
impl<T> Buffer<T> {
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::<T>()) as i32,
self.usage,
);
self.size = size;
}
}
}

58
glow/src/widget.rs Normal file
View File

@ -0,0 +1,58 @@
//! 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;
#[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};
/// 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<Renderer>;

15
glow/src/widget/button.rs Normal file
View File

@ -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_graphics::button::{Style, StyleSheet};
pub use iced_native::button::State;
/// 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>;

View File

@ -0,0 +1,9 @@
//! 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
pub use iced_graphics::canvas::*;

View File

@ -0,0 +1,9 @@
//! Show toggle controls using checkboxes.
use crate::Renderer;
pub use iced_graphics::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<Message> = iced_native::Checkbox<Message, Renderer>;

View File

@ -0,0 +1,10 @@
//! Decorate content and apply alignment.
use crate::Renderer;
pub use iced_graphics::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>;

View File

@ -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>;

View File

@ -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_graphics::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<Renderer>;

10
glow/src/widget/radio.rs Normal file
View File

@ -0,0 +1,10 @@
//! Create choices using radio buttons.
use crate::Renderer;
pub use iced_graphics::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<Message> = iced_native::Radio<Message, Renderer>;

View File

@ -0,0 +1,13 @@
//! 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;
/// 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>;

16
glow/src/widget/slider.rs Normal file
View File

@ -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_graphics::slider::{Handle, HandleShape, Style, StyleSheet};
pub use iced_native::slider::State;
/// 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>;

View File

@ -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_graphics::text_input::{Style, StyleSheet};
pub use iced_native::text_input::State;
/// 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>;

4
glow/src/window.rs Normal file
View File

@ -0,0 +1,4 @@
//! Display rendering results on windows.
mod compositor;
pub use compositor::Compositor;

View File

@ -0,0 +1,74 @@
use crate::{Backend, Renderer, Settings, Viewport};
use core::ffi::c_void;
use glow::HasContext;
use iced_graphics::{Antialiasing, Size};
use iced_native::mouse;
/// A window graphics backend for iced powered by `glow`.
#[allow(missing_debug_implementations)]
pub struct Compositor {
gl: glow::Context,
}
impl iced_graphics::window::GLCompositor for Compositor {
type Settings = Settings;
type Renderer = Renderer;
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);
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);
// 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<u32>) {
unsafe {
self.gl.viewport(
0,
0,
physical_size.width as i32,
physical_size.height as i32,
);
}
}
fn draw<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
viewport: &Viewport,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
) -> mouse::Interaction {
let gl = &self.gl;
unsafe {
gl.clear(glow::COLOR_BUFFER_BIT);
}
renderer.backend_mut().draw(gl, viewport, output, overlay)
}
}

30
glutin/Cargo.toml Normal file
View File

@ -0,0 +1,30 @@
[package]
name = "iced_glutin"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
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"]

27
glutin/README.md Normal file
View File

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

209
glutin/src/application.rs Normal file
View File

@ -0,0 +1,209 @@
//! 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::{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<A, E, C>(
settings: Settings<A::Flags>,
compositor_settings: C::Settings,
) where
A: Application + 'static,
E: Executor + 'static,
C: window::GLCompositor<Renderer = A::Renderer> + 'static,
{
use glutin::{
event,
event_loop::{ControlFlow, EventLoop},
ContextBuilder,
};
let mut debug = Debug::new();
debug.startup_started();
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());
Runtime::new(executor, proxy)
};
let flags = settings.flags;
let (application, init_command) = runtime.enter(|| A::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 builder = settings.window.into_builder(
&title,
mode,
event_loop.primary_monitor(),
);
let context = ContextBuilder::new()
.with_vsync(true)
.with_multisampling(C::sample_count(&compositor_settings) as u16)
.build_windowed(builder, &event_loop)
.expect("Open window");
#[allow(unsafe_code)]
unsafe {
context.make_current().expect("Make OpenGL context current")
}
};
let clipboard = Clipboard::new(&context.window());
let mut mouse_interaction = mouse::Interaction::default();
let mut modifiers = glutin::event::ModifiersState::default();
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;
#[allow(unsafe_code)]
let (mut compositor, mut renderer) = unsafe {
C::new(compositor_settings, |address| {
context.get_proc_address(address)
})
};
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;
}
})
}

23
glutin/src/lib.rs Normal file
View File

@ -0,0 +1,23 @@
//! 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 iced_winit::settings;
pub use iced_winit::Mode;
#[doc(no_inline)]
pub use application::Application;
#[doc(no_inline)]
pub use settings::Settings;

37
graphics/Cargo.toml Normal file
View File

@ -0,0 +1,37 @@
[package]
name = "iced_graphics"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
[features]
canvas = ["lyon"]
font-source = ["font-kit"]
font-fallback = []
font-icons = []
opengl = []
[dependencies]
bytemuck = "1.2"
glam = "0.8"
raw-window-handle = "0.3"
[dependencies.iced_native]
version = "0.2"
path = "../native"
[dependencies.iced_style]
version = "0.1"
path = "../style"
[dependencies.lyon]
version = "0.15"
optional = true
[dependencies.font-kit]
version = "0.6"
optional = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true

View File

@ -0,0 +1,26 @@
/// 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 {
/// Returns the amount of samples of the [`Antialiasing`].
///
/// [`Antialiasing`]: enum.Antialiasing.html
pub fn sample_count(self) -> u32 {
match self {
Antialiasing::MSAAx2 => 2,
Antialiasing::MSAAx4 => 4,
Antialiasing::MSAAx8 => 8,
Antialiasing::MSAAx16 => 16,
}
}
}

50
graphics/src/backend.rs Normal file
View File

@ -0,0 +1,50 @@
//! 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,
size: f32,
font: Font,
bounds: Size,
) -> (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);
}

33
graphics/src/font.rs Normal file
View File

@ -0,0 +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}';

View File

@ -1,18 +1,24 @@
pub use font_kit::{
error::SelectionError as LoadError, family_name::FamilyName as Family,
};
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<Vec<u8>, LoadError> {
let font = self.raw.select_best_match(
families,

359
graphics/src/layer.rs Normal file
View File

@ -0,0 +1,359 @@
//! Organize rendering primitives into a flattened list of layers.
use crate::image;
use crate::svg;
use crate::triangle;
use crate::{
Background, Font, HorizontalAlignment, Point, Primitive, Rectangle, Size,
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<Quad>,
/// The triangle meshes of the [`Layer`].
///
/// [`Layer`]: struct.Layer.html
pub meshes: Vec<Mesh<'a>>,
/// The text of the [`Layer`].
///
/// [`Layer`]: struct.Layer.html
pub text: Vec<Text<'a>>,
/// The images of the [`Layer`].
///
/// [`Layer`]: struct.Layer.html
pub images: Vec<Image>,
}
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,
quads: Vec::new(),
meshes: Vec::new(),
text: Vec::new(),
images: Vec::new(),
}
}
/// 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<str>], viewport: &Viewport) -> Self {
let mut overlay =
Layer::new(Rectangle::with_size(viewport.logical_size()));
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
}
/// 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,
) -> Vec<Self> {
let first_layer =
Layer::new(Rectangle::with_size(viewport.logical_size()));
let mut layers = vec![first_layer];
Self::process_primitive(&mut layers, Vector::new(0.0, 0.0), primitive);
layers
}
fn process_primitive(
layers: &mut Vec<Self>,
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,
],
size: [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.bounds.intersection(&bounds) {
layer.meshes.push(Mesh {
origin: Point::new(translation.x, translation.y),
buffers,
clip_bounds,
});
}
}
Primitive::Clip {
bounds,
offset,
content,
} => {
let layer = layers.last_mut().unwrap();
let translated_bounds = *bounds + translation;
// Only draw visible content
if let Some(clip_bounds) =
layer.bounds.intersection(&translated_bounds)
{
let clip_layer = Layer::new(clip_bounds);
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,
});
}
}
}
}
/// 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],
/// 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<f32>,
}
/// 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 {}

40
graphics/src/lib.rs Normal file
View File

@ -0,0 +1,40 @@
//! 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;
mod renderer;
mod transformation;
mod viewport;
mod widget;
pub mod backend;
pub mod font;
pub mod layer;
pub mod triangle;
pub mod window;
#[doc(no_inline)]
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,
};

View File

@ -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.
///

98
graphics/src/renderer.rs Normal file
View File

@ -0,0 +1,98 @@
use crate::{Backend, Defaults, Primitive};
use iced_native::layout::{self, Layout};
use iced_native::mouse;
use iced_native::{Background, Color, Element, Point, Widget};
/// A backend-agnostic renderer that supports all the built-in widgets.
#[derive(Debug)]
pub struct Renderer<B: Backend> {
backend: B,
}
impl<B: Backend> Renderer<B> {
/// 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
}
}
impl<B> iced_native::Renderer for Renderer<B>
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<B> layout::Debugger for Renderer<B>
where
B: Backend,
{
fn explain<Message>(
&mut self,
defaults: &Defaults,
widget: &dyn Widget<Message, Self>,
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<Primitive>,
) {
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);
}
}

32
graphics/src/triangle.rs Normal file
View File

@ -0,0 +1,32 @@
//! Draw geometry using meshes of triangles.
/// 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<Vertex2D>,
/// 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<u32>,
}
/// 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],
}
#[allow(unsafe_code)]
unsafe impl bytemuck::Zeroable for Vertex2D {}
#[allow(unsafe_code)]
unsafe impl bytemuck::Pod for Vertex2D {}

70
graphics/src/viewport.rs Normal file
View File

@ -0,0 +1,70 @@
use crate::{Size, Transformation};
/// A viewing region for displaying computer graphics.
#[derive(Debug)]
pub struct Viewport {
physical_size: Size<u32>,
logical_size: Size<f32>,
scale_factor: f64,
projection: Transformation,
}
impl Viewport {
/// Creates a new [`Viewport`] with the given physical dimensions and scale
/// factor.
///
/// [`Viewport`]: struct.Viewport.html
pub fn with_physical_size(size: Size<u32>, scale_factor: f64) -> Viewport {
Viewport {
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),
}
}
/// Returns the physical size of the [`Viewport`].
///
/// [`Viewport`]: struct.Viewport.html
pub fn physical_size(&self) -> Size<u32> {
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<f32> {
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
}
}

59
graphics/src/widget.rs Normal file
View File

@ -0,0 +1,59 @@
//! 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 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 row;
mod space;
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 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")]
#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))]
pub mod canvas;
#[cfg(feature = "canvas")]
#[doc(no_inline)]
pub use canvas::Canvas;

View File

@ -1,9 +1,29 @@
use crate::{button::StyleSheet, defaults, Defaults, Primitive, Renderer};
//! 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::defaults::{self, Defaults};
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::{
mouse, Background, Color, Element, Layout, Point, Rectangle, Vector,
Background, Color, Element, Layout, Point, Rectangle, Vector,
};
impl iced_native::button::Renderer for 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<Backend>>;
impl<B> iced_native::button::Renderer for Renderer<B>
where
B: Backend,
{
const DEFAULT_PADDING: u16 = 5;
type Style = Box<dyn StyleSheet>;

View File

@ -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<Geometry>{
/// // 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<Message, P: Program<Message>> {
width: Length,
height: Length,
program: P,
phantom: PhantomData<Message>,
}
impl<Message, P: Program<Message>> Canvas<Message, P> {
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<Message, P, B> Widget<Message, Renderer<B>> for Canvas<Message, P>
where
P: Program<Message>,
B: Backend,
{
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(
&self,
_renderer: &Renderer<B>,
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<Message>,
_renderer: &Renderer<B>,
_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<B>,
_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::<Marker>().hash(state);
self.width.hash(state);
self.height.hash(state);
}
}
impl<'a, Message, P, B> From<Canvas<Message, P>>
for Element<'a, Message, Renderer<B>>
where
Message: 'static,
P: Program<Message> + 'a,
B: Backend,
{
fn from(canvas: Canvas<Message, P>) -> Element<'a, Message, Renderer<B>> {
Element::new(canvas)
}
}

View File

@ -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,
});
}

View File

@ -1,9 +1,22 @@
use crate::{checkbox::StyleSheet, Primitive, Renderer};
use iced_native::{
checkbox, mouse, HorizontalAlignment, Rectangle, VerticalAlignment,
};
//! Show toggle controls using checkboxes.
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use iced_native::checkbox;
use iced_native::mouse;
use iced_native::{HorizontalAlignment, Rectangle, VerticalAlignment};
impl checkbox::Renderer for 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<Message, Backend> =
iced_native::Checkbox<Message, Renderer<Backend>>;
impl<B> checkbox::Renderer for Renderer<B>
where
B: Backend + backend::Text,
{
type Style = Box<dyn StyleSheet>;
const DEFAULT_SIZE: u16 = 20;
@ -35,8 +48,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(),

View File

@ -1,11 +1,20 @@
use crate::{Primitive, Renderer};
use iced_native::{mouse, row, Element, Layout, Point};
use crate::{Backend, Primitive, Renderer};
use iced_native::column;
use iced_native::mouse;
use iced_native::{Element, Layout, Point};
impl row::Renderer for Renderer {
/// A container that distributes its contents vertically.
pub type Column<'a, Message, Backend> =
iced_native::Column<'a, Message, Renderer<Backend>>;
impl<B> column::Renderer for Renderer<B>
where
B: Backend,
{
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
children: &[Element<'_, Message, Self>],
content: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
) -> Self::Output {
@ -13,7 +22,7 @@ impl row::Renderer for Renderer {
(
Primitive::Group {
primitives: children
primitives: content
.iter()
.zip(layout.children())
.map(|(child, layout)| {

View File

@ -1,7 +1,22 @@
use crate::{container, defaults, Defaults, Primitive, Renderer};
//! Decorate content and apply alignment.
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 {
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<Backend>>;
impl<B> iced_native::container::Renderer for Renderer<B>
where
B: Backend,
{
type Style = Box<dyn container::StyleSheet>;
fn draw<Message>(

View File

@ -1,9 +1,18 @@
//! Display images in your user interface.
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 {
pub use iced_native::image::{Handle, Image};
impl<B> image::Renderer for Renderer<B>
where
B: Backend + backend::Image,
{
fn dimensions(&self, handle: &image::Handle) -> (u32, u32) {
self.image_pipeline.dimensions(handle)
self.backend().dimensions(handle)
}
fn draw(

View File

@ -1,11 +1,36 @@
use crate::{Primitive, Renderer};
use iced_native::{
mouse,
pane_grid::{self, Axis, Pane},
Element, Layout, Point, Rectangle, Vector,
//! 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::{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,
State,
};
impl pane_grid::Renderer for Renderer {
/// 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<Backend>>;
impl<B> pane_grid::Renderer for Renderer<B>
where
B: Backend,
{
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,

View File

@ -1,7 +1,26 @@
use crate::{progress_bar::StyleSheet, Primitive, Renderer};
use iced_native::{mouse, progress_bar, Color, Rectangle};
//! 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::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::progress_bar;
use iced_native::{Color, Rectangle};
impl progress_bar::Renderer for 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<Backend> = iced_native::ProgressBar<Renderer<Backend>>;
impl<B> progress_bar::Renderer for Renderer<B>
where
B: Backend,
{
type Style = Box<dyn StyleSheet>;
const DEFAULT_HEIGHT: u16 = 30;

View File

@ -1,10 +1,25 @@
use crate::{radio::StyleSheet, Primitive, Renderer};
use iced_native::{mouse, radio, Background, Color, Rectangle};
//! Create choices using radio buttons.
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};
/// A circular button representing a choice.
///
/// This is an alias of an `iced_native` radio button with an
/// `iced_wgpu::Renderer`.
pub type Radio<Message, Backend> =
iced_native::Radio<Message, Renderer<Backend>>;
const SIZE: f32 = 28.0;
const DOT_SIZE: f32 = SIZE / 2.0;
impl radio::Renderer for Renderer {
impl<B> radio::Renderer for Renderer<B>
where
B: Backend,
{
type Style = Box<dyn StyleSheet>;
const DEFAULT_SIZE: u16 = SIZE as u16;

View File

@ -1,7 +1,16 @@
use crate::{Primitive, Renderer};
use iced_native::{column, mouse, Element, Layout, Point};
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::row;
use iced_native::{Element, Layout, Point};
impl column::Renderer for Renderer {
/// A container that distributes its contents horizontally.
pub type Row<'a, Message, Backend> =
iced_native::Row<'a, Message, Renderer<Backend>>;
impl<B> row::Renderer for Renderer<B>
where
B: Backend,
{
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,

View File

@ -1,10 +1,27 @@
use crate::{Primitive, Renderer};
use iced_native::{mouse, scrollable, Background, Color, Rectangle, Vector};
//! Navigate an endless amount of content with a scrollbar.
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};
/// 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<Backend>>;
const SCROLLBAR_WIDTH: u16 = 10;
const SCROLLBAR_MARGIN: u16 = 2;
impl scrollable::Renderer for Renderer {
impl<B> scrollable::Renderer for Renderer<B>
where
B: Backend,
{
type Style = Box<dyn iced_style::scrollable::StyleSheet>;
fn scrollbar(

View File

@ -1,12 +1,30 @@
use crate::{
slider::{HandleShape, StyleSheet},
Primitive, Renderer,
};
use iced_native::{mouse, slider, Background, Color, Point, Rectangle};
//! 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::{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};
/// 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<Backend>>;
const HANDLE_HEIGHT: f32 = 22.0;
impl slider::Renderer for Renderer {
impl<B> slider::Renderer for Renderer<B>
where
B: Backend,
{
type Style = Box<dyn StyleSheet>;
fn height(&self) -> u32 {

View File

@ -0,0 +1,15 @@
use crate::{Backend, Primitive, Renderer};
use iced_native::mouse;
use iced_native::space;
use iced_native::Rectangle;
pub use iced_native::Space;
impl<B> space::Renderer for Renderer<B>
where
B: Backend,
{
fn draw(&mut self, _bounds: Rectangle) -> Self::Output {
(Primitive::None, mouse::Interaction::default())
}
}

View File

@ -1,9 +1,16 @@
//! Display vector graphics in your application.
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use iced_native::{mouse, svg, Layout};
impl svg::Renderer for Renderer {
pub use iced_native::svg::{Handle, Svg};
impl<B> svg::Renderer for Renderer<B>
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(

View File

@ -1,12 +1,23 @@
//! Write some text for your users to read.
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use iced_native::mouse;
use iced_native::text;
use iced_native::{
mouse, text, Color, Font, HorizontalAlignment, Rectangle, Size,
VerticalAlignment,
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<Backend> = iced_native::Text<Renderer<Backend>>;
use std::f32;
impl text::Renderer for Renderer {
impl<B> text::Renderer for Renderer<B>
where
B: Backend + backend::Text,
{
type Font = Font;
const DEFAULT_SIZE: u16 = 20;
@ -18,7 +29,7 @@ impl text::Renderer for Renderer {
font: Font,
bounds: Size,
) -> (f32, f32) {
self.text_pipeline
self.backend()
.measure(content, f32::from(size), font, bounds)
}

View File

@ -1,14 +1,32 @@
use crate::{text_input::StyleSheet, Primitive, Renderer};
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
//!
//! [`TextInput`]: struct.TextInput.html
//! [`State`]: struct.State.html
use crate::backend::{self, Backend};
use crate::{Primitive, Renderer};
use iced_native::mouse;
use iced_native::text_input::{self, cursor};
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 {
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<Backend>>;
impl<B> text_input::Renderer for Renderer<B>
where
B: Backend + backend::Text,
{
type Style = Box<dyn StyleSheet>;
fn default_size(&self) -> u16 {
@ -17,12 +35,10 @@ impl text_input::Renderer for Renderer {
}
fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 {
let (width, _) = self.text_pipeline.measure(
value,
f32::from(size),
font,
Size::INFINITY,
);
let backend = self.backend();
let (width, _) =
backend.measure(value, f32::from(size), font, Size::INFINITY);
width
}
@ -234,14 +250,17 @@ impl text_input::Renderer for Renderer {
}
}
fn measure_cursor_and_scroll_offset(
renderer: &Renderer,
fn measure_cursor_and_scroll_offset<B>(
renderer: &Renderer<B>,
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();

10
graphics/src/window.rs Normal file
View File

@ -0,0 +1,10 @@
//! Draw graphics to window surfaces.
mod compositor;
#[cfg(feature = "opengl")]
mod gl_compositor;
pub use compositor::Compositor;
#[cfg(feature = "opengl")]
pub use gl_compositor::GLCompositor;

View File

@ -1,14 +1,14 @@
use crate::mouse;
use crate::Viewport;
use iced_native::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;
/// The iced renderer of the backend.
type Renderer: crate::Renderer;
type Renderer: iced_native::Renderer;
/// The surface of the backend.
type Surface;
@ -16,7 +16,7 @@ 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);
@ -48,8 +48,8 @@ pub trait Backend: Sized {
&mut self,
renderer: &mut Self::Renderer,
swap_chain: &mut Self::SwapChain,
output: &<Self::Renderer as crate::Renderer>::Output,
scale_factor: f64,
viewport: &Viewport,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
) -> mouse::Interaction;
}

View File

@ -0,0 +1,67 @@
use crate::{Size, Viewport};
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<u32>);
/// Draws the provided output with the given [`Renderer`].
///
/// [`Compositor`]: trait.Compositor.html
fn draw<T: AsRef<str>>(
&mut self,
renderer: &mut Self::Renderer,
viewport: &Viewport,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
) -> mouse::Interaction;
}

View File

@ -7,9 +7,11 @@ description = "A renderer-agnostic library for native GUIs"
license = "MIT"
repository = "https://github.com/hecrj/iced"
[features]
debug = []
[dependencies]
twox-hash = "1.5"
raw-window-handle = "0.3"
unicode-segmentation = "1.6"
[dependencies.iced_core]

View File

@ -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();

View File

@ -1,3 +1,4 @@
#![allow(missing_docs)]
#[derive(Debug)]
pub struct Debug;

View File

@ -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)]
@ -42,6 +38,7 @@
pub mod keyboard;
pub mod layout;
pub mod mouse;
pub mod program;
pub mod renderer;
pub mod subscription;
pub mod widget;
@ -54,6 +51,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 +70,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;

39
native/src/program.rs Normal file
View File

@ -0,0 +1,39 @@
//! Build interactive programs using The Elm Architecture.
use crate::{Command, Element, Renderer};
mod state;
pub use state::State;
/// The core of a user interface application following The Elm Architecture.
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.
///
/// [`Program`]: 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<Self::Message>;
/// Returns the widgets to display in the [`Program`].
///
/// These widgets can produce __messages__ based on user interaction.
///
/// [`Program`]: trait.Program.html
fn view(&mut self) -> Element<'_, Self::Message, Self::Renderer>;
}

185
native/src/program/state.rs Normal file
View File

@ -0,0 +1,185 @@
use crate::{
Cache, Clipboard, Command, Debug, Event, Program, Renderer, Size,
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<P>
where
P: Program + 'static,
{
program: P,
cache: Option<Cache>,
primitive: <P::Renderer as Renderer>::Output,
queued_events: Vec<Event>,
queued_messages: Vec<P::Message>,
}
impl<P> State<P>
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,
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(),
}
}
/// 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) -> &<P::Renderer as Renderer>::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>,
bounds: Size,
renderer: &mut P::Renderer,
debug: &mut Debug,
) -> Option<Command<P::Message>> {
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
}

View File

@ -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))

View File

@ -14,37 +14,39 @@ impl Axis {
&self,
rectangle: &Rectangle,
ratio: f32,
halved_spacing: f32,
spacing: f32,
) -> (Rectangle, Rectangle) {
match self {
Axis::Horizontal => {
let height_top = (rectangle.height * ratio).round();
let height_bottom = rectangle.height - height_top;
let height_top =
(rectangle.height * ratio - spacing / 2.0).round();
let height_bottom = rectangle.height - height_top - spacing;
(
Rectangle {
height: height_top - halved_spacing,
height: height_top,
..*rectangle
},
Rectangle {
y: rectangle.y + height_top + halved_spacing,
height: height_bottom - halved_spacing,
y: rectangle.y + height_top + spacing,
height: height_bottom,
..*rectangle
},
)
}
Axis::Vertical => {
let width_left = (rectangle.width * ratio).round();
let width_right = rectangle.width - width_left;
let width_left =
(rectangle.width * ratio - spacing / 2.0).round();
let width_right = rectangle.width - width_left - spacing;
(
Rectangle {
width: width_left - halved_spacing,
width: width_left,
..*rectangle
},
Rectangle {
x: rectangle.x + width_left + halved_spacing,
width: width_right - halved_spacing,
x: rectangle.x + width_left + spacing,
width: width_right,
..*rectangle
},
)
@ -52,3 +54,161 @@ impl Axis {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
enum Case {
Horizontal {
overall_height: f32,
spacing: f32,
top_height: f32,
bottom_y: f32,
bottom_height: f32,
},
Vertical {
overall_width: f32,
spacing: f32,
left_width: f32,
right_x: f32,
right_width: f32,
},
}
#[test]
fn split() {
let cases = vec![
// Even height, even spacing
Case::Horizontal {
overall_height: 10.0,
spacing: 2.0,
top_height: 4.0,
bottom_y: 6.0,
bottom_height: 4.0,
},
// Odd height, even spacing
Case::Horizontal {
overall_height: 9.0,
spacing: 2.0,
top_height: 4.0,
bottom_y: 6.0,
bottom_height: 3.0,
},
// Even height, odd spacing
Case::Horizontal {
overall_height: 10.0,
spacing: 1.0,
top_height: 5.0,
bottom_y: 6.0,
bottom_height: 4.0,
},
// Odd height, odd spacing
Case::Horizontal {
overall_height: 9.0,
spacing: 1.0,
top_height: 4.0,
bottom_y: 5.0,
bottom_height: 4.0,
},
// Even width, even spacing
Case::Vertical {
overall_width: 10.0,
spacing: 2.0,
left_width: 4.0,
right_x: 6.0,
right_width: 4.0,
},
// Odd width, even spacing
Case::Vertical {
overall_width: 9.0,
spacing: 2.0,
left_width: 4.0,
right_x: 6.0,
right_width: 3.0,
},
// Even width, odd spacing
Case::Vertical {
overall_width: 10.0,
spacing: 1.0,
left_width: 5.0,
right_x: 6.0,
right_width: 4.0,
},
// Odd width, odd spacing
Case::Vertical {
overall_width: 9.0,
spacing: 1.0,
left_width: 4.0,
right_x: 5.0,
right_width: 4.0,
},
];
for case in cases {
match case {
Case::Horizontal {
overall_height,
spacing,
top_height,
bottom_y,
bottom_height,
} => {
let a = Axis::Horizontal;
let r = Rectangle {
x: 0.0,
y: 0.0,
width: 10.0,
height: overall_height,
};
let (top, bottom) = a.split(&r, 0.5, spacing);
assert_eq!(
top,
Rectangle {
height: top_height,
..r
}
);
assert_eq!(
bottom,
Rectangle {
y: bottom_y,
height: bottom_height,
..r
}
);
}
Case::Vertical {
overall_width,
spacing,
left_width,
right_x,
right_width,
} => {
let a = Axis::Vertical;
let r = Rectangle {
x: 0.0,
y: 0.0,
width: overall_width,
height: 10.0,
};
let (left, right) = a.split(&r, 0.5, spacing);
assert_eq!(
left,
Rectangle {
width: left_width,
..r
}
);
assert_eq!(
right,
Rectangle {
x: right_x,
width: right_width,
..r
}
);
}
}
}
}
}

View File

@ -56,7 +56,7 @@ impl Node {
let mut regions = HashMap::new();
self.compute_regions(
spacing / 2.0,
spacing,
&Rectangle {
x: 0.0,
y: 0.0,
@ -83,7 +83,7 @@ impl Node {
let mut splits = HashMap::new();
self.compute_splits(
spacing / 2.0,
spacing,
&Rectangle {
x: 0.0,
y: 0.0,
@ -185,7 +185,7 @@ impl Node {
fn compute_regions(
&self,
halved_spacing: f32,
spacing: f32,
current: &Rectangle,
regions: &mut HashMap<Pane, Rectangle>,
) {
@ -193,11 +193,10 @@ impl Node {
Node::Split {
axis, ratio, a, b, ..
} => {
let (region_a, region_b) =
axis.split(current, *ratio, halved_spacing);
let (region_a, region_b) = axis.split(current, *ratio, spacing);
a.compute_regions(halved_spacing, &region_a, regions);
b.compute_regions(halved_spacing, &region_b, regions);
a.compute_regions(spacing, &region_a, regions);
b.compute_regions(spacing, &region_b, regions);
}
Node::Pane(pane) => {
let _ = regions.insert(*pane, *current);
@ -207,7 +206,7 @@ impl Node {
fn compute_splits(
&self,
halved_spacing: f32,
spacing: f32,
current: &Rectangle,
splits: &mut HashMap<Split, (Axis, Rectangle, f32)>,
) {
@ -219,13 +218,12 @@ impl Node {
b,
id,
} => {
let (region_a, region_b) =
axis.split(current, *ratio, halved_spacing);
let (region_a, region_b) = axis.split(current, *ratio, spacing);
let _ = splits.insert(*id, (*axis, *current, *ratio));
a.compute_splits(halved_spacing, &region_a, splits);
b.compute_splits(halved_spacing, &region_b, splits);
a.compute_splits(spacing, &region_a, splits);
b.compute_splits(spacing, &region_b, splits);
}
Node::Pane(_) => {}
}

View File

@ -1,6 +1,4 @@
//! Build window-based GUI applications.
mod backend;
mod event;
pub use backend::Backend;
pub use event::Event;

View File

@ -188,20 +188,21 @@ pub trait Application: Sized {
{
#[cfg(not(target_arch = "wasm32"))]
{
let wgpu_settings = iced_wgpu::Settings {
let renderer_settings = crate::renderer::Settings {
default_font: settings.default_font,
antialiasing: if settings.antialiasing {
Some(iced_wgpu::settings::Antialiasing::MSAAx4)
Some(crate::renderer::settings::Antialiasing::MSAAx4)
} else {
None
},
..iced_wgpu::Settings::default()
..crate::renderer::Settings::default()
};
<Instance<Self> as iced_winit::Application>::run(
settings.into(),
wgpu_settings,
);
crate::runtime::application::run::<
Instance<Self>,
Self::Executor,
crate::renderer::window::Compositor,
>(settings.into(), renderer_settings);
}
#[cfg(target_arch = "wasm32")]
@ -212,15 +213,29 @@ pub trait Application: Sized {
struct Instance<A: Application>(A);
#[cfg(not(target_arch = "wasm32"))]
impl<A> iced_winit::Application for Instance<A>
impl<A> iced_winit::Program for Instance<A>
where
A: Application,
{
type Backend = iced_wgpu::window::Backend;
type Executor = A::Executor;
type Flags = A::Flags;
type Renderer = crate::renderer::Renderer;
type Message = A::Message;
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
self.0.update(message)
}
fn view(&mut self) -> Element<'_, Self::Message> {
self.0.view()
}
}
#[cfg(not(target_arch = "wasm32"))]
impl<A> crate::runtime::Application for Instance<A>
where
A: Application,
{
type Flags = A::Flags;
fn new(flags: Self::Flags) -> (Self, Command<A::Message>) {
let (app, command) = A::new(flags);
@ -238,17 +253,9 @@ where
}
}
fn update(&mut self, message: Self::Message) -> Command<Self::Message> {
self.0.update(message)
}
fn subscription(&self) -> Subscription<Self::Message> {
self.0.subscription()
}
fn view(&mut self) -> Element<'_, Self::Message> {
self.0.view()
}
}
#[cfg(target_arch = "wasm32")]

View File

@ -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>;
crate::runtime::Element<'a, Message, crate::renderer::Renderer>;
#[cfg(target_arch = "wasm32")]
pub use iced_web::Element;

Some files were not shown because too many files have changed in this diff Show More