Draft basic text rendering using wgpu_glyph
This commit is contained in:
parent
a90f7fcb98
commit
0c3f78713d
4 changed files with 221 additions and 49 deletions
|
|
@ -41,6 +41,15 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
cursor_position: Point,
|
||||||
|
) -> Renderer::Primitive {
|
||||||
|
self.widget.draw(renderer, layout, cursor_position)
|
||||||
|
}
|
||||||
|
|
||||||
/// Applies a transformation to the produced message of the [`Element`].
|
/// Applies a transformation to the produced message of the [`Element`].
|
||||||
///
|
///
|
||||||
/// This method is useful when you want to decouple different parts of your
|
/// This method is useful when you want to decouple different parts of your
|
||||||
|
|
|
||||||
34
src/lib.rs
34
src/lib.rs
|
|
@ -1,4 +1,4 @@
|
||||||
pub use iced_wgpu::Renderer;
|
pub use iced_wgpu::{Primitive, Renderer};
|
||||||
pub use iced_winit::{
|
pub use iced_winit::{
|
||||||
button, slider, text, winit, Align, Button, Checkbox, Color, Image,
|
button, slider, text, winit, Align, Button, Checkbox, Color, Image,
|
||||||
Justify, Length, Radio, Slider, Text,
|
Justify, Length, Radio, Slider, Text,
|
||||||
|
|
@ -33,13 +33,15 @@ pub trait UserInterface {
|
||||||
.expect("Open window");
|
.expect("Open window");
|
||||||
|
|
||||||
let size = window.inner_size().to_physical(window.hidpi_factor());;
|
let size = window.inner_size().to_physical(window.hidpi_factor());;
|
||||||
|
let (width, height) = (size.width as u16, size.height as u16);
|
||||||
|
|
||||||
let mut renderer =
|
let mut renderer = Renderer::new(&window);
|
||||||
Renderer::new(&window, size.width as u32, size.height as u32);
|
let mut target = renderer.target(width, height);
|
||||||
|
|
||||||
let mut cache = Some(iced_winit::Cache::default());
|
let mut cache = Some(iced_winit::Cache::default());
|
||||||
let mut events = Vec::new();
|
let mut events = Vec::new();
|
||||||
let mut redraws = 0;
|
let mut redraws = 0;
|
||||||
|
let mut primitive = Primitive::None;
|
||||||
|
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
|
|
||||||
|
|
@ -51,7 +53,7 @@ pub trait UserInterface {
|
||||||
// This will allow us to rebuild it only when a message is
|
// This will allow us to rebuild it only when a message is
|
||||||
// handled.
|
// handled.
|
||||||
let mut user_interface = iced_winit::UserInterface::build(
|
let mut user_interface = iced_winit::UserInterface::build(
|
||||||
self.view(),
|
document(&mut self, width, height),
|
||||||
cache.take().unwrap(),
|
cache.take().unwrap(),
|
||||||
&mut renderer,
|
&mut renderer,
|
||||||
);
|
);
|
||||||
|
|
@ -59,7 +61,7 @@ pub trait UserInterface {
|
||||||
let messages = user_interface.update(events.drain(..));
|
let messages = user_interface.update(events.drain(..));
|
||||||
|
|
||||||
if messages.is_empty() {
|
if messages.is_empty() {
|
||||||
let _ = user_interface.draw(&mut renderer);
|
primitive = user_interface.draw(&mut renderer);
|
||||||
|
|
||||||
cache = Some(user_interface.into_cache());
|
cache = Some(user_interface.into_cache());
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -72,12 +74,12 @@ pub trait UserInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_interface = iced_winit::UserInterface::build(
|
let user_interface = iced_winit::UserInterface::build(
|
||||||
self.view(),
|
document(&mut self, width, height),
|
||||||
temp_cache,
|
temp_cache,
|
||||||
&mut renderer,
|
&mut renderer,
|
||||||
);
|
);
|
||||||
|
|
||||||
let _ = user_interface.draw(&mut renderer);
|
primitive = user_interface.draw(&mut renderer);
|
||||||
|
|
||||||
cache = Some(user_interface.into_cache());
|
cache = Some(user_interface.into_cache());
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +91,7 @@ pub trait UserInterface {
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
println!("Redrawing {}", redraws);
|
println!("Redrawing {}", redraws);
|
||||||
renderer.draw();
|
renderer.draw(&mut target, &primitive);
|
||||||
|
|
||||||
redraws += 1;
|
redraws += 1;
|
||||||
|
|
||||||
|
|
@ -108,3 +110,19 @@ pub trait UserInterface {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn document<UserInterface>(
|
||||||
|
user_interface: &mut UserInterface,
|
||||||
|
width: u16,
|
||||||
|
height: u16,
|
||||||
|
) -> Element<UserInterface::Message>
|
||||||
|
where
|
||||||
|
UserInterface: self::UserInterface,
|
||||||
|
UserInterface::Message: 'static,
|
||||||
|
{
|
||||||
|
Column::new()
|
||||||
|
.width(Length::Units(width))
|
||||||
|
.height(Length::Units(height))
|
||||||
|
.push(user_interface.view())
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,5 @@ vulkan = ["wgpu/vulkan"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced_native = { version = "0.1.0-alpha", path = "../native" }
|
iced_native = { version = "0.1.0-alpha", path = "../native" }
|
||||||
wgpu = "0.3"
|
wgpu = "0.3"
|
||||||
|
wgpu_glyph = "0.4"
|
||||||
raw-window-handle = "0.1"
|
raw-window-handle = "0.1"
|
||||||
|
|
|
||||||
226
wgpu/src/lib.rs
226
wgpu/src/lib.rs
|
|
@ -1,7 +1,7 @@
|
||||||
use iced_native::{
|
use iced_native::{
|
||||||
button, checkbox, column, image, radio, renderer::Debugger, row, slider,
|
button, checkbox, column, image, radio, renderer::Debugger, row, slider,
|
||||||
text, Button, Checkbox, Color, Column, Image, Layout, Node, Point, Radio,
|
text, Button, Checkbox, Color, Column, Image, Layout, Node, Point, Radio,
|
||||||
Row, Slider, Style, Text, Widget,
|
Rectangle, Row, Slider, Style, Text, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use raw_window_handle::HasRawWindowHandle;
|
use raw_window_handle::HasRawWindowHandle;
|
||||||
|
|
@ -10,28 +10,34 @@ use wgpu::{
|
||||||
Instance, Limits, PowerPreference, RequestAdapterOptions, Surface,
|
Instance, Limits, PowerPreference, RequestAdapterOptions, Surface,
|
||||||
SwapChain, SwapChainDescriptor, TextureFormat, TextureUsage,
|
SwapChain, SwapChainDescriptor, TextureFormat, TextureUsage,
|
||||||
};
|
};
|
||||||
|
use wgpu_glyph::{GlyphBrush, GlyphBrushBuilder, GlyphCruncher, Section};
|
||||||
|
|
||||||
|
use std::f32;
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
pub struct Renderer {
|
pub struct Renderer {
|
||||||
instance: Instance,
|
instance: Instance,
|
||||||
surface: Surface,
|
surface: Surface,
|
||||||
adapter: Adapter,
|
adapter: Adapter,
|
||||||
device: Device,
|
device: Device,
|
||||||
|
glyph_brush: Rc<RefCell<GlyphBrush<'static, ()>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Target {
|
||||||
|
width: u16,
|
||||||
|
height: u16,
|
||||||
swap_chain: SwapChain,
|
swap_chain: SwapChain,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer {
|
impl Renderer {
|
||||||
pub fn new<W: HasRawWindowHandle>(
|
pub fn new<W: HasRawWindowHandle>(window: &W) -> Self {
|
||||||
window: &W,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
) -> Self {
|
|
||||||
let instance = Instance::new();
|
let instance = Instance::new();
|
||||||
|
|
||||||
let adapter = instance.request_adapter(&RequestAdapterOptions {
|
let adapter = instance.request_adapter(&RequestAdapterOptions {
|
||||||
power_preference: PowerPreference::LowPower,
|
power_preference: PowerPreference::LowPower,
|
||||||
});
|
});
|
||||||
|
|
||||||
let device = adapter.request_device(&DeviceDescriptor {
|
let mut device = adapter.request_device(&DeviceDescriptor {
|
||||||
extensions: Extensions {
|
extensions: Extensions {
|
||||||
anisotropic_filtering: false,
|
anisotropic_filtering: false,
|
||||||
},
|
},
|
||||||
|
|
@ -40,28 +46,40 @@ impl Renderer {
|
||||||
|
|
||||||
let surface = instance.create_surface(window.raw_window_handle());
|
let surface = instance.create_surface(window.raw_window_handle());
|
||||||
|
|
||||||
let swap_chain = device.create_swap_chain(
|
let font: &[u8] =
|
||||||
&surface,
|
include_bytes!("../../examples/tour/resources/Roboto-Regular.ttf");
|
||||||
&SwapChainDescriptor {
|
|
||||||
usage: TextureUsage::OUTPUT_ATTACHMENT,
|
let glyph_brush = GlyphBrushBuilder::using_font_bytes(font)
|
||||||
format: TextureFormat::Bgra8UnormSrgb,
|
.build(&mut device, TextureFormat::Bgra8UnormSrgb);
|
||||||
width,
|
|
||||||
height,
|
|
||||||
present_mode: wgpu::PresentMode::Vsync,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
instance,
|
instance,
|
||||||
surface,
|
surface,
|
||||||
adapter,
|
adapter,
|
||||||
device,
|
device,
|
||||||
swap_chain,
|
glyph_brush: Rc::new(RefCell::new(glyph_brush)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self) {
|
pub fn target(&self, width: u16, height: u16) -> Target {
|
||||||
let frame = self.swap_chain.get_next_texture();
|
Target {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
swap_chain: self.device.create_swap_chain(
|
||||||
|
&self.surface,
|
||||||
|
&SwapChainDescriptor {
|
||||||
|
usage: TextureUsage::OUTPUT_ATTACHMENT,
|
||||||
|
format: TextureFormat::Bgra8UnormSrgb,
|
||||||
|
width: u32::from(width),
|
||||||
|
height: u32::from(height),
|
||||||
|
present_mode: wgpu::PresentMode::Vsync,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&mut self, target: &mut Target, primitive: &Primitive) {
|
||||||
|
let frame = target.swap_chain.get_next_texture();
|
||||||
|
|
||||||
let mut encoder = self
|
let mut encoder = self
|
||||||
.device
|
.device
|
||||||
|
|
@ -83,39 +101,169 @@ impl Renderer {
|
||||||
depth_stencil_attachment: None,
|
depth_stencil_attachment: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.draw_primitive(primitive);
|
||||||
|
|
||||||
|
self.glyph_brush
|
||||||
|
.borrow_mut()
|
||||||
|
.draw_queued(
|
||||||
|
&mut self.device,
|
||||||
|
&mut encoder,
|
||||||
|
&frame.view,
|
||||||
|
u32::from(target.width),
|
||||||
|
u32::from(target.height),
|
||||||
|
)
|
||||||
|
.expect("Draw text");
|
||||||
|
|
||||||
self.device.get_queue().submit(&[encoder.finish()]);
|
self.device.get_queue().submit(&[encoder.finish()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_primitive(&mut self, primitive: &Primitive) {
|
||||||
|
match primitive {
|
||||||
|
Primitive::None => {}
|
||||||
|
Primitive::Group { primitives } => {
|
||||||
|
for primitive in primitives {
|
||||||
|
self.draw_primitive(primitive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Primitive::Text {
|
||||||
|
content,
|
||||||
|
bounds,
|
||||||
|
size,
|
||||||
|
} => self.glyph_brush.borrow_mut().queue(Section {
|
||||||
|
text: &content,
|
||||||
|
screen_position: (bounds.x, bounds.y),
|
||||||
|
bounds: (bounds.width, bounds.height),
|
||||||
|
scale: wgpu_glyph::Scale { x: *size, y: *size },
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Primitive {
|
||||||
|
None,
|
||||||
|
Group {
|
||||||
|
primitives: Vec<Primitive>,
|
||||||
|
},
|
||||||
|
Text {
|
||||||
|
content: String,
|
||||||
|
bounds: Rectangle,
|
||||||
|
size: f32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl iced_native::Renderer for Renderer {
|
||||||
|
type Primitive = Primitive;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl column::Renderer for Renderer {
|
impl column::Renderer for Renderer {
|
||||||
fn draw<Message>(
|
fn draw<Message>(
|
||||||
&mut self,
|
&mut self,
|
||||||
_column: &Column<'_, Message, Self>,
|
column: &Column<'_, Message, Self>,
|
||||||
_layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> Self::Primitive {
|
) -> Self::Primitive {
|
||||||
()
|
Primitive::Group {
|
||||||
|
primitives: column
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.zip(layout.children())
|
||||||
|
.map(|(child, layout)| {
|
||||||
|
child.draw(self, layout, cursor_position)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl row::Renderer for Renderer {
|
impl row::Renderer for Renderer {
|
||||||
fn draw<Message>(
|
fn draw<Message>(
|
||||||
&mut self,
|
&mut self,
|
||||||
_column: &Row<'_, Message, Self>,
|
row: &Row<'_, Message, Self>,
|
||||||
_layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> Self::Primitive {
|
) -> Self::Primitive {
|
||||||
()
|
Primitive::Group {
|
||||||
|
primitives: row
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.zip(layout.children())
|
||||||
|
.map(|(child, layout)| {
|
||||||
|
child.draw(self, layout, cursor_position)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl text::Renderer for Renderer {
|
impl text::Renderer for Renderer {
|
||||||
fn node(&self, _text: &Text) -> Node {
|
fn node(&self, text: &Text) -> Node {
|
||||||
Node::new(Style::default())
|
let glyph_brush = self.glyph_brush.clone();
|
||||||
|
let content = text.content.clone();
|
||||||
|
|
||||||
|
// TODO: Investigate why stretch tries to measure this MANY times
|
||||||
|
// with every ancestor's bounds.
|
||||||
|
// Bug? Using the library wrong? I should probably open an issue on
|
||||||
|
// the stretch repository.
|
||||||
|
// I noticed that the first measure is the one that matters in
|
||||||
|
// practice. Here, we use a RefCell to store the cached measurement.
|
||||||
|
let measure = RefCell::new(None);
|
||||||
|
let size = text.size.map(f32::from).unwrap_or(20.0);
|
||||||
|
|
||||||
|
let style = Style::default().width(text.width);
|
||||||
|
|
||||||
|
iced_native::Node::with_measure(style, move |bounds| {
|
||||||
|
let mut measure = measure.borrow_mut();
|
||||||
|
|
||||||
|
if measure.is_none() {
|
||||||
|
let bounds = (
|
||||||
|
match bounds.width {
|
||||||
|
iced_native::Number::Undefined => f32::INFINITY,
|
||||||
|
iced_native::Number::Defined(w) => w,
|
||||||
|
},
|
||||||
|
match bounds.height {
|
||||||
|
iced_native::Number::Undefined => f32::INFINITY,
|
||||||
|
iced_native::Number::Defined(h) => h,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let text = Section {
|
||||||
|
text: &content,
|
||||||
|
scale: wgpu_glyph::Scale { x: size, y: size },
|
||||||
|
bounds,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (width, height) = if let Some(bounds) =
|
||||||
|
glyph_brush.borrow_mut().glyph_bounds(&text)
|
||||||
|
{
|
||||||
|
(bounds.width(), bounds.height())
|
||||||
|
} else {
|
||||||
|
(0.0, 0.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
let size = iced_native::Size { width, height };
|
||||||
|
|
||||||
|
// If the text has no width boundary we avoid caching as the
|
||||||
|
// layout engine may just be measuring text in a row.
|
||||||
|
if bounds.0 == f32::INFINITY {
|
||||||
|
return size;
|
||||||
|
} else {
|
||||||
|
*measure = Some(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
measure.unwrap()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&mut self, _text: &Text, _layout: Layout<'_>) -> Self::Primitive {
|
fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Primitive {
|
||||||
()
|
Primitive::Text {
|
||||||
|
content: text.content.clone(),
|
||||||
|
size: f32::from(text.size.unwrap_or(20)),
|
||||||
|
bounds: layout.bounds(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,7 +278,7 @@ impl checkbox::Renderer for Renderer {
|
||||||
_layout: Layout<'_>,
|
_layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
) -> Self::Primitive {
|
) -> Self::Primitive {
|
||||||
()
|
Primitive::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,7 +293,7 @@ impl radio::Renderer for Renderer {
|
||||||
_layout: Layout<'_>,
|
_layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
) -> Self::Primitive {
|
) -> Self::Primitive {
|
||||||
()
|
Primitive::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,7 +308,7 @@ impl slider::Renderer for Renderer {
|
||||||
_layout: Layout<'_>,
|
_layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
) -> Self::Primitive {
|
) -> Self::Primitive {
|
||||||
()
|
Primitive::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,7 +322,7 @@ impl image::Renderer<&str> for Renderer {
|
||||||
_checkbox: &Image<&str>,
|
_checkbox: &Image<&str>,
|
||||||
_layout: Layout<'_>,
|
_layout: Layout<'_>,
|
||||||
) -> Self::Primitive {
|
) -> Self::Primitive {
|
||||||
()
|
Primitive::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,14 +337,10 @@ impl button::Renderer for Renderer {
|
||||||
_layout: Layout<'_>,
|
_layout: Layout<'_>,
|
||||||
_cursor_position: Point,
|
_cursor_position: Point,
|
||||||
) -> Self::Primitive {
|
) -> Self::Primitive {
|
||||||
()
|
Primitive::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl iced_native::Renderer for Renderer {
|
|
||||||
type Primitive = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debugger for Renderer {
|
impl Debugger for Renderer {
|
||||||
fn explain<Message>(
|
fn explain<Message>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue