Merge pull request #1697 from iced-rs/text-glyphon
Text shaping, font fallback, and `iced_wgpu` overhaul
This commit is contained in:
commit
368cadd25a
94 changed files with 1647 additions and 5395 deletions
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
|
@ -38,4 +38,4 @@ jobs:
|
|||
- name: Check compilation of `todos` example
|
||||
run: cargo build --package todos --target wasm32-unknown-unknown
|
||||
- name: Check compilation of `integration_wgpu` example
|
||||
run: cargo build --package integration_wgpu --target wasm32-unknown-unknown
|
||||
run: cargo build --package integration --target wasm32-unknown-unknown
|
||||
|
|
|
|||
23
Cargo.toml
23
Cargo.toml
|
|
@ -12,21 +12,14 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
|||
categories = ["gui"]
|
||||
|
||||
[features]
|
||||
default = ["wgpu"]
|
||||
# Enables the `Image` widget
|
||||
image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"]
|
||||
image = ["iced_wgpu/image", "image_rs"]
|
||||
# Enables the `Svg` widget
|
||||
svg = ["iced_wgpu?/svg", "iced_glow?/svg"]
|
||||
svg = ["iced_wgpu/svg"]
|
||||
# Enables the `Canvas` widget
|
||||
canvas = ["iced_graphics/canvas"]
|
||||
# Enables the `QRCode` widget
|
||||
qr_code = ["iced_graphics/qr_code"]
|
||||
# Enables the `iced_wgpu` renderer
|
||||
wgpu = ["iced_wgpu"]
|
||||
# Enables using system fonts
|
||||
default_system_font = ["iced_wgpu?/default_system_font", "iced_glow?/default_system_font"]
|
||||
# Enables the `iced_glow` renderer. Overrides `iced_wgpu`
|
||||
glow = ["iced_glow", "iced_glutin"]
|
||||
# Enables a debug view in native platforms (press F12)
|
||||
debug = ["iced_winit/debug"]
|
||||
# Enables `tokio` as the `executor::Default` on native platforms
|
||||
|
|
@ -42,9 +35,7 @@ system = ["iced_winit/system"]
|
|||
# Enables chrome traces
|
||||
chrome-trace = [
|
||||
"iced_winit/chrome-trace",
|
||||
"iced_glutin?/trace",
|
||||
"iced_wgpu?/tracing",
|
||||
"iced_glow?/tracing",
|
||||
"iced_wgpu/tracing",
|
||||
]
|
||||
|
||||
[badges]
|
||||
|
|
@ -55,8 +46,6 @@ members = [
|
|||
"core",
|
||||
"futures",
|
||||
"graphics",
|
||||
"glow",
|
||||
"glutin",
|
||||
"lazy",
|
||||
"native",
|
||||
"style",
|
||||
|
|
@ -71,8 +60,6 @@ iced_futures = { version = "0.6", path = "futures" }
|
|||
iced_native = { version = "0.9", path = "native" }
|
||||
iced_graphics = { version = "0.7", path = "graphics" }
|
||||
iced_winit = { version = "0.8", path = "winit", features = ["application"] }
|
||||
iced_glutin = { version = "0.7", path = "glutin", optional = true }
|
||||
iced_glow = { version = "0.7", path = "glow", optional = true }
|
||||
thiserror = "1.0"
|
||||
|
||||
[dependencies.image_rs]
|
||||
|
|
@ -81,10 +68,10 @@ package = "image"
|
|||
optional = true
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
iced_wgpu = { version = "0.9", path = "wgpu", optional = true }
|
||||
iced_wgpu = { version = "0.9", path = "wgpu" }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
iced_wgpu = { version = "0.9", path = "wgpu", features = ["webgl"], optional = true }
|
||||
iced_wgpu = { version = "0.9", path = "wgpu", features = ["webgl"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
|
|
|||
|
|
@ -1,24 +1,45 @@
|
|||
//! Load and use fonts.
|
||||
use std::hash::Hash;
|
||||
|
||||
/// A font.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Font {
|
||||
/// The default font.
|
||||
///
|
||||
/// This is normally a font configured in a renderer or loaded from the
|
||||
/// system.
|
||||
Default,
|
||||
/// The name of a font family of choice.
|
||||
Name(&'static str),
|
||||
|
||||
/// An external font.
|
||||
External {
|
||||
/// The name of the external font
|
||||
name: &'static str,
|
||||
/// Serif fonts represent the formal text style for a script.
|
||||
Serif,
|
||||
|
||||
/// The bytes of the external font
|
||||
bytes: &'static [u8],
|
||||
},
|
||||
/// Glyphs in sans-serif fonts, as the term is used in CSS, are generally low
|
||||
/// contrast and have stroke endings that are plain — without any flaring,
|
||||
/// cross stroke, or other ornamentation.
|
||||
SansSerif,
|
||||
|
||||
/// Glyphs in cursive fonts generally use a more informal script style, and
|
||||
/// the result looks more like handwritten pen or brush writing than printed
|
||||
/// letterwork.
|
||||
Cursive,
|
||||
|
||||
/// Fantasy fonts are primarily decorative or expressive fonts that contain
|
||||
/// decorative or expressive representations of characters.
|
||||
Fantasy,
|
||||
|
||||
/// The sole criterion of a monospace font is that all glyphs have the same
|
||||
/// fixed width.
|
||||
Monospace,
|
||||
}
|
||||
|
||||
impl Default for Font {
|
||||
fn default() -> Font {
|
||||
Font::Default
|
||||
}
|
||||
/// The weight of some text.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Weight {
|
||||
Thin,
|
||||
ExtraLight,
|
||||
Light,
|
||||
Normal,
|
||||
Medium,
|
||||
Semibold,
|
||||
Bold,
|
||||
ExtraBold,
|
||||
Black,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#![forbid(unsafe_code, rust_2018_idioms)]
|
||||
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
|
||||
pub mod alignment;
|
||||
pub mod font;
|
||||
pub mod keyboard;
|
||||
pub mod mouse;
|
||||
pub mod time;
|
||||
|
|
@ -32,7 +33,6 @@ pub mod time;
|
|||
mod background;
|
||||
mod color;
|
||||
mod content_fit;
|
||||
mod font;
|
||||
mod length;
|
||||
mod padding;
|
||||
mod pixels;
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,10 +1,9 @@
|
|||
use iced::executor;
|
||||
use iced::font::{self, Font};
|
||||
use iced::widget::{checkbox, column, container};
|
||||
use iced::{Element, Font, Length, Sandbox, Settings};
|
||||
use iced::{Application, Command, Element, Length, Settings, Theme};
|
||||
|
||||
const ICON_FONT: Font = Font::External {
|
||||
name: "Icons",
|
||||
bytes: include_bytes!("../fonts/icons.ttf"),
|
||||
};
|
||||
const ICON_FONT: Font = Font::Name("icons");
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
Example::run(Settings::default())
|
||||
|
|
@ -20,24 +19,35 @@ struct Example {
|
|||
enum Message {
|
||||
DefaultChecked(bool),
|
||||
CustomChecked(bool),
|
||||
FontLoaded(Result<(), font::Error>),
|
||||
}
|
||||
|
||||
impl Sandbox for Example {
|
||||
impl Application for Example {
|
||||
type Message = Message;
|
||||
type Flags = ();
|
||||
type Executor = executor::Default;
|
||||
type Theme = Theme;
|
||||
|
||||
fn new() -> Self {
|
||||
Default::default()
|
||||
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
|
||||
(
|
||||
Self::default(),
|
||||
font::load(include_bytes!("../fonts/icons.ttf").as_ref())
|
||||
.map(Message::FontLoaded),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
String::from("Checkbox - Iced")
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) {
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::DefaultChecked(value) => self.default_checkbox = value,
|
||||
Message::CustomChecked(value) => self.custom_checkbox = value,
|
||||
Message::FontLoaded(_) => (),
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "integration_wgpu"
|
||||
name = "integration"
|
||||
version = "0.1.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
|
@ -8,8 +8,8 @@
|
|||
<h1>integration_wgpu</h1>
|
||||
<canvas id="iced_canvas"></canvas>
|
||||
<script type="module">
|
||||
import init from "./integration_wgpu.js";
|
||||
init('./integration_wgpu_bg.wasm');
|
||||
import init from "./integration.js";
|
||||
init('./integration_bg.wasm');
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
|
|
@ -125,17 +125,18 @@ pub fn main() {
|
|||
|
||||
let mut resized = false;
|
||||
|
||||
// Initialize staging belt
|
||||
let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024);
|
||||
|
||||
// Initialize scene and GUI controls
|
||||
let scene = Scene::new(&device, format);
|
||||
let controls = Controls::new();
|
||||
|
||||
// Initialize iced
|
||||
let mut debug = Debug::new();
|
||||
let mut renderer =
|
||||
Renderer::new(Backend::new(&device, Settings::default(), format));
|
||||
let mut renderer = Renderer::new(Backend::new(
|
||||
&device,
|
||||
&queue,
|
||||
Settings::default(),
|
||||
format,
|
||||
));
|
||||
|
||||
let mut state = program::State::new(
|
||||
controls,
|
||||
|
|
@ -247,8 +248,9 @@ pub fn main() {
|
|||
renderer.with_primitives(|backend, primitive| {
|
||||
backend.present(
|
||||
&device,
|
||||
&mut staging_belt,
|
||||
&queue,
|
||||
&mut encoder,
|
||||
None,
|
||||
&view,
|
||||
primitive,
|
||||
&viewport,
|
||||
|
|
@ -257,7 +259,6 @@ pub fn main() {
|
|||
});
|
||||
|
||||
// Then we submit the work
|
||||
staging_belt.finish();
|
||||
queue.submit(Some(encoder.finish()));
|
||||
frame.present();
|
||||
|
||||
|
|
@ -267,10 +268,6 @@ pub fn main() {
|
|||
state.mouse_interaction(),
|
||||
),
|
||||
);
|
||||
|
||||
// And recall staging buffers
|
||||
staging_belt.recall();
|
||||
|
||||
}
|
||||
Err(error) => match error {
|
||||
wgpu::SurfaceError::OutOfMemory => {
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
[package]
|
||||
name = "integration_opengl"
|
||||
version = "0.1.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
iced_glutin = { path = "../../glutin" }
|
||||
iced_glow = { path = "../../glow" }
|
||||
iced_winit = { path = "../../winit" }
|
||||
env_logger = "0.8"
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
## OpenGL integration
|
||||
|
||||
A demonstration of how to integrate Iced in an existing graphical OpenGL application.
|
||||
|
||||
The __[`main`]__ file contains all the code of the example.
|
||||
|
||||
<div align="center">
|
||||
<a href="https://imgbox.com/9P9ETcod" target="_blank"><img src="https://images2.imgbox.com/2a/51/9P9ETcod_o.gif" alt="image host"/></a>
|
||||
</div>
|
||||
|
||||
You can run it with `cargo run`:
|
||||
```
|
||||
cargo run --package integration_opengl
|
||||
```
|
||||
|
||||
[`main`]: src/main.rs
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
use iced_glow::Renderer;
|
||||
use iced_glutin::widget::Slider;
|
||||
use iced_glutin::widget::{Column, Row, Text};
|
||||
use iced_glutin::{Alignment, Color, Command, Element, Length, Program};
|
||||
|
||||
pub struct Controls {
|
||||
background_color: Color,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
BackgroundColorChanged(Color),
|
||||
}
|
||||
|
||||
impl Controls {
|
||||
pub fn new() -> Controls {
|
||||
Controls {
|
||||
background_color: Color::BLACK,
|
||||
}
|
||||
}
|
||||
|
||||
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) => {
|
||||
self.background_color = color;
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message, Renderer> {
|
||||
let background_color = self.background_color;
|
||||
|
||||
let sliders = Row::new()
|
||||
.width(500)
|
||||
.spacing(20)
|
||||
.push(
|
||||
Slider::new(0.0..=1.0, background_color.r, move |r| {
|
||||
Message::BackgroundColorChanged(Color {
|
||||
r,
|
||||
..background_color
|
||||
})
|
||||
})
|
||||
.step(0.01),
|
||||
)
|
||||
.push(
|
||||
Slider::new(0.0..=1.0, background_color.g, move |g| {
|
||||
Message::BackgroundColorChanged(Color {
|
||||
g,
|
||||
..background_color
|
||||
})
|
||||
})
|
||||
.step(0.01),
|
||||
)
|
||||
.push(
|
||||
Slider::new(0.0..=1.0, background_color.b, move |b| {
|
||||
Message::BackgroundColorChanged(Color {
|
||||
b,
|
||||
..background_color
|
||||
})
|
||||
})
|
||||
.step(0.01),
|
||||
);
|
||||
|
||||
Row::new()
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.align_items(Alignment::End)
|
||||
.push(
|
||||
Column::new()
|
||||
.width(Length::Fill)
|
||||
.align_items(Alignment::End)
|
||||
.push(
|
||||
Column::new()
|
||||
.padding(10)
|
||||
.spacing(10)
|
||||
.push(
|
||||
Text::new("Background color")
|
||||
.style(Color::WHITE),
|
||||
)
|
||||
.push(sliders)
|
||||
.push(
|
||||
Text::new(format!("{background_color:?}"))
|
||||
.size(14)
|
||||
.style(Color::WHITE),
|
||||
),
|
||||
),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,187 +0,0 @@
|
|||
mod controls;
|
||||
mod scene;
|
||||
|
||||
use controls::Controls;
|
||||
use scene::Scene;
|
||||
|
||||
use glow::*;
|
||||
use glutin::dpi::PhysicalPosition;
|
||||
use glutin::event::{Event, ModifiersState, WindowEvent};
|
||||
use glutin::event_loop::ControlFlow;
|
||||
use iced_glow::glow;
|
||||
use iced_glow::{Backend, Renderer, Settings, Viewport};
|
||||
use iced_glutin::conversion;
|
||||
use iced_glutin::glutin;
|
||||
use iced_glutin::renderer;
|
||||
use iced_glutin::{program, Clipboard, Color, Debug, Size};
|
||||
|
||||
pub fn main() {
|
||||
env_logger::init();
|
||||
let (gl, event_loop, windowed_context, shader_version) = {
|
||||
let el = glutin::event_loop::EventLoop::new();
|
||||
|
||||
let wb = glutin::window::WindowBuilder::new()
|
||||
.with_title("OpenGL integration example")
|
||||
.with_inner_size(glutin::dpi::LogicalSize::new(1024.0, 768.0));
|
||||
|
||||
let windowed_context = glutin::ContextBuilder::new()
|
||||
.with_vsync(true)
|
||||
.build_windowed(wb, &el)
|
||||
.unwrap();
|
||||
|
||||
unsafe {
|
||||
let windowed_context = windowed_context.make_current().unwrap();
|
||||
|
||||
let gl = glow::Context::from_loader_function(|s| {
|
||||
windowed_context.get_proc_address(s) as *const _
|
||||
});
|
||||
|
||||
// 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);
|
||||
|
||||
(gl, el, windowed_context, "#version 410")
|
||||
}
|
||||
};
|
||||
|
||||
let physical_size = windowed_context.window().inner_size();
|
||||
let mut viewport = Viewport::with_physical_size(
|
||||
Size::new(physical_size.width, physical_size.height),
|
||||
windowed_context.window().scale_factor(),
|
||||
);
|
||||
|
||||
let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
|
||||
let mut modifiers = ModifiersState::default();
|
||||
let mut clipboard = Clipboard::connect(windowed_context.window());
|
||||
|
||||
let mut renderer = Renderer::new(Backend::new(&gl, Settings::default()));
|
||||
|
||||
let mut debug = Debug::new();
|
||||
|
||||
let controls = Controls::new();
|
||||
let mut state = program::State::new(
|
||||
controls,
|
||||
viewport.logical_size(),
|
||||
&mut renderer,
|
||||
&mut debug,
|
||||
);
|
||||
let mut resized = false;
|
||||
|
||||
let scene = Scene::new(&gl, shader_version);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => {
|
||||
match event {
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
cursor_position = position;
|
||||
}
|
||||
WindowEvent::ModifiersChanged(new_modifiers) => {
|
||||
modifiers = new_modifiers;
|
||||
}
|
||||
WindowEvent::Resized(physical_size) => {
|
||||
viewport = Viewport::with_physical_size(
|
||||
Size::new(
|
||||
physical_size.width,
|
||||
physical_size.height,
|
||||
),
|
||||
windowed_context.window().scale_factor(),
|
||||
);
|
||||
|
||||
resized = true;
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
scene.cleanup(&gl);
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Map window event to iced event
|
||||
if let Some(event) = iced_winit::conversion::window_event(
|
||||
&event,
|
||||
windowed_context.window().scale_factor(),
|
||||
modifiers,
|
||||
) {
|
||||
state.queue_event(event);
|
||||
}
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
// If there are events pending
|
||||
if !state.is_queue_empty() {
|
||||
// We update iced
|
||||
let _ = state.update(
|
||||
viewport.logical_size(),
|
||||
conversion::cursor_position(
|
||||
cursor_position,
|
||||
viewport.scale_factor(),
|
||||
),
|
||||
&mut renderer,
|
||||
&iced_glow::Theme::Dark,
|
||||
&renderer::Style {
|
||||
text_color: Color::WHITE,
|
||||
},
|
||||
&mut clipboard,
|
||||
&mut debug,
|
||||
);
|
||||
|
||||
// and request a redraw
|
||||
windowed_context.window().request_redraw();
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
if resized {
|
||||
let size = windowed_context.window().inner_size();
|
||||
|
||||
unsafe {
|
||||
gl.viewport(
|
||||
0,
|
||||
0,
|
||||
size.width as i32,
|
||||
size.height as i32,
|
||||
);
|
||||
}
|
||||
|
||||
resized = false;
|
||||
}
|
||||
|
||||
let program = state.program();
|
||||
{
|
||||
// We clear the frame
|
||||
scene.clear(&gl, program.background_color());
|
||||
|
||||
// Draw the scene
|
||||
scene.draw(&gl);
|
||||
}
|
||||
|
||||
// And then iced on top
|
||||
renderer.with_primitives(|backend, primitive| {
|
||||
backend.present(
|
||||
&gl,
|
||||
primitive,
|
||||
&viewport,
|
||||
&debug.overlay(),
|
||||
);
|
||||
});
|
||||
|
||||
// Update the mouse cursor
|
||||
windowed_context.window().set_cursor_icon(
|
||||
iced_winit::conversion::mouse_interaction(
|
||||
state.mouse_interaction(),
|
||||
),
|
||||
);
|
||||
|
||||
windowed_context.swap_buffers().unwrap();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
use glow::*;
|
||||
use iced_glow::glow;
|
||||
use iced_glow::Color;
|
||||
|
||||
pub struct Scene {
|
||||
program: glow::Program,
|
||||
vertex_array: glow::VertexArray,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
pub fn new(gl: &glow::Context, shader_version: &str) -> Self {
|
||||
unsafe {
|
||||
let vertex_array = gl
|
||||
.create_vertex_array()
|
||||
.expect("Cannot create vertex array");
|
||||
gl.bind_vertex_array(Some(vertex_array));
|
||||
|
||||
let program = gl.create_program().expect("Cannot create program");
|
||||
|
||||
let (vertex_shader_source, fragment_shader_source) = (
|
||||
r#"const vec2 verts[3] = vec2[3](
|
||||
vec2(0.5f, 1.0f),
|
||||
vec2(0.0f, 0.0f),
|
||||
vec2(1.0f, 0.0f)
|
||||
);
|
||||
out vec2 vert;
|
||||
void main() {
|
||||
vert = verts[gl_VertexID];
|
||||
gl_Position = vec4(vert - 0.5, 0.0, 1.0);
|
||||
}"#,
|
||||
r#"precision highp float;
|
||||
in vec2 vert;
|
||||
out vec4 color;
|
||||
void main() {
|
||||
color = vec4(vert, 0.5, 1.0);
|
||||
}"#,
|
||||
);
|
||||
|
||||
let shader_sources = [
|
||||
(glow::VERTEX_SHADER, vertex_shader_source),
|
||||
(glow::FRAGMENT_SHADER, fragment_shader_source),
|
||||
];
|
||||
|
||||
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,
|
||||
&format!("{shader_version}\n{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);
|
||||
}
|
||||
|
||||
gl.use_program(Some(program));
|
||||
Self {
|
||||
program,
|
||||
vertex_array,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&self, gl: &glow::Context, background_color: Color) {
|
||||
let [r, g, b, a] = background_color.into_linear();
|
||||
unsafe {
|
||||
gl.clear_color(r, g, b, a);
|
||||
gl.clear(glow::COLOR_BUFFER_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, gl: &glow::Context) {
|
||||
unsafe {
|
||||
gl.bind_vertex_array(Some(self.vertex_array));
|
||||
gl.use_program(Some(self.program));
|
||||
gl.draw_arrays(glow::TRIANGLES, 0, 3);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cleanup(&self, gl: &glow::Context) {
|
||||
unsafe {
|
||||
gl.delete_program(self.program);
|
||||
gl.delete_vertex_array(self.vertex_array);
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
|
@ -1,5 +1,6 @@
|
|||
use iced::alignment::{self, Alignment};
|
||||
use iced::event::{self, Event};
|
||||
use iced::font::{self, Font};
|
||||
use iced::keyboard;
|
||||
use iced::subscription;
|
||||
use iced::theme::{self, Theme};
|
||||
|
|
@ -9,7 +10,7 @@ use iced::widget::{
|
|||
};
|
||||
use iced::window;
|
||||
use iced::{Application, Element};
|
||||
use iced::{Color, Command, Font, Length, Settings, Subscription};
|
||||
use iced::{Color, Command, Length, Settings, Subscription};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -44,6 +45,7 @@ struct State {
|
|||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
Loaded(Result<SavedState, LoadError>),
|
||||
FontLoaded(Result<(), font::Error>),
|
||||
Saved(Result<(), SaveError>),
|
||||
InputChanged(String),
|
||||
CreateTask,
|
||||
|
|
@ -61,7 +63,11 @@ impl Application for Todos {
|
|||
fn new(_flags: ()) -> (Todos, Command<Message>) {
|
||||
(
|
||||
Todos::Loading,
|
||||
Command::perform(SavedState::load(), Message::Loaded),
|
||||
Command::batch(vec![
|
||||
font::load(include_bytes!("../fonts/icons.ttf").as_slice())
|
||||
.map(Message::FontLoaded),
|
||||
Command::perform(SavedState::load(), Message::Loaded),
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -384,7 +390,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
|
|||
let tasks_left = tasks.iter().filter(|task| !task.completed).count();
|
||||
|
||||
let filter_button = |label, filter, current_filter| {
|
||||
let label = text(label).size(16);
|
||||
let label = text(label);
|
||||
|
||||
let button = button(label).style(if filter == current_filter {
|
||||
theme::Button::Primary
|
||||
|
|
@ -401,8 +407,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
|
|||
tasks_left,
|
||||
if tasks_left == 1 { "task" } else { "tasks" }
|
||||
))
|
||||
.width(Length::Fill)
|
||||
.size(16),
|
||||
.width(Length::Fill),
|
||||
row![
|
||||
filter_button("All", Filter::All, current_filter),
|
||||
filter_button("Active", Filter::Active, current_filter),
|
||||
|
|
@ -466,10 +471,7 @@ fn empty_message(message: &str) -> Element<'_, Message> {
|
|||
}
|
||||
|
||||
// Fonts
|
||||
const ICONS: Font = Font::External {
|
||||
name: "Icons",
|
||||
bytes: include_bytes!("../../todos/fonts/icons.ttf"),
|
||||
};
|
||||
const ICONS: Font = Font::Name("Iced-Todos-Icons");
|
||||
|
||||
fn icon(unicode: char) -> Text<'static> {
|
||||
text(unicode.to_string())
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
[package]
|
||||
name = "iced_glow"
|
||||
version = "0.7.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A glow renderer for iced"
|
||||
license = "MIT AND OFL-1.1"
|
||||
repository = "https://github.com/iced-rs/iced"
|
||||
|
||||
[features]
|
||||
svg = ["iced_graphics/svg"]
|
||||
image = ["iced_graphics/image"]
|
||||
png = ["iced_graphics/png"]
|
||||
jpeg = ["iced_graphics/jpeg"]
|
||||
jpeg_rayon = ["iced_graphics/jpeg_rayon"]
|
||||
gif = ["iced_graphics/gif"]
|
||||
webp = ["iced_graphics/webp"]
|
||||
pnm = ["iced_graphics/pnm"]
|
||||
ico = ["iced_graphics/ico"]
|
||||
bmp = ["iced_graphics/bmp"]
|
||||
hdr = ["iced_graphics/hdr"]
|
||||
dds = ["iced_graphics/dds"]
|
||||
farbfeld = ["iced_graphics/farbfeld"]
|
||||
canvas = ["iced_graphics/canvas"]
|
||||
qr_code = ["iced_graphics/qr_code"]
|
||||
default_system_font = ["iced_graphics/font-source"]
|
||||
|
||||
[dependencies]
|
||||
glow = "0.11.1"
|
||||
glow_glyph = "0.5.0"
|
||||
glyph_brush = "0.7"
|
||||
euclid = "0.22"
|
||||
bytemuck = "1.4"
|
||||
log = "0.4"
|
||||
|
||||
[dependencies.iced_native]
|
||||
version = "0.9"
|
||||
path = "../native"
|
||||
|
||||
[dependencies.iced_graphics]
|
||||
version = "0.7"
|
||||
path = "../graphics"
|
||||
features = ["font-fallback", "font-icons", "opengl"]
|
||||
|
||||
[dependencies.tracing]
|
||||
version = "0.1.6"
|
||||
optional = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
all-features = true
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
# `iced_glow`
|
||||
[][documentation]
|
||||
[](https://crates.io/crates/iced_glow)
|
||||
[](https://github.com/iced-rs/iced/blob/master/LICENSE)
|
||||
[](https://discord.gg/3xZJ65GAhd)
|
||||
|
||||
`iced_glow` is a [`glow`] renderer for [`iced_native`]. This renderer supports OpenGL 3.0+ and OpenGL ES 2.0.
|
||||
|
||||
This renderer is mostly used as a fallback for hardware that doesn't support [`wgpu`] (Vulkan, Metal or DX12).
|
||||
|
||||
Currently, `iced_glow` supports the following primitives:
|
||||
- Text, which is rendered using [`glow_glyph`]. No shaping at all.
|
||||
- Quads or rectangles, with rounded borders and a solid background color.
|
||||
- Clip areas, useful to implement scrollables or hide overflowing content.
|
||||
- Meshes of triangles, useful to draw geometry freely.
|
||||
|
||||
<p align="center">
|
||||
<img alt="The native target" src="../docs/graphs/native.png" width="80%">
|
||||
</p>
|
||||
|
||||
[documentation]: https://docs.rs/iced_glow
|
||||
[`iced_native`]: ../native
|
||||
[`glow`]: https://github.com/grovesNL/glow
|
||||
[`wgpu`]: https://github.com/gfx-rs/wgpu
|
||||
[`glow_glyph`]: https://github.com/hecrj/glow_glyph
|
||||
|
||||
## Installation
|
||||
Add `iced_glow` as a dependency in your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
iced_glow = "0.7"
|
||||
```
|
||||
|
||||
__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/iced-rs/iced/releases
|
||||
|
||||
## Current limitations
|
||||
|
||||
The current implementation is quite naive, it uses:
|
||||
|
||||
- A different pipeline/shader for each primitive
|
||||
- A very simplistic layer model: every `Clip` primitive will generate new layers
|
||||
- _Many_ render passes instead of preparing everything upfront
|
||||
- A glyph cache that is trimmed incorrectly when there are multiple layers (a [`glyph_brush`] limitation)
|
||||
|
||||
Some of these issues are already being worked on! If you want to help, [get in touch!]
|
||||
|
||||
[get in touch!]: ../CONTRIBUTING.md
|
||||
[`glyph_brush`]: https://github.com/alexheretic/glyph-brush
|
||||
|
|
@ -1,280 +0,0 @@
|
|||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
use crate::image;
|
||||
use crate::quad;
|
||||
use crate::text;
|
||||
use crate::{program, triangle};
|
||||
use crate::{Settings, Transformation, Viewport};
|
||||
|
||||
use iced_graphics::backend;
|
||||
use iced_graphics::font;
|
||||
use iced_graphics::{Layer, Primitive};
|
||||
use iced_native::alignment;
|
||||
use iced_native::{Font, Size};
|
||||
|
||||
/// A [`glow`] graphics backend for [`iced`].
|
||||
///
|
||||
/// [`glow`]: https://github.com/grovesNL/glow
|
||||
/// [`iced`]: https://github.com/iced-rs/iced
|
||||
#[derive(Debug)]
|
||||
pub struct Backend {
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
image_pipeline: image::Pipeline,
|
||||
quad_pipeline: quad::Pipeline,
|
||||
text_pipeline: text::Pipeline,
|
||||
triangle_pipeline: triangle::Pipeline,
|
||||
default_text_size: f32,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
/// Creates a new [`Backend`].
|
||||
pub fn new(gl: &glow::Context, settings: Settings) -> Self {
|
||||
let text_pipeline = text::Pipeline::new(
|
||||
gl,
|
||||
settings.default_font,
|
||||
settings.text_multithreading,
|
||||
);
|
||||
|
||||
let shader_version = program::Version::new(gl);
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
let image_pipeline = image::Pipeline::new(gl, &shader_version);
|
||||
let quad_pipeline = quad::Pipeline::new(gl, &shader_version);
|
||||
let triangle_pipeline = triangle::Pipeline::new(gl, &shader_version);
|
||||
|
||||
Self {
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
image_pipeline,
|
||||
quad_pipeline,
|
||||
text_pipeline,
|
||||
triangle_pipeline,
|
||||
default_text_size: settings.default_text_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the provided primitives in the default framebuffer.
|
||||
///
|
||||
/// The text provided as overlay will be rendered on top of the primitives.
|
||||
/// This is useful for rendering debug information.
|
||||
pub fn present<T: AsRef<str>>(
|
||||
&mut self,
|
||||
gl: &glow::Context,
|
||||
primitives: &[Primitive],
|
||||
viewport: &Viewport,
|
||||
overlay_text: &[T],
|
||||
) {
|
||||
let viewport_size = viewport.physical_size();
|
||||
let scale_factor = viewport.scale_factor() as f32;
|
||||
let projection = viewport.projection();
|
||||
|
||||
let mut layers = Layer::generate(primitives, viewport);
|
||||
layers.push(Layer::overlay(overlay_text, viewport));
|
||||
|
||||
for layer in layers {
|
||||
self.flush(
|
||||
gl,
|
||||
scale_factor,
|
||||
projection,
|
||||
&layer,
|
||||
viewport_size.height,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
self.image_pipeline.trim_cache(gl);
|
||||
}
|
||||
|
||||
fn flush(
|
||||
&mut self,
|
||||
gl: &glow::Context,
|
||||
scale_factor: f32,
|
||||
transformation: Transformation,
|
||||
layer: &Layer<'_>,
|
||||
target_height: u32,
|
||||
) {
|
||||
let mut bounds = (layer.bounds * scale_factor).snap();
|
||||
|
||||
if bounds.width < 1 || bounds.height < 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
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(
|
||||
&layer.meshes,
|
||||
gl,
|
||||
target_height,
|
||||
scaled,
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
if !layer.images.is_empty() {
|
||||
let scaled = transformation
|
||||
* Transformation::scale(scale_factor, scale_factor);
|
||||
|
||||
self.image_pipeline.draw(
|
||||
gl,
|
||||
target_height,
|
||||
scaled,
|
||||
scale_factor,
|
||||
&layer.images,
|
||||
bounds,
|
||||
);
|
||||
}
|
||||
|
||||
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 {
|
||||
alignment::Horizontal::Left => {
|
||||
glow_glyph::HorizontalAlign::Left
|
||||
}
|
||||
alignment::Horizontal::Center => {
|
||||
glow_glyph::HorizontalAlign::Center
|
||||
}
|
||||
alignment::Horizontal::Right => {
|
||||
glow_glyph::HorizontalAlign::Right
|
||||
}
|
||||
})
|
||||
.v_align(match text.vertical_alignment {
|
||||
alignment::Vertical::Top => {
|
||||
glow_glyph::VerticalAlign::Top
|
||||
}
|
||||
alignment::Vertical::Center => {
|
||||
glow_glyph::VerticalAlign::Center
|
||||
}
|
||||
alignment::Vertical::Bottom => {
|
||||
glow_glyph::VerticalAlign::Bottom
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
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;
|
||||
const ARROW_DOWN_ICON: char = font::ARROW_DOWN_ICON;
|
||||
|
||||
fn default_size(&self) -> f32 {
|
||||
self.default_text_size
|
||||
}
|
||||
|
||||
fn measure(
|
||||
&self,
|
||||
contents: &str,
|
||||
size: f32,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
) -> (f32, f32) {
|
||||
self.text_pipeline.measure(contents, size, font, bounds)
|
||||
}
|
||||
|
||||
fn hit_test(
|
||||
&self,
|
||||
contents: &str,
|
||||
size: f32,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
point: iced_native::Point,
|
||||
nearest_only: bool,
|
||||
) -> Option<text::Hit> {
|
||||
self.text_pipeline.hit_test(
|
||||
contents,
|
||||
size,
|
||||
font,
|
||||
bounds,
|
||||
point,
|
||||
nearest_only,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
impl backend::Image for Backend {
|
||||
fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> {
|
||||
self.image_pipeline.dimensions(handle)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
impl backend::Svg for Backend {
|
||||
fn viewport_dimensions(
|
||||
&self,
|
||||
handle: &iced_native::svg::Handle,
|
||||
) -> Size<u32> {
|
||||
self.image_pipeline.viewport_dimensions(handle)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,254 +0,0 @@
|
|||
mod storage;
|
||||
|
||||
use storage::Storage;
|
||||
|
||||
pub use iced_graphics::triangle::{Mesh2D, Vertex2D};
|
||||
|
||||
use crate::program::{self, Shader};
|
||||
use crate::Transformation;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
use iced_graphics::image::raster;
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
use iced_graphics::image::vector;
|
||||
|
||||
use iced_graphics::layer;
|
||||
use iced_graphics::Rectangle;
|
||||
use iced_graphics::Size;
|
||||
|
||||
use glow::HasContext;
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing::info_span;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Pipeline {
|
||||
program: <glow::Context as HasContext>::Program,
|
||||
vertex_array: <glow::Context as HasContext>::VertexArray,
|
||||
vertex_buffer: <glow::Context as HasContext>::Buffer,
|
||||
transform_location: <glow::Context as HasContext>::UniformLocation,
|
||||
storage: Storage,
|
||||
#[cfg(feature = "image")]
|
||||
raster_cache: RefCell<raster::Cache<Storage>>,
|
||||
#[cfg(feature = "svg")]
|
||||
vector_cache: RefCell<vector::Cache<Storage>>,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
gl: &glow::Context,
|
||||
shader_version: &program::Version,
|
||||
) -> Pipeline {
|
||||
let program = unsafe {
|
||||
let vertex_shader = Shader::vertex(
|
||||
gl,
|
||||
shader_version,
|
||||
include_str!("shader/common/image.vert"),
|
||||
);
|
||||
let fragment_shader = Shader::fragment(
|
||||
gl,
|
||||
shader_version,
|
||||
include_str!("shader/common/image.frag"),
|
||||
);
|
||||
|
||||
program::create(
|
||||
gl,
|
||||
&[vertex_shader, fragment_shader],
|
||||
&[(0, "i_Position")],
|
||||
)
|
||||
};
|
||||
|
||||
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_buffer =
|
||||
unsafe { gl.create_buffer().expect("Create vertex buffer") };
|
||||
let vertex_array =
|
||||
unsafe { gl.create_vertex_array().expect("Create vertex array") };
|
||||
|
||||
unsafe {
|
||||
gl.bind_vertex_array(Some(vertex_array));
|
||||
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
|
||||
|
||||
let vertices = &[0u8, 0, 1, 0, 0, 1, 1, 1];
|
||||
gl.buffer_data_size(
|
||||
glow::ARRAY_BUFFER,
|
||||
vertices.len() as i32,
|
||||
glow::STATIC_DRAW,
|
||||
);
|
||||
gl.buffer_sub_data_u8_slice(
|
||||
glow::ARRAY_BUFFER,
|
||||
0,
|
||||
bytemuck::cast_slice(vertices),
|
||||
);
|
||||
|
||||
gl.enable_vertex_attrib_array(0);
|
||||
gl.vertex_attrib_pointer_f32(
|
||||
0,
|
||||
2,
|
||||
glow::UNSIGNED_BYTE,
|
||||
false,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
gl.bind_buffer(glow::ARRAY_BUFFER, None);
|
||||
gl.bind_vertex_array(None);
|
||||
}
|
||||
|
||||
Pipeline {
|
||||
program,
|
||||
vertex_array,
|
||||
vertex_buffer,
|
||||
transform_location,
|
||||
storage: Storage::default(),
|
||||
#[cfg(feature = "image")]
|
||||
raster_cache: RefCell::new(raster::Cache::default()),
|
||||
#[cfg(feature = "svg")]
|
||||
vector_cache: RefCell::new(vector::Cache::default()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
pub fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> {
|
||||
self.raster_cache.borrow_mut().load(handle).dimensions()
|
||||
}
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
pub fn viewport_dimensions(
|
||||
&self,
|
||||
handle: &iced_native::svg::Handle,
|
||||
) -> Size<u32> {
|
||||
let mut cache = self.vector_cache.borrow_mut();
|
||||
let svg = cache.load(handle);
|
||||
|
||||
svg.viewport_dimensions()
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
mut gl: &glow::Context,
|
||||
target_height: u32,
|
||||
transformation: Transformation,
|
||||
_scale_factor: f32,
|
||||
images: &[layer::Image],
|
||||
layer_bounds: Rectangle<u32>,
|
||||
) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Glow::Image", "DRAW").entered();
|
||||
|
||||
unsafe {
|
||||
gl.use_program(Some(self.program));
|
||||
gl.bind_vertex_array(Some(self.vertex_array));
|
||||
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer));
|
||||
gl.enable(glow::SCISSOR_TEST);
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
let mut raster_cache = self.raster_cache.borrow_mut();
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
let mut vector_cache = self.vector_cache.borrow_mut();
|
||||
|
||||
for image in images {
|
||||
let (entry, bounds) = match &image {
|
||||
#[cfg(feature = "image")]
|
||||
layer::Image::Raster { handle, bounds } => (
|
||||
raster_cache.upload(handle, &mut gl, &mut self.storage),
|
||||
bounds,
|
||||
),
|
||||
#[cfg(not(feature = "image"))]
|
||||
layer::Image::Raster { handle: _, bounds } => (None, bounds),
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
layer::Image::Vector {
|
||||
handle,
|
||||
color,
|
||||
bounds,
|
||||
} => {
|
||||
let size = [bounds.width, bounds.height];
|
||||
(
|
||||
vector_cache.upload(
|
||||
handle,
|
||||
*color,
|
||||
size,
|
||||
_scale_factor,
|
||||
&mut gl,
|
||||
&mut self.storage,
|
||||
),
|
||||
bounds,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "svg"))]
|
||||
layer::Image::Vector { bounds, .. } => (None, bounds),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
gl.scissor(
|
||||
layer_bounds.x as i32,
|
||||
(target_height - (layer_bounds.y + layer_bounds.height))
|
||||
as i32,
|
||||
layer_bounds.width as i32,
|
||||
layer_bounds.height as i32,
|
||||
);
|
||||
|
||||
if let Some(storage::Entry { texture, .. }) = entry {
|
||||
gl.bind_texture(glow::TEXTURE_2D, Some(*texture))
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
let translate = Transformation::translate(bounds.x, bounds.y);
|
||||
let scale = Transformation::scale(bounds.width, bounds.height);
|
||||
let transformation = transformation * translate * scale;
|
||||
let matrix: [f32; 16] = transformation.into();
|
||||
gl.uniform_matrix_4_f32_slice(
|
||||
Some(&self.transform_location),
|
||||
false,
|
||||
&matrix,
|
||||
);
|
||||
|
||||
gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
gl.bind_texture(glow::TEXTURE_2D, None);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
gl.bind_buffer(glow::ARRAY_BUFFER, None);
|
||||
gl.bind_vertex_array(None);
|
||||
gl.use_program(None);
|
||||
gl.disable(glow::SCISSOR_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trim_cache(&mut self, mut gl: &glow::Context) {
|
||||
#[cfg(feature = "image")]
|
||||
self.raster_cache
|
||||
.borrow_mut()
|
||||
.trim(&mut self.storage, &mut gl);
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
self.vector_cache
|
||||
.borrow_mut()
|
||||
.trim(&mut self.storage, &mut gl);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
use iced_graphics::image;
|
||||
use iced_graphics::Size;
|
||||
|
||||
use glow::HasContext;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Storage;
|
||||
|
||||
impl image::Storage for Storage {
|
||||
type Entry = Entry;
|
||||
type State<'a> = &'a glow::Context;
|
||||
|
||||
fn upload(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
data: &[u8],
|
||||
gl: &mut &glow::Context,
|
||||
) -> Option<Self::Entry> {
|
||||
unsafe {
|
||||
let texture = gl.create_texture().expect("create texture");
|
||||
gl.bind_texture(glow::TEXTURE_2D, Some(texture));
|
||||
gl.tex_image_2d(
|
||||
glow::TEXTURE_2D,
|
||||
0,
|
||||
glow::SRGB8_ALPHA8 as i32,
|
||||
width as i32,
|
||||
height as i32,
|
||||
0,
|
||||
glow::RGBA,
|
||||
glow::UNSIGNED_BYTE,
|
||||
Some(data),
|
||||
);
|
||||
gl.tex_parameter_i32(
|
||||
glow::TEXTURE_2D,
|
||||
glow::TEXTURE_WRAP_S,
|
||||
glow::CLAMP_TO_EDGE as _,
|
||||
);
|
||||
gl.tex_parameter_i32(
|
||||
glow::TEXTURE_2D,
|
||||
glow::TEXTURE_WRAP_T,
|
||||
glow::CLAMP_TO_EDGE as _,
|
||||
);
|
||||
gl.tex_parameter_i32(
|
||||
glow::TEXTURE_2D,
|
||||
glow::TEXTURE_MIN_FILTER,
|
||||
glow::LINEAR as _,
|
||||
);
|
||||
gl.tex_parameter_i32(
|
||||
glow::TEXTURE_2D,
|
||||
glow::TEXTURE_MAG_FILTER,
|
||||
glow::LINEAR as _,
|
||||
);
|
||||
gl.bind_texture(glow::TEXTURE_2D, None);
|
||||
|
||||
Some(Entry {
|
||||
size: Size::new(width, height),
|
||||
texture,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&mut self, entry: &Entry, gl: &mut &glow::Context) {
|
||||
unsafe { gl.delete_texture(entry.texture) }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Entry {
|
||||
size: Size<u32>,
|
||||
pub(super) texture: glow::NativeTexture,
|
||||
}
|
||||
|
||||
impl image::storage::Entry for Entry {
|
||||
fn size(&self) -> Size<u32> {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
//! A [`glow`] renderer for [`iced_native`].
|
||||
//!
|
||||
//! 
|
||||
//!
|
||||
//! [`glow`]: https://github.com/grovesNL/glow
|
||||
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
||||
)]
|
||||
#![deny(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
unused_results,
|
||||
clippy::extra_unused_lifetimes,
|
||||
clippy::from_over_into,
|
||||
clippy::needless_borrow,
|
||||
clippy::new_without_default,
|
||||
clippy::useless_conversion
|
||||
)]
|
||||
#![forbid(rust_2018_idioms)]
|
||||
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
pub use glow;
|
||||
|
||||
mod backend;
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
mod image;
|
||||
mod program;
|
||||
mod quad;
|
||||
mod text;
|
||||
mod triangle;
|
||||
|
||||
pub mod settings;
|
||||
pub mod window;
|
||||
|
||||
pub use backend::Backend;
|
||||
pub use settings::Settings;
|
||||
|
||||
pub(crate) use iced_graphics::Transformation;
|
||||
|
||||
pub use iced_graphics::{Error, Viewport};
|
||||
pub use iced_native::Theme;
|
||||
|
||||
pub use iced_native::alignment;
|
||||
pub use iced_native::{Alignment, Background, Color, Command, Length, Vector};
|
||||
|
||||
/// A [`glow`] graphics renderer for [`iced`].
|
||||
///
|
||||
/// [`glow`]: https://github.com/grovesNL/glow
|
||||
/// [`iced`]: https://github.com/iced-rs/iced
|
||||
pub type Renderer<Theme = iced_native::Theme> =
|
||||
iced_graphics::Renderer<Backend, Theme>;
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
use glow::HasContext;
|
||||
|
||||
/// The [`Version`] of a `Program`.
|
||||
pub struct Version {
|
||||
vertex: String,
|
||||
fragment: String,
|
||||
}
|
||||
|
||||
impl Version {
|
||||
pub fn new(gl: &glow::Context) -> Version {
|
||||
let version = gl.version();
|
||||
|
||||
let (vertex, fragment) = match (
|
||||
version.major,
|
||||
version.minor,
|
||||
version.is_embedded,
|
||||
) {
|
||||
// OpenGL 3.0+
|
||||
(3, 0 | 1 | 2, false) => (
|
||||
format!("#version 1{}0\n#extension GL_ARB_explicit_attrib_location : enable", version.minor + 3),
|
||||
format!(
|
||||
"#version 1{}0\n#extension GL_ARB_explicit_attrib_location : enable\n#define HIGHER_THAN_300 1",
|
||||
version.minor + 3
|
||||
),
|
||||
),
|
||||
// OpenGL 3.3+
|
||||
(3 | 4, _, false) => (
|
||||
format!("#version {}{}0\n#extension GL_ARB_explicit_attrib_location : enable", version.major, version.minor),
|
||||
format!(
|
||||
"#version {}{}0\n#extension GL_ARB_explicit_attrib_location : enable\n#define HIGHER_THAN_300 1",
|
||||
version.major, version.minor
|
||||
),
|
||||
),
|
||||
// OpenGL ES 3.0+
|
||||
(3, _, true) => (
|
||||
format!("#version 3{}0 es", version.minor),
|
||||
format!(
|
||||
"#version 3{}0 es\n#define HIGHER_THAN_300 1",
|
||||
version.minor
|
||||
),
|
||||
),
|
||||
// OpenGL ES 2.0+
|
||||
(2, _, true) => (
|
||||
String::from(
|
||||
"#version 100\n#define in attribute\n#define out varying",
|
||||
),
|
||||
String::from("#version 100\n#define in varying"),
|
||||
),
|
||||
// OpenGL 2.1
|
||||
(2, _, false) => (
|
||||
String::from(
|
||||
"#version 120\n#define in attribute\n#define out varying",
|
||||
),
|
||||
String::from("#version 120\n#define in varying"),
|
||||
),
|
||||
// OpenGL 1.1+
|
||||
_ => panic!("Incompatible context version: {version:?}"),
|
||||
};
|
||||
|
||||
log::info!("Shader directive: {}", vertex.lines().next().unwrap());
|
||||
|
||||
Version { vertex, fragment }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Shader(<glow::Context as HasContext>::Shader);
|
||||
|
||||
impl Shader {
|
||||
fn compile(gl: &glow::Context, stage: u32, content: &str) -> Shader {
|
||||
unsafe {
|
||||
let shader = gl.create_shader(stage).expect("Cannot create shader");
|
||||
|
||||
gl.shader_source(shader, content);
|
||||
gl.compile_shader(shader);
|
||||
|
||||
if !gl.get_shader_compile_status(shader) {
|
||||
panic!("{}", gl.get_shader_info_log(shader));
|
||||
}
|
||||
|
||||
Shader(shader)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a vertex [`Shader`].
|
||||
pub fn vertex(
|
||||
gl: &glow::Context,
|
||||
version: &Version,
|
||||
content: &'static str,
|
||||
) -> Self {
|
||||
let content = format!("{}\n{}", version.vertex, content);
|
||||
|
||||
Shader::compile(gl, glow::VERTEX_SHADER, &content)
|
||||
}
|
||||
|
||||
/// Creates a fragment [`Shader`].
|
||||
pub fn fragment(
|
||||
gl: &glow::Context,
|
||||
version: &Version,
|
||||
content: &'static str,
|
||||
) -> Self {
|
||||
let content = format!("{}\n{}", version.fragment, content);
|
||||
|
||||
Shader::compile(gl, glow::FRAGMENT_SHADER, &content)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn create(
|
||||
gl: &glow::Context,
|
||||
shaders: &[Shader],
|
||||
attributes: &[(u32, &str)],
|
||||
) -> <glow::Context as HasContext>::Program {
|
||||
let program = gl.create_program().expect("Cannot create program");
|
||||
|
||||
for shader in shaders {
|
||||
gl.attach_shader(program, shader.0);
|
||||
}
|
||||
|
||||
for (i, name) in attributes {
|
||||
gl.bind_attrib_location(program, *i, name);
|
||||
}
|
||||
|
||||
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.0);
|
||||
gl.delete_shader(shader.0);
|
||||
}
|
||||
|
||||
program
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
mod compatibility;
|
||||
mod core;
|
||||
|
||||
use crate::program;
|
||||
use crate::Transformation;
|
||||
use glow::HasContext;
|
||||
use iced_graphics::layer;
|
||||
use iced_native::Rectangle;
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing::info_span;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Pipeline {
|
||||
Core(core::Pipeline),
|
||||
Compatibility(compatibility::Pipeline),
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
gl: &glow::Context,
|
||||
shader_version: &program::Version,
|
||||
) -> Pipeline {
|
||||
let gl_version = gl.version();
|
||||
|
||||
// OpenGL 3.0+ and OpenGL ES 3.0+ have instancing (which is what separates `core` from `compatibility`)
|
||||
if gl_version.major >= 3 {
|
||||
log::info!("Mode: core");
|
||||
Pipeline::Core(core::Pipeline::new(gl, shader_version))
|
||||
} else {
|
||||
log::info!("Mode: compatibility");
|
||||
Pipeline::Compatibility(compatibility::Pipeline::new(
|
||||
gl,
|
||||
shader_version,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
gl: &glow::Context,
|
||||
target_height: u32,
|
||||
instances: &[layer::Quad],
|
||||
transformation: Transformation,
|
||||
scale: f32,
|
||||
bounds: Rectangle<u32>,
|
||||
) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Glow::Quad", "DRAW").enter();
|
||||
|
||||
match self {
|
||||
Pipeline::Core(pipeline) => {
|
||||
pipeline.draw(
|
||||
gl,
|
||||
target_height,
|
||||
instances,
|
||||
transformation,
|
||||
scale,
|
||||
bounds,
|
||||
);
|
||||
}
|
||||
Pipeline::Compatibility(pipeline) => {
|
||||
pipeline.draw(
|
||||
gl,
|
||||
target_height,
|
||||
instances,
|
||||
transformation,
|
||||
scale,
|
||||
bounds,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,349 +0,0 @@
|
|||
use crate::program::{self, Shader};
|
||||
use crate::Transformation;
|
||||
use glow::HasContext;
|
||||
use iced_graphics::layer;
|
||||
use iced_native::Rectangle;
|
||||
|
||||
// Only change `MAX_QUADS`, otherwise you could cause problems
|
||||
// by splitting a triangle into different render passes.
|
||||
const MAX_QUADS: usize = 100_000;
|
||||
const MAX_VERTICES: usize = MAX_QUADS * 4;
|
||||
const MAX_INDICES: usize = MAX_QUADS * 6;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pipeline {
|
||||
program: <glow::Context as HasContext>::Program,
|
||||
vertex_array: <glow::Context as HasContext>::VertexArray,
|
||||
vertex_buffer: <glow::Context as HasContext>::Buffer,
|
||||
index_buffer: <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,
|
||||
shader_version: &program::Version,
|
||||
) -> Pipeline {
|
||||
let program = unsafe {
|
||||
let vertex_shader = Shader::vertex(
|
||||
gl,
|
||||
shader_version,
|
||||
include_str!("../shader/compatibility/quad.vert"),
|
||||
);
|
||||
let fragment_shader = Shader::fragment(
|
||||
gl,
|
||||
shader_version,
|
||||
include_str!("../shader/compatibility/quad.frag"),
|
||||
);
|
||||
|
||||
program::create(
|
||||
gl,
|
||||
&[vertex_shader, fragment_shader],
|
||||
&[
|
||||
(0, "i_Pos"),
|
||||
(1, "i_Scale"),
|
||||
(2, "i_Color"),
|
||||
(3, "i_BorderColor"),
|
||||
(4, "i_BorderRadius"),
|
||||
(5, "i_BorderWidth"),
|
||||
],
|
||||
)
|
||||
};
|
||||
|
||||
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));
|
||||
|
||||
gl.uniform_matrix_4_f32_slice(
|
||||
Some(&transform_location),
|
||||
false,
|
||||
Transformation::identity().as_ref(),
|
||||
);
|
||||
|
||||
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, vertex_buffer, index_buffer) =
|
||||
unsafe { create_buffers(gl, MAX_VERTICES) };
|
||||
|
||||
Pipeline {
|
||||
program,
|
||||
vertex_array,
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
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>,
|
||||
) {
|
||||
// TODO: Remove this allocation (probably by changing the shader and removing the need of two `position`)
|
||||
let vertices: Vec<Vertex> =
|
||||
instances.iter().flat_map(Vertex::from_quad).collect();
|
||||
|
||||
// TODO: Remove this allocation (or allocate only when needed)
|
||||
let indices: Vec<i32> = (0..instances.len().min(MAX_QUADS) as i32)
|
||||
.flat_map(|i| {
|
||||
[i * 4, 1 + i * 4, 2 + i * 4, 2 + i * 4, 1 + i * 4, 3 + i * 4]
|
||||
})
|
||||
.cycle()
|
||||
.take(instances.len() * 6)
|
||||
.collect();
|
||||
|
||||
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.vertex_buffer));
|
||||
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer));
|
||||
}
|
||||
|
||||
if transformation != self.current_transform {
|
||||
unsafe {
|
||||
gl.uniform_matrix_4_f32_slice(
|
||||
Some(&self.transform_location),
|
||||
false,
|
||||
transformation.as_ref(),
|
||||
);
|
||||
|
||||
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 passes = vertices
|
||||
.chunks(MAX_VERTICES)
|
||||
.zip(indices.chunks(MAX_INDICES));
|
||||
|
||||
for (vertices, indices) in passes {
|
||||
unsafe {
|
||||
gl.buffer_sub_data_u8_slice(
|
||||
glow::ARRAY_BUFFER,
|
||||
0,
|
||||
bytemuck::cast_slice(vertices),
|
||||
);
|
||||
|
||||
gl.buffer_sub_data_u8_slice(
|
||||
glow::ELEMENT_ARRAY_BUFFER,
|
||||
0,
|
||||
bytemuck::cast_slice(indices),
|
||||
);
|
||||
|
||||
gl.draw_elements(
|
||||
glow::TRIANGLES,
|
||||
indices.len() as i32,
|
||||
glow::UNSIGNED_INT,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
gl.bind_vertex_array(None);
|
||||
gl.use_program(None);
|
||||
gl.disable(glow::SCISSOR_TEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn create_buffers(
|
||||
gl: &glow::Context,
|
||||
size: usize,
|
||||
) -> (
|
||||
<glow::Context as HasContext>::VertexArray,
|
||||
<glow::Context as HasContext>::Buffer,
|
||||
<glow::Context as HasContext>::Buffer,
|
||||
) {
|
||||
let vertex_array = gl.create_vertex_array().expect("Create vertex array");
|
||||
let vertex_buffer = gl.create_buffer().expect("Create vertex buffer");
|
||||
let index_buffer = gl.create_buffer().expect("Create index buffer");
|
||||
|
||||
gl.bind_vertex_array(Some(vertex_array));
|
||||
|
||||
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer));
|
||||
gl.buffer_data_size(
|
||||
glow::ELEMENT_ARRAY_BUFFER,
|
||||
12 * size as i32,
|
||||
glow::DYNAMIC_DRAW,
|
||||
);
|
||||
|
||||
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
|
||||
gl.buffer_data_size(
|
||||
glow::ARRAY_BUFFER,
|
||||
(size * Vertex::SIZE) as i32,
|
||||
glow::DYNAMIC_DRAW,
|
||||
);
|
||||
|
||||
let stride = Vertex::SIZE 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, 2, glow::FLOAT, false, stride, 4 * 2);
|
||||
|
||||
gl.enable_vertex_attrib_array(2);
|
||||
gl.vertex_attrib_pointer_f32(2, 4, glow::FLOAT, false, stride, 4 * (2 + 2));
|
||||
|
||||
gl.enable_vertex_attrib_array(3);
|
||||
gl.vertex_attrib_pointer_f32(
|
||||
3,
|
||||
4,
|
||||
glow::FLOAT,
|
||||
false,
|
||||
stride,
|
||||
4 * (2 + 2 + 4),
|
||||
);
|
||||
|
||||
gl.enable_vertex_attrib_array(4);
|
||||
gl.vertex_attrib_pointer_f32(
|
||||
4,
|
||||
4,
|
||||
glow::FLOAT,
|
||||
false,
|
||||
stride,
|
||||
4 * (2 + 2 + 4 + 4),
|
||||
);
|
||||
|
||||
gl.enable_vertex_attrib_array(5);
|
||||
gl.vertex_attrib_pointer_f32(
|
||||
5,
|
||||
1,
|
||||
glow::FLOAT,
|
||||
false,
|
||||
stride,
|
||||
4 * (2 + 2 + 4 + 4 + 4),
|
||||
);
|
||||
|
||||
gl.enable_vertex_attrib_array(6);
|
||||
gl.vertex_attrib_pointer_f32(
|
||||
6,
|
||||
2,
|
||||
glow::FLOAT,
|
||||
false,
|
||||
stride,
|
||||
4 * (2 + 2 + 4 + 4 + 4 + 1),
|
||||
);
|
||||
|
||||
gl.bind_vertex_array(None);
|
||||
gl.bind_buffer(glow::ARRAY_BUFFER, None);
|
||||
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
||||
|
||||
(vertex_array, vertex_buffer, index_buffer)
|
||||
}
|
||||
|
||||
/// The vertex of a colored rectangle with a border.
|
||||
///
|
||||
/// This type can be directly uploaded to GPU memory.
|
||||
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct Vertex {
|
||||
/// The position of the [`Vertex`].
|
||||
pub position: [f32; 2],
|
||||
|
||||
/// The size of the [`Vertex`].
|
||||
pub size: [f32; 2],
|
||||
|
||||
/// The color of the [`Vertex`], in __linear RGB__.
|
||||
pub color: [f32; 4],
|
||||
|
||||
/// The border color of the [`Vertex`], in __linear RGB__.
|
||||
pub border_color: [f32; 4],
|
||||
|
||||
/// The border radius of the [`Vertex`].
|
||||
pub border_radius: [f32; 4],
|
||||
|
||||
/// The border width of the [`Vertex`].
|
||||
pub border_width: f32,
|
||||
|
||||
/// The __quad__ position of the [`Vertex`].
|
||||
pub q_position: [f32; 2],
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
const SIZE: usize = std::mem::size_of::<Self>();
|
||||
|
||||
fn from_quad(quad: &layer::Quad) -> [Vertex; 4] {
|
||||
let base = Vertex {
|
||||
position: quad.position,
|
||||
size: quad.size,
|
||||
color: quad.color,
|
||||
border_color: quad.color,
|
||||
border_radius: quad.border_radius,
|
||||
border_width: quad.border_width,
|
||||
q_position: [0.0, 0.0],
|
||||
};
|
||||
|
||||
[
|
||||
base,
|
||||
Self {
|
||||
q_position: [0.0, 1.0],
|
||||
..base
|
||||
},
|
||||
Self {
|
||||
q_position: [1.0, 0.0],
|
||||
..base
|
||||
},
|
||||
Self {
|
||||
q_position: [1.0, 1.0],
|
||||
..base
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,244 +0,0 @@
|
|||
use crate::program::{self, Shader};
|
||||
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,
|
||||
shader_version: &program::Version,
|
||||
) -> Pipeline {
|
||||
let program = unsafe {
|
||||
let vertex_shader = Shader::vertex(
|
||||
gl,
|
||||
shader_version,
|
||||
include_str!("../shader/core/quad.vert"),
|
||||
);
|
||||
let fragment_shader = Shader::fragment(
|
||||
gl,
|
||||
shader_version,
|
||||
include_str!("../shader/core/quad.frag"),
|
||||
);
|
||||
|
||||
program::create(
|
||||
gl,
|
||||
&[vertex_shader, fragment_shader],
|
||||
&[
|
||||
(0, "i_Pos"),
|
||||
(1, "i_Scale"),
|
||||
(2, "i_Color"),
|
||||
(3, "i_BorderColor"),
|
||||
(4, "i_BorderRadius"),
|
||||
(5, "i_BorderWidth"),
|
||||
],
|
||||
)
|
||||
};
|
||||
|
||||
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));
|
||||
|
||||
gl.uniform_matrix_4_f32_slice(
|
||||
Some(&transform_location),
|
||||
false,
|
||||
Transformation::identity().as_ref(),
|
||||
);
|
||||
|
||||
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 {
|
||||
gl.uniform_matrix_4_f32_slice(
|
||||
Some(&self.transform_location),
|
||||
false,
|
||||
transformation.as_ref(),
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
for instances in instances.chunks(MAX_INSTANCES) {
|
||||
unsafe {
|
||||
gl.buffer_sub_data_u8_slice(
|
||||
glow::ARRAY_BUFFER,
|
||||
0,
|
||||
bytemuck::cast_slice(instances),
|
||||
);
|
||||
|
||||
gl.draw_arrays_instanced(
|
||||
glow::TRIANGLE_STRIP,
|
||||
0,
|
||||
4,
|
||||
instances.len() as i32,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
4,
|
||||
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 + 4),
|
||||
);
|
||||
gl.vertex_attrib_divisor(5, 1);
|
||||
|
||||
gl.bind_vertex_array(None);
|
||||
gl.bind_buffer(glow::ARRAY_BUFFER, None);
|
||||
|
||||
(vertex_array, buffer)
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
//! Configure a renderer.
|
||||
pub use iced_graphics::Antialiasing;
|
||||
|
||||
/// The settings of a [`Backend`].
|
||||
///
|
||||
/// [`Backend`]: crate::Backend
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
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 default size of text.
|
||||
///
|
||||
/// By default, it will be set to `20.0`.
|
||||
pub default_text_size: f32,
|
||||
|
||||
/// If enabled, spread text workload in multiple threads when multiple cores
|
||||
/// are available.
|
||||
///
|
||||
/// By default, it is disabled.
|
||||
pub text_multithreading: bool,
|
||||
|
||||
/// The antialiasing strategy that will be used for triangle primitives.
|
||||
///
|
||||
/// By default, it is `None`.
|
||||
pub antialiasing: Option<Antialiasing>,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Settings {
|
||||
Settings {
|
||||
default_font: None,
|
||||
default_text_size: 20.0,
|
||||
text_multithreading: false,
|
||||
antialiasing: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Settings {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Settings")
|
||||
// Instead of printing the font bytes, we simply show a `bool` indicating if using a default font or not.
|
||||
.field("default_font", &self.default_font.is_some())
|
||||
.field("default_text_size", &self.default_text_size)
|
||||
.field("text_multithreading", &self.text_multithreading)
|
||||
.field("antialiasing", &self.antialiasing)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
/// Creates new [`Settings`] using environment configuration.
|
||||
///
|
||||
/// Currently, this is equivalent to calling [`Settings::default`].
|
||||
pub fn from_env() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
#ifdef GL_ES
|
||||
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
||||
precision highp float;
|
||||
#else
|
||||
precision mediump float;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HIGHER_THAN_300
|
||||
layout (location = 0) out vec4 fragColor;
|
||||
#define gl_FragColor fragColor
|
||||
#endif
|
||||
|
||||
in vec2 raw_position;
|
||||
|
||||
uniform vec4 gradient_direction;
|
||||
uniform int color_stops_size;
|
||||
// GLSL does not support dynamically sized arrays without SSBOs so this is capped to 16 stops
|
||||
//stored as color(vec4) -> offset(vec4) sequentially;
|
||||
uniform vec4 color_stops[32];
|
||||
|
||||
//TODO: rewrite without branching to make ALUs happy
|
||||
void main() {
|
||||
vec2 start = gradient_direction.xy;
|
||||
vec2 end = gradient_direction.zw;
|
||||
vec2 gradient_vec = vec2(end - start);
|
||||
vec2 current_vec = vec2(raw_position.xy - start);
|
||||
vec2 unit = normalize(gradient_vec);
|
||||
float coord_offset = dot(unit, current_vec) / length(gradient_vec);
|
||||
//if a gradient has a start/end stop that is identical, the mesh will have a transparent fill
|
||||
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
float min_offset = color_stops[1].x;
|
||||
float max_offset = color_stops[color_stops_size - 1].x;
|
||||
|
||||
for (int i = 0; i < color_stops_size - 2; i += 2) {
|
||||
float curr_offset = color_stops[i+1].x;
|
||||
float next_offset = color_stops[i+3].x;
|
||||
|
||||
if (coord_offset <= min_offset) {
|
||||
//current coordinate is before the first defined offset, set it to the start color
|
||||
gl_FragColor = color_stops[0];
|
||||
}
|
||||
|
||||
if (curr_offset <= coord_offset && coord_offset <= next_offset) {
|
||||
//current fragment is between the current offset processing & the next one, interpolate colors
|
||||
gl_FragColor = mix(color_stops[i], color_stops[i+2], smoothstep(
|
||||
curr_offset,
|
||||
next_offset,
|
||||
coord_offset
|
||||
));
|
||||
}
|
||||
|
||||
if (coord_offset >= max_offset) {
|
||||
//current coordinate is before the last defined offset, set it to the last color
|
||||
gl_FragColor = color_stops[color_stops_size - 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
uniform mat4 u_Transform;
|
||||
|
||||
in vec2 i_Position;
|
||||
out vec2 raw_position;
|
||||
|
||||
void main() {
|
||||
gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0);
|
||||
raw_position = i_Position;
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
#ifdef GL_ES
|
||||
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
||||
precision highp float;
|
||||
#else
|
||||
precision mediump float;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
uniform sampler2D tex;
|
||||
in vec2 tex_pos;
|
||||
|
||||
#ifdef HIGHER_THAN_300
|
||||
out vec4 fragColor;
|
||||
#define gl_FragColor fragColor
|
||||
#endif
|
||||
#ifdef GL_ES
|
||||
#define texture texture2D
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture(tex, tex_pos);
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
uniform mat4 u_Transform;
|
||||
|
||||
in vec2 i_Position;
|
||||
out vec2 tex_pos;
|
||||
|
||||
void main() {
|
||||
gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0);
|
||||
tex_pos = i_Position;
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
#ifdef GL_ES
|
||||
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
||||
precision highp float;
|
||||
#else
|
||||
precision mediump float;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HIGHER_THAN_300
|
||||
out vec4 fragColor;
|
||||
#define gl_FragColor fragColor
|
||||
#endif
|
||||
|
||||
in vec4 v_Color;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = v_Color;
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
uniform mat4 u_Transform;
|
||||
|
||||
in vec2 i_Position;
|
||||
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;
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
#ifdef GL_ES
|
||||
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
||||
precision highp float;
|
||||
#else
|
||||
precision mediump float;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
uniform float u_ScreenHeight;
|
||||
|
||||
varying vec4 v_Color;
|
||||
varying vec4 v_BorderColor;
|
||||
varying vec2 v_Pos;
|
||||
varying vec2 v_Scale;
|
||||
varying vec4 v_BorderRadius;
|
||||
varying float v_BorderWidth;
|
||||
|
||||
float _distance(vec2 frag_coord, vec2 position, 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);
|
||||
}
|
||||
|
||||
float selectBorderRadius(vec4 radi, vec2 position, vec2 center)
|
||||
{
|
||||
float rx = radi.x;
|
||||
float ry = radi.y;
|
||||
rx = position.x > center.x ? radi.y : radi.x;
|
||||
ry = position.x > center.x ? radi.z : radi.w;
|
||||
rx = position.y > center.y ? ry : rx;
|
||||
return rx;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y);
|
||||
|
||||
float border_radius = selectBorderRadius(
|
||||
v_BorderRadius,
|
||||
fragCoord,
|
||||
(v_Pos + v_Scale * 0.5).xy
|
||||
);
|
||||
|
||||
float internal_border = max(border_radius - 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
|
||||
);
|
||||
|
||||
vec4 mixed_color = mix(v_Color, v_BorderColor, border_mix);
|
||||
|
||||
float d = _distance(
|
||||
fragCoord,
|
||||
v_Pos,
|
||||
v_Scale,
|
||||
border_radius
|
||||
);
|
||||
|
||||
float radius_alpha =
|
||||
1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d);
|
||||
|
||||
gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
uniform mat4 u_Transform;
|
||||
uniform float u_Scale;
|
||||
|
||||
attribute vec2 i_Pos;
|
||||
attribute vec2 i_Scale;
|
||||
attribute vec4 i_Color;
|
||||
attribute vec4 i_BorderColor;
|
||||
attribute vec4 i_BorderRadius;
|
||||
attribute float i_BorderWidth;
|
||||
attribute vec2 q_Pos;
|
||||
|
||||
varying vec4 v_Color;
|
||||
varying vec4 v_BorderColor;
|
||||
varying vec2 v_Pos;
|
||||
varying vec2 v_Scale;
|
||||
varying vec4 v_BorderRadius;
|
||||
varying float v_BorderWidth;
|
||||
|
||||
|
||||
void main() {
|
||||
vec2 p_Pos = i_Pos * u_Scale;
|
||||
vec2 p_Scale = i_Scale * u_Scale;
|
||||
|
||||
vec4 i_BorderRadius = vec4(
|
||||
min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0),
|
||||
min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0),
|
||||
min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0),
|
||||
min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0)
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
#ifdef GL_ES
|
||||
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
||||
precision highp float;
|
||||
#else
|
||||
precision mediump float;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HIGHER_THAN_300
|
||||
out vec4 fragColor;
|
||||
#define gl_FragColor fragColor
|
||||
#endif
|
||||
|
||||
uniform float u_ScreenHeight;
|
||||
|
||||
in vec4 v_Color;
|
||||
in vec4 v_BorderColor;
|
||||
in vec2 v_Pos;
|
||||
in vec2 v_Scale;
|
||||
in vec4 v_BorderRadius;
|
||||
in float v_BorderWidth;
|
||||
|
||||
float fDistance(vec2 frag_coord, vec2 position, 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);
|
||||
}
|
||||
|
||||
float selectBorderRadius(vec4 radi, vec2 position, vec2 center)
|
||||
{
|
||||
float rx = radi.x;
|
||||
float ry = radi.y;
|
||||
rx = position.x > center.x ? radi.y : radi.x;
|
||||
ry = position.x > center.x ? radi.z : radi.w;
|
||||
rx = position.y > center.y ? ry : rx;
|
||||
return rx;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 mixed_color;
|
||||
|
||||
vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y);
|
||||
|
||||
float border_radius = selectBorderRadius(
|
||||
v_BorderRadius,
|
||||
fragCoord,
|
||||
(v_Pos + v_Scale * 0.5).xy
|
||||
);
|
||||
|
||||
// TODO: Remove branching (?)
|
||||
if(v_BorderWidth > 0.0) {
|
||||
float internal_border = max(border_radius - v_BorderWidth, 0.0);
|
||||
|
||||
float internal_distance = fDistance(
|
||||
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 = fDistance(
|
||||
fragCoord,
|
||||
v_Pos,
|
||||
v_Scale,
|
||||
border_radius
|
||||
);
|
||||
|
||||
float radius_alpha =
|
||||
1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d);
|
||||
|
||||
gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha);
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
uniform mat4 u_Transform;
|
||||
uniform float u_Scale;
|
||||
|
||||
in vec2 i_Pos;
|
||||
in vec2 i_Scale;
|
||||
in vec4 i_Color;
|
||||
in vec4 i_BorderColor;
|
||||
in vec4 i_BorderRadius;
|
||||
in float i_BorderWidth;
|
||||
|
||||
out vec4 v_Color;
|
||||
out vec4 v_BorderColor;
|
||||
out vec2 v_Pos;
|
||||
out vec2 v_Scale;
|
||||
out vec4 v_BorderRadius;
|
||||
out float v_BorderWidth;
|
||||
|
||||
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;
|
||||
|
||||
vec4 i_BorderRadius = vec4(
|
||||
min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0),
|
||||
min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0),
|
||||
min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0),
|
||||
min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0)
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
257
glow/src/text.rs
257
glow/src/text.rs
|
|
@ -1,257 +0,0 @@
|
|||
use crate::Transformation;
|
||||
|
||||
use iced_graphics::font;
|
||||
|
||||
use glow_glyph::ab_glyph;
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
|
||||
pub use iced_native::text::Hit;
|
||||
|
||||
#[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]>,
|
||||
multithreading: bool,
|
||||
) -> Self {
|
||||
let default_font = default_font.map(|slice| slice.to_vec());
|
||||
|
||||
// TODO: Font customization
|
||||
#[cfg(feature = "default_system_font")]
|
||||
let default_font = {
|
||||
default_font.or_else(|| {
|
||||
font::Source::new()
|
||||
.load(&[font::Family::SansSerif, font::Family::Serif])
|
||||
.ok()
|
||||
})
|
||||
};
|
||||
|
||||
let default_font =
|
||||
default_font.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_builder =
|
||||
glow_glyph::GlyphBrushBuilder::using_font(font.clone())
|
||||
.initial_cache_size((2048, 2048))
|
||||
.draw_cache_multithread(multithreading);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true);
|
||||
|
||||
let draw_brush = draw_brush_builder.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 hit_test(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
font: iced_native::Font,
|
||||
bounds: iced_native::Size,
|
||||
point: iced_native::Point,
|
||||
nearest_only: bool,
|
||||
) -> Option<Hit> {
|
||||
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()
|
||||
};
|
||||
|
||||
let mut mb = self.measure_brush.borrow_mut();
|
||||
|
||||
// The underlying type is FontArc, so clones are cheap.
|
||||
use ab_glyph::{Font, ScaleFont};
|
||||
let font = mb.fonts()[font_id].clone().into_scaled(size);
|
||||
|
||||
// Implements an iterator over the glyph bounding boxes.
|
||||
let bounds = mb.glyphs(section).map(
|
||||
|glow_glyph::SectionGlyph {
|
||||
byte_index, glyph, ..
|
||||
}| {
|
||||
(
|
||||
*byte_index,
|
||||
iced_native::Rectangle::new(
|
||||
iced_native::Point::new(
|
||||
glyph.position.x - font.h_side_bearing(glyph.id),
|
||||
glyph.position.y - font.ascent(),
|
||||
),
|
||||
iced_native::Size::new(
|
||||
font.h_advance(glyph.id),
|
||||
font.ascent() - font.descent(),
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
// Implements computation of the character index based on the byte index
|
||||
// within the input string.
|
||||
let char_index = |byte_index| {
|
||||
let mut b_count = 0;
|
||||
for (i, utf8_len) in
|
||||
content.chars().map(|c| c.len_utf8()).enumerate()
|
||||
{
|
||||
if byte_index < (b_count + utf8_len) {
|
||||
return i;
|
||||
}
|
||||
b_count += utf8_len;
|
||||
}
|
||||
|
||||
byte_index
|
||||
};
|
||||
|
||||
if !nearest_only {
|
||||
for (idx, bounds) in bounds.clone() {
|
||||
if bounds.contains(point) {
|
||||
return Some(Hit::CharOffset(char_index(idx)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nearest = bounds
|
||||
.map(|(index, bounds)| (index, bounds.center()))
|
||||
.min_by(|(_, center_a), (_, center_b)| {
|
||||
center_a
|
||||
.distance(point)
|
||||
.partial_cmp(¢er_b.distance(point))
|
||||
.unwrap_or(std::cmp::Ordering::Greater)
|
||||
});
|
||||
|
||||
nearest.map(|(idx, center)| {
|
||||
Hit::NearestCharOffset(char_index(idx), point - center)
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,595 +0,0 @@
|
|||
//! Draw meshes of triangles.
|
||||
use crate::program;
|
||||
use crate::Transformation;
|
||||
|
||||
use iced_graphics::gradient::Gradient;
|
||||
use iced_graphics::layer::mesh::{self, Mesh};
|
||||
use iced_graphics::triangle::{ColoredVertex2D, Vertex2D};
|
||||
|
||||
use glow::HasContext;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing::info_span;
|
||||
|
||||
const DEFAULT_VERTICES: usize = 1_000;
|
||||
const DEFAULT_INDICES: usize = 1_000;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Pipeline {
|
||||
indices: Buffer<u32>,
|
||||
solid: solid::Program,
|
||||
gradient: gradient::Program,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(gl: &glow::Context, shader_version: &program::Version) -> Self {
|
||||
let mut indices = unsafe {
|
||||
Buffer::new(
|
||||
gl,
|
||||
glow::ELEMENT_ARRAY_BUFFER,
|
||||
glow::DYNAMIC_DRAW,
|
||||
DEFAULT_INDICES,
|
||||
)
|
||||
};
|
||||
|
||||
let solid = solid::Program::new(gl, shader_version);
|
||||
let gradient = gradient::Program::new(gl, shader_version);
|
||||
|
||||
unsafe {
|
||||
gl.bind_vertex_array(Some(solid.vertex_array));
|
||||
indices.bind(gl, 0);
|
||||
|
||||
gl.bind_vertex_array(Some(gradient.vertex_array));
|
||||
indices.bind(gl, 0);
|
||||
|
||||
gl.bind_vertex_array(None);
|
||||
}
|
||||
|
||||
Self {
|
||||
indices,
|
||||
solid,
|
||||
gradient,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
meshes: &[Mesh<'_>],
|
||||
gl: &glow::Context,
|
||||
target_height: u32,
|
||||
transformation: Transformation,
|
||||
scale_factor: f32,
|
||||
) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Glow::Triangle", "DRAW").enter();
|
||||
|
||||
unsafe {
|
||||
gl.enable(glow::MULTISAMPLE);
|
||||
gl.enable(glow::SCISSOR_TEST);
|
||||
}
|
||||
|
||||
// Count the total amount of vertices & indices we need to handle
|
||||
let count = mesh::attribute_count_of(meshes);
|
||||
|
||||
// Then we ensure the current attribute buffers are big enough, resizing if necessary
|
||||
unsafe {
|
||||
self.indices.bind(gl, count.indices);
|
||||
}
|
||||
|
||||
// We upload all the vertices and indices upfront
|
||||
let mut solid_vertex_offset = 0;
|
||||
let mut gradient_vertex_offset = 0;
|
||||
let mut index_offset = 0;
|
||||
|
||||
for mesh in meshes {
|
||||
let indices = mesh.indices();
|
||||
|
||||
unsafe {
|
||||
gl.buffer_sub_data_u8_slice(
|
||||
glow::ELEMENT_ARRAY_BUFFER,
|
||||
(index_offset * std::mem::size_of::<u32>()) as i32,
|
||||
bytemuck::cast_slice(indices),
|
||||
);
|
||||
|
||||
index_offset += indices.len();
|
||||
}
|
||||
|
||||
match mesh {
|
||||
Mesh::Solid { buffers, .. } => {
|
||||
unsafe {
|
||||
self.solid.vertices.bind(gl, count.solid_vertices);
|
||||
|
||||
gl.buffer_sub_data_u8_slice(
|
||||
glow::ARRAY_BUFFER,
|
||||
(solid_vertex_offset
|
||||
* std::mem::size_of::<ColoredVertex2D>())
|
||||
as i32,
|
||||
bytemuck::cast_slice(&buffers.vertices),
|
||||
);
|
||||
}
|
||||
|
||||
solid_vertex_offset += buffers.vertices.len();
|
||||
}
|
||||
Mesh::Gradient { buffers, .. } => {
|
||||
unsafe {
|
||||
self.gradient
|
||||
.vertices
|
||||
.bind(gl, count.gradient_vertices);
|
||||
|
||||
gl.buffer_sub_data_u8_slice(
|
||||
glow::ARRAY_BUFFER,
|
||||
(gradient_vertex_offset
|
||||
* std::mem::size_of::<Vertex2D>())
|
||||
as i32,
|
||||
bytemuck::cast_slice(&buffers.vertices),
|
||||
);
|
||||
}
|
||||
|
||||
gradient_vertex_offset += buffers.vertices.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then we draw each mesh using offsets
|
||||
let mut last_solid_vertex = 0;
|
||||
let mut last_gradient_vertex = 0;
|
||||
let mut last_index = 0;
|
||||
|
||||
for mesh in meshes {
|
||||
let indices = mesh.indices();
|
||||
let origin = mesh.origin();
|
||||
|
||||
let transform =
|
||||
transformation * Transformation::translate(origin.x, origin.y);
|
||||
|
||||
let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
|
||||
|
||||
unsafe {
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
match mesh {
|
||||
Mesh::Solid { buffers, .. } => unsafe {
|
||||
gl.use_program(Some(self.solid.program));
|
||||
gl.bind_vertex_array(Some(self.solid.vertex_array));
|
||||
|
||||
if transform != self.solid.uniforms.transform {
|
||||
gl.uniform_matrix_4_f32_slice(
|
||||
Some(&self.solid.uniforms.transform_location),
|
||||
false,
|
||||
transform.as_ref(),
|
||||
);
|
||||
|
||||
self.solid.uniforms.transform = transform;
|
||||
}
|
||||
|
||||
gl.draw_elements_base_vertex(
|
||||
glow::TRIANGLES,
|
||||
indices.len() as i32,
|
||||
glow::UNSIGNED_INT,
|
||||
(last_index * std::mem::size_of::<u32>()) as i32,
|
||||
last_solid_vertex as i32,
|
||||
);
|
||||
|
||||
last_solid_vertex += buffers.vertices.len();
|
||||
},
|
||||
Mesh::Gradient {
|
||||
buffers, gradient, ..
|
||||
} => unsafe {
|
||||
gl.use_program(Some(self.gradient.program));
|
||||
gl.bind_vertex_array(Some(self.gradient.vertex_array));
|
||||
|
||||
if transform != self.gradient.uniforms.transform {
|
||||
gl.uniform_matrix_4_f32_slice(
|
||||
Some(&self.gradient.uniforms.locations.transform),
|
||||
false,
|
||||
transform.as_ref(),
|
||||
);
|
||||
|
||||
self.gradient.uniforms.transform = transform;
|
||||
}
|
||||
|
||||
if &self.gradient.uniforms.gradient != *gradient {
|
||||
match gradient {
|
||||
Gradient::Linear(linear) => {
|
||||
gl.uniform_4_f32(
|
||||
Some(
|
||||
&self
|
||||
.gradient
|
||||
.uniforms
|
||||
.locations
|
||||
.gradient_direction,
|
||||
),
|
||||
linear.start.x,
|
||||
linear.start.y,
|
||||
linear.end.x,
|
||||
linear.end.y,
|
||||
);
|
||||
|
||||
gl.uniform_1_i32(
|
||||
Some(
|
||||
&self
|
||||
.gradient
|
||||
.uniforms
|
||||
.locations
|
||||
.color_stops_size,
|
||||
),
|
||||
(linear.color_stops.len() * 2) as i32,
|
||||
);
|
||||
|
||||
let mut stops = [0.0; 128];
|
||||
|
||||
for (index, stop) in linear
|
||||
.color_stops
|
||||
.iter()
|
||||
.enumerate()
|
||||
.take(16)
|
||||
{
|
||||
let [r, g, b, a] = stop.color.into_linear();
|
||||
|
||||
stops[index * 8] = r;
|
||||
stops[(index * 8) + 1] = g;
|
||||
stops[(index * 8) + 2] = b;
|
||||
stops[(index * 8) + 3] = a;
|
||||
stops[(index * 8) + 4] = stop.offset;
|
||||
stops[(index * 8) + 5] = 0.;
|
||||
stops[(index * 8) + 6] = 0.;
|
||||
stops[(index * 8) + 7] = 0.;
|
||||
}
|
||||
|
||||
gl.uniform_4_f32_slice(
|
||||
Some(
|
||||
&self
|
||||
.gradient
|
||||
.uniforms
|
||||
.locations
|
||||
.color_stops,
|
||||
),
|
||||
&stops,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.gradient.uniforms.gradient = (*gradient).clone();
|
||||
}
|
||||
|
||||
gl.draw_elements_base_vertex(
|
||||
glow::TRIANGLES,
|
||||
indices.len() as i32,
|
||||
glow::UNSIGNED_INT,
|
||||
(last_index * std::mem::size_of::<u32>()) as i32,
|
||||
last_gradient_vertex as i32,
|
||||
);
|
||||
|
||||
last_gradient_vertex += buffers.vertices.len();
|
||||
},
|
||||
}
|
||||
|
||||
last_index += indices.len();
|
||||
}
|
||||
|
||||
unsafe {
|
||||
gl.bind_vertex_array(None);
|
||||
gl.disable(glow::SCISSOR_TEST);
|
||||
gl.disable(glow::MULTISAMPLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod solid {
|
||||
use crate::program;
|
||||
use crate::triangle;
|
||||
use glow::{Context, HasContext, NativeProgram};
|
||||
use iced_graphics::triangle::ColoredVertex2D;
|
||||
use iced_graphics::Transformation;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Program {
|
||||
pub program: <Context as HasContext>::Program,
|
||||
pub vertex_array: <glow::Context as HasContext>::VertexArray,
|
||||
pub vertices: triangle::Buffer<ColoredVertex2D>,
|
||||
pub uniforms: Uniforms,
|
||||
}
|
||||
|
||||
impl Program {
|
||||
pub fn new(gl: &Context, shader_version: &program::Version) -> Self {
|
||||
let program = unsafe {
|
||||
let vertex_shader = program::Shader::vertex(
|
||||
gl,
|
||||
shader_version,
|
||||
include_str!("shader/common/solid.vert"),
|
||||
);
|
||||
|
||||
let fragment_shader = program::Shader::fragment(
|
||||
gl,
|
||||
shader_version,
|
||||
include_str!("shader/common/solid.frag"),
|
||||
);
|
||||
|
||||
program::create(
|
||||
gl,
|
||||
&[vertex_shader, fragment_shader],
|
||||
&[(0, "i_Position"), (1, "i_Color")],
|
||||
)
|
||||
};
|
||||
|
||||
let vertex_array = unsafe {
|
||||
gl.create_vertex_array().expect("Create vertex array")
|
||||
};
|
||||
|
||||
let vertices = unsafe {
|
||||
triangle::Buffer::new(
|
||||
gl,
|
||||
glow::ARRAY_BUFFER,
|
||||
glow::DYNAMIC_DRAW,
|
||||
super::DEFAULT_VERTICES,
|
||||
)
|
||||
};
|
||||
|
||||
unsafe {
|
||||
gl.bind_vertex_array(Some(vertex_array));
|
||||
|
||||
let stride = std::mem::size_of::<ColoredVertex2D>() 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);
|
||||
};
|
||||
|
||||
Self {
|
||||
program,
|
||||
vertex_array,
|
||||
vertices,
|
||||
uniforms: Uniforms::new(gl, program),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Uniforms {
|
||||
pub transform: Transformation,
|
||||
pub transform_location: <Context as HasContext>::UniformLocation,
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
fn new(gl: &Context, program: NativeProgram) -> Self {
|
||||
let transform = Transformation::identity();
|
||||
let transform_location =
|
||||
unsafe { gl.get_uniform_location(program, "u_Transform") }
|
||||
.expect("Solid - Get u_Transform.");
|
||||
|
||||
unsafe {
|
||||
gl.use_program(Some(program));
|
||||
|
||||
gl.uniform_matrix_4_f32_slice(
|
||||
Some(&transform_location),
|
||||
false,
|
||||
transform.as_ref(),
|
||||
);
|
||||
|
||||
gl.use_program(None);
|
||||
}
|
||||
|
||||
Self {
|
||||
transform,
|
||||
transform_location,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod gradient {
|
||||
use crate::program;
|
||||
use crate::triangle;
|
||||
use glow::{Context, HasContext, NativeProgram};
|
||||
use iced_graphics::gradient::{self, Gradient};
|
||||
use iced_graphics::triangle::Vertex2D;
|
||||
use iced_graphics::Transformation;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Program {
|
||||
pub program: <Context as HasContext>::Program,
|
||||
pub vertex_array: <glow::Context as HasContext>::VertexArray,
|
||||
pub vertices: triangle::Buffer<Vertex2D>,
|
||||
pub uniforms: Uniforms,
|
||||
}
|
||||
|
||||
impl Program {
|
||||
pub fn new(gl: &Context, shader_version: &program::Version) -> Self {
|
||||
let program = unsafe {
|
||||
let vertex_shader = program::Shader::vertex(
|
||||
gl,
|
||||
shader_version,
|
||||
include_str!("shader/common/gradient.vert"),
|
||||
);
|
||||
|
||||
let fragment_shader = program::Shader::fragment(
|
||||
gl,
|
||||
shader_version,
|
||||
include_str!("shader/common/gradient.frag"),
|
||||
);
|
||||
|
||||
program::create(
|
||||
gl,
|
||||
&[vertex_shader, fragment_shader],
|
||||
&[(0, "i_Position")],
|
||||
)
|
||||
};
|
||||
|
||||
let vertex_array = unsafe {
|
||||
gl.create_vertex_array().expect("Create vertex array")
|
||||
};
|
||||
|
||||
let vertices = unsafe {
|
||||
triangle::Buffer::new(
|
||||
gl,
|
||||
glow::ARRAY_BUFFER,
|
||||
glow::DYNAMIC_DRAW,
|
||||
super::DEFAULT_VERTICES,
|
||||
)
|
||||
};
|
||||
|
||||
unsafe {
|
||||
gl.bind_vertex_array(Some(vertex_array));
|
||||
|
||||
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.bind_vertex_array(None);
|
||||
};
|
||||
|
||||
Self {
|
||||
program,
|
||||
vertex_array,
|
||||
vertices,
|
||||
uniforms: Uniforms::new(gl, program),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Uniforms {
|
||||
pub gradient: Gradient,
|
||||
pub transform: Transformation,
|
||||
pub locations: Locations,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Locations {
|
||||
pub gradient_direction: <Context as HasContext>::UniformLocation,
|
||||
pub color_stops_size: <Context as HasContext>::UniformLocation,
|
||||
//currently the maximum number of stops is 16 due to lack of SSBO in GL2.1
|
||||
pub color_stops: <Context as HasContext>::UniformLocation,
|
||||
pub transform: <Context as HasContext>::UniformLocation,
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
fn new(gl: &Context, program: NativeProgram) -> Self {
|
||||
let gradient_direction = unsafe {
|
||||
gl.get_uniform_location(program, "gradient_direction")
|
||||
}
|
||||
.expect("Gradient - Get gradient_direction.");
|
||||
|
||||
let color_stops_size =
|
||||
unsafe { gl.get_uniform_location(program, "color_stops_size") }
|
||||
.expect("Gradient - Get color_stops_size.");
|
||||
|
||||
let color_stops = unsafe {
|
||||
gl.get_uniform_location(program, "color_stops")
|
||||
.expect("Gradient - Get color_stops.")
|
||||
};
|
||||
|
||||
let transform = Transformation::identity();
|
||||
let transform_location =
|
||||
unsafe { gl.get_uniform_location(program, "u_Transform") }
|
||||
.expect("Solid - Get u_Transform.");
|
||||
|
||||
unsafe {
|
||||
gl.use_program(Some(program));
|
||||
|
||||
gl.uniform_matrix_4_f32_slice(
|
||||
Some(&transform_location),
|
||||
false,
|
||||
transform.as_ref(),
|
||||
);
|
||||
|
||||
gl.use_program(None);
|
||||
}
|
||||
|
||||
Self {
|
||||
gradient: Gradient::Linear(gradient::Linear {
|
||||
start: Default::default(),
|
||||
end: Default::default(),
|
||||
color_stops: vec![],
|
||||
}),
|
||||
transform: Transformation::identity(),
|
||||
locations: Locations {
|
||||
gradient_direction,
|
||||
color_stops_size,
|
||||
color_stops,
|
||||
transform: transform_location,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
//! Display rendering results on windows.
|
||||
mod compositor;
|
||||
|
||||
pub use compositor::Compositor;
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
use crate::{Backend, Color, Error, Renderer, Settings, Viewport};
|
||||
|
||||
use glow::HasContext;
|
||||
use iced_graphics::{compositor, Antialiasing, Size};
|
||||
|
||||
use core::ffi::c_void;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// A window graphics backend for iced powered by `glow`.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Compositor<Theme> {
|
||||
gl: glow::Context,
|
||||
theme: PhantomData<Theme>,
|
||||
}
|
||||
|
||||
impl<Theme> iced_graphics::window::GLCompositor for Compositor<Theme> {
|
||||
type Settings = Settings;
|
||||
type Renderer = Renderer<Theme>;
|
||||
|
||||
unsafe fn new(
|
||||
settings: Self::Settings,
|
||||
loader_function: impl FnMut(&str) -> *const c_void,
|
||||
) -> Result<(Self, Self::Renderer), Error> {
|
||||
let gl = glow::Context::from_loader_function(loader_function);
|
||||
|
||||
log::info!("{:#?}", settings);
|
||||
|
||||
let version = gl.version();
|
||||
log::info!(
|
||||
"OpenGL version: {:?} (Embedded: {})",
|
||||
version,
|
||||
version.is_embedded
|
||||
);
|
||||
|
||||
let renderer = gl.get_parameter_string(glow::RENDERER);
|
||||
log::info!("Renderer: {}", renderer);
|
||||
|
||||
// Enable auto-conversion from/to sRGB
|
||||
gl.enable(glow::FRAMEBUFFER_SRGB);
|
||||
|
||||
// Enable alpha blending
|
||||
gl.enable(glow::BLEND);
|
||||
gl.blend_func_separate(
|
||||
glow::SRC_ALPHA,
|
||||
glow::ONE_MINUS_SRC_ALPHA,
|
||||
glow::ONE,
|
||||
glow::ONE_MINUS_SRC_ALPHA,
|
||||
);
|
||||
|
||||
// Disable multisampling by default
|
||||
gl.disable(glow::MULTISAMPLE);
|
||||
|
||||
let renderer = Renderer::new(Backend::new(&gl, settings));
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
gl,
|
||||
theme: PhantomData,
|
||||
},
|
||||
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 fetch_information(&self) -> compositor::Information {
|
||||
let adapter = unsafe { self.gl.get_parameter_string(glow::RENDERER) };
|
||||
|
||||
compositor::Information {
|
||||
backend: format!("{:?}", self.gl.version()),
|
||||
adapter,
|
||||
}
|
||||
}
|
||||
|
||||
fn present<T: AsRef<str>>(
|
||||
&mut self,
|
||||
renderer: &mut Self::Renderer,
|
||||
viewport: &Viewport,
|
||||
color: Color,
|
||||
overlay: &[T],
|
||||
) {
|
||||
let gl = &self.gl;
|
||||
|
||||
let [r, g, b, a] = color.into_linear();
|
||||
|
||||
unsafe {
|
||||
gl.clear_color(r, g, b, a);
|
||||
gl.clear(glow::COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
renderer.with_primitives(|backend, primitive| {
|
||||
backend.present(gl, primitive, viewport, overlay);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
[package]
|
||||
name = "iced_glutin"
|
||||
version = "0.7.0"
|
||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A glutin runtime for Iced"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/iced-rs/iced"
|
||||
documentation = "https://docs.rs/iced_glutin"
|
||||
keywords = ["gui", "ui", "graphics", "interface", "widgets"]
|
||||
categories = ["gui"]
|
||||
|
||||
[features]
|
||||
trace = ["iced_winit/trace"]
|
||||
debug = ["iced_winit/debug"]
|
||||
system = ["iced_winit/system"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
|
||||
[dependencies.glutin]
|
||||
version = "0.29"
|
||||
git = "https://github.com/iced-rs/glutin"
|
||||
rev = "da8d291486b4c9bec12487a46c119c4b1d386abf"
|
||||
|
||||
[dependencies.iced_native]
|
||||
version = "0.9"
|
||||
path = "../native"
|
||||
|
||||
[dependencies.iced_winit]
|
||||
version = "0.8"
|
||||
path = "../winit"
|
||||
features = ["application"]
|
||||
|
||||
[dependencies.iced_graphics]
|
||||
version = "0.7"
|
||||
path = "../graphics"
|
||||
features = ["opengl"]
|
||||
|
||||
[dependencies.tracing]
|
||||
version = "0.1.6"
|
||||
optional = true
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
# `iced_glutin`
|
||||
[][documentation]
|
||||
[](https://crates.io/crates/iced_glutin)
|
||||
[](https://github.com/iced-rs/iced/blob/master/LICENSE)
|
||||
[](https://discord.gg/3xZJ65GAhd)
|
||||
|
||||
`iced_glutin` offers some convenient abstractions on top of [`iced_native`] to quickstart development when using [`glutin`].
|
||||
|
||||
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.
|
||||
|
||||
<p align="center">
|
||||
<img alt="The native target" src="../docs/graphs/native.png" width="80%">
|
||||
</p>
|
||||
|
||||
[documentation]: https://docs.rs/iced_glutin
|
||||
[`iced_native`]: ../native
|
||||
[`glutin`]: https://github.com/rust-windowing/glutin
|
||||
|
||||
## Installation
|
||||
Add `iced_glutin` as a dependency in your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
iced_glutin = "0.7"
|
||||
```
|
||||
|
||||
__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/iced-rs/iced/releases
|
||||
|
|
@ -1,508 +0,0 @@
|
|||
//! Create interactive, native cross-platform applications.
|
||||
use crate::mouse;
|
||||
use crate::{Error, Executor, Runtime};
|
||||
|
||||
pub use iced_winit::application::StyleSheet;
|
||||
pub use iced_winit::Application;
|
||||
|
||||
use iced_graphics::window;
|
||||
use iced_winit::application;
|
||||
use iced_winit::conversion;
|
||||
use iced_winit::futures;
|
||||
use iced_winit::futures::channel::mpsc;
|
||||
use iced_winit::renderer;
|
||||
use iced_winit::time::Instant;
|
||||
use iced_winit::user_interface;
|
||||
use iced_winit::{Clipboard, Command, Debug, Event, Proxy, Settings};
|
||||
|
||||
use glutin::window::Window;
|
||||
use std::mem::ManuallyDrop;
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing::{info_span, instrument::Instrument};
|
||||
|
||||
/// Runs an [`Application`] with an executor, compositor, and the provided
|
||||
/// settings.
|
||||
pub fn run<A, E, C>(
|
||||
settings: Settings<A::Flags>,
|
||||
compositor_settings: C::Settings,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
A: Application + 'static,
|
||||
E: Executor + 'static,
|
||||
C: window::GLCompositor<Renderer = A::Renderer> + 'static,
|
||||
<A::Renderer as iced_native::Renderer>::Theme: StyleSheet,
|
||||
{
|
||||
use futures::task;
|
||||
use futures::Future;
|
||||
use glutin::event_loop::EventLoopBuilder;
|
||||
use glutin::platform::run_return::EventLoopExtRunReturn;
|
||||
use glutin::ContextBuilder;
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
let _guard = iced_winit::Profiler::init();
|
||||
|
||||
let mut debug = Debug::new();
|
||||
debug.startup_started();
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Application::Glutin", "RUN").entered();
|
||||
|
||||
let mut event_loop = EventLoopBuilder::with_user_event().build();
|
||||
let proxy = event_loop.create_proxy();
|
||||
|
||||
let runtime = {
|
||||
let executor = E::new().map_err(Error::ExecutorCreationFailed)?;
|
||||
let proxy = Proxy::new(event_loop.create_proxy());
|
||||
|
||||
Runtime::new(executor, proxy)
|
||||
};
|
||||
|
||||
let (application, init_command) = {
|
||||
let flags = settings.flags;
|
||||
|
||||
runtime.enter(|| A::new(flags))
|
||||
};
|
||||
|
||||
let context = {
|
||||
let builder = settings.window.into_builder(
|
||||
&application.title(),
|
||||
event_loop.primary_monitor(),
|
||||
settings.id,
|
||||
);
|
||||
|
||||
log::debug!("Window builder: {:#?}", builder);
|
||||
|
||||
let opengl_builder = ContextBuilder::new()
|
||||
.with_vsync(true)
|
||||
.with_multisampling(C::sample_count(&compositor_settings) as u16);
|
||||
|
||||
let opengles_builder = opengl_builder.clone().with_gl(
|
||||
glutin::GlRequest::Specific(glutin::Api::OpenGlEs, (2, 0)),
|
||||
);
|
||||
|
||||
let (first_builder, second_builder) = if settings.try_opengles_first {
|
||||
(opengles_builder, opengl_builder)
|
||||
} else {
|
||||
(opengl_builder, opengles_builder)
|
||||
};
|
||||
|
||||
log::info!("Trying first builder: {:#?}", first_builder);
|
||||
|
||||
let context = first_builder
|
||||
.build_windowed(builder.clone(), &event_loop)
|
||||
.or_else(|_| {
|
||||
log::info!("Trying second builder: {:#?}", second_builder);
|
||||
second_builder.build_windowed(builder, &event_loop)
|
||||
})
|
||||
.map_err(|error| {
|
||||
use glutin::CreationError;
|
||||
use iced_graphics::Error as ContextError;
|
||||
|
||||
match error {
|
||||
CreationError::Window(error) => {
|
||||
Error::WindowCreationFailed(error)
|
||||
}
|
||||
CreationError::OpenGlVersionNotSupported => {
|
||||
Error::GraphicsCreationFailed(
|
||||
ContextError::VersionNotSupported,
|
||||
)
|
||||
}
|
||||
CreationError::NoAvailablePixelFormat => {
|
||||
Error::GraphicsCreationFailed(
|
||||
ContextError::NoAvailablePixelFormat,
|
||||
)
|
||||
}
|
||||
error => Error::GraphicsCreationFailed(
|
||||
ContextError::BackendError(error.to_string()),
|
||||
),
|
||||
}
|
||||
})?;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
context.make_current().expect("Make OpenGL context current")
|
||||
}
|
||||
};
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
let (compositor, renderer) = unsafe {
|
||||
C::new(compositor_settings, |address| {
|
||||
context.get_proc_address(address)
|
||||
})?
|
||||
};
|
||||
|
||||
let (mut event_sender, event_receiver) = mpsc::unbounded();
|
||||
let (control_sender, mut control_receiver) = mpsc::unbounded();
|
||||
|
||||
let mut instance = Box::pin({
|
||||
let run_instance = run_instance::<A, E, C>(
|
||||
application,
|
||||
compositor,
|
||||
renderer,
|
||||
runtime,
|
||||
proxy,
|
||||
debug,
|
||||
event_receiver,
|
||||
control_sender,
|
||||
context,
|
||||
init_command,
|
||||
settings.exit_on_close_request,
|
||||
);
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
let run_instance =
|
||||
run_instance.instrument(info_span!("Application", "LOOP"));
|
||||
|
||||
run_instance
|
||||
});
|
||||
|
||||
let mut context = task::Context::from_waker(task::noop_waker_ref());
|
||||
|
||||
let _ = event_loop.run_return(move |event, _, control_flow| {
|
||||
use glutin::event_loop::ControlFlow;
|
||||
|
||||
if let ControlFlow::ExitWithCode(_) = control_flow {
|
||||
return;
|
||||
}
|
||||
|
||||
let event = match event {
|
||||
glutin::event::Event::WindowEvent {
|
||||
event:
|
||||
glutin::event::WindowEvent::ScaleFactorChanged {
|
||||
new_inner_size,
|
||||
..
|
||||
},
|
||||
window_id,
|
||||
} => Some(glutin::event::Event::WindowEvent {
|
||||
event: glutin::event::WindowEvent::Resized(*new_inner_size),
|
||||
window_id,
|
||||
}),
|
||||
_ => event.to_static(),
|
||||
};
|
||||
|
||||
if let Some(event) = event {
|
||||
event_sender.start_send(event).expect("Send event");
|
||||
|
||||
let poll = instance.as_mut().poll(&mut context);
|
||||
|
||||
match poll {
|
||||
task::Poll::Pending => {
|
||||
if let Ok(Some(flow)) = control_receiver.try_next() {
|
||||
*control_flow = flow;
|
||||
}
|
||||
}
|
||||
task::Poll::Ready(_) => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_instance<A, E, C>(
|
||||
mut application: A,
|
||||
mut compositor: C,
|
||||
mut renderer: A::Renderer,
|
||||
mut runtime: Runtime<E, Proxy<A::Message>, A::Message>,
|
||||
mut proxy: glutin::event_loop::EventLoopProxy<A::Message>,
|
||||
mut debug: Debug,
|
||||
mut event_receiver: mpsc::UnboundedReceiver<
|
||||
glutin::event::Event<'_, A::Message>,
|
||||
>,
|
||||
mut control_sender: mpsc::UnboundedSender<glutin::event_loop::ControlFlow>,
|
||||
mut context: glutin::ContextWrapper<glutin::PossiblyCurrent, Window>,
|
||||
init_command: Command<A::Message>,
|
||||
exit_on_close_request: bool,
|
||||
) where
|
||||
A: Application + 'static,
|
||||
E: Executor + 'static,
|
||||
C: window::GLCompositor<Renderer = A::Renderer> + 'static,
|
||||
<A::Renderer as iced_native::Renderer>::Theme: StyleSheet,
|
||||
{
|
||||
use glutin::event;
|
||||
use glutin::event_loop::ControlFlow;
|
||||
use iced_winit::futures::stream::StreamExt;
|
||||
|
||||
let mut clipboard = Clipboard::connect(context.window());
|
||||
let mut cache = user_interface::Cache::default();
|
||||
let mut state = application::State::new(&application, context.window());
|
||||
let mut viewport_version = state.viewport_version();
|
||||
let mut should_exit = false;
|
||||
|
||||
application::run_command(
|
||||
&application,
|
||||
&mut cache,
|
||||
&state,
|
||||
&mut renderer,
|
||||
init_command,
|
||||
&mut runtime,
|
||||
&mut clipboard,
|
||||
&mut should_exit,
|
||||
&mut proxy,
|
||||
&mut debug,
|
||||
context.window(),
|
||||
|| compositor.fetch_information(),
|
||||
);
|
||||
runtime.track(application.subscription());
|
||||
|
||||
let mut user_interface =
|
||||
ManuallyDrop::new(application::build_user_interface(
|
||||
&application,
|
||||
user_interface::Cache::default(),
|
||||
&mut renderer,
|
||||
state.logical_size(),
|
||||
&mut debug,
|
||||
));
|
||||
|
||||
let mut mouse_interaction = mouse::Interaction::default();
|
||||
let mut events = Vec::new();
|
||||
let mut messages = Vec::new();
|
||||
let mut redraw_pending = false;
|
||||
|
||||
debug.startup_finished();
|
||||
|
||||
while let Some(event) = event_receiver.next().await {
|
||||
match event {
|
||||
event::Event::NewEvents(start_cause) => {
|
||||
redraw_pending = matches!(
|
||||
start_cause,
|
||||
event::StartCause::Init
|
||||
| event::StartCause::Poll
|
||||
| event::StartCause::ResumeTimeReached { .. }
|
||||
);
|
||||
}
|
||||
event::Event::MainEventsCleared => {
|
||||
if !redraw_pending && events.is_empty() && messages.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
debug.event_processing_started();
|
||||
|
||||
let (interface_state, statuses) = user_interface.update(
|
||||
&events,
|
||||
state.cursor_position(),
|
||||
&mut renderer,
|
||||
&mut clipboard,
|
||||
&mut messages,
|
||||
);
|
||||
|
||||
debug.event_processing_finished();
|
||||
|
||||
for event in events.drain(..).zip(statuses.into_iter()) {
|
||||
runtime.broadcast(event);
|
||||
}
|
||||
|
||||
if !messages.is_empty()
|
||||
|| matches!(
|
||||
interface_state,
|
||||
user_interface::State::Outdated
|
||||
)
|
||||
{
|
||||
let mut cache =
|
||||
ManuallyDrop::into_inner(user_interface).into_cache();
|
||||
|
||||
// Update application
|
||||
application::update(
|
||||
&mut application,
|
||||
&mut cache,
|
||||
&state,
|
||||
&mut renderer,
|
||||
&mut runtime,
|
||||
&mut clipboard,
|
||||
&mut should_exit,
|
||||
&mut proxy,
|
||||
&mut debug,
|
||||
&mut messages,
|
||||
context.window(),
|
||||
|| compositor.fetch_information(),
|
||||
);
|
||||
|
||||
// Update window
|
||||
state.synchronize(&application, context.window());
|
||||
|
||||
user_interface =
|
||||
ManuallyDrop::new(application::build_user_interface(
|
||||
&application,
|
||||
cache,
|
||||
&mut renderer,
|
||||
state.logical_size(),
|
||||
&mut debug,
|
||||
));
|
||||
|
||||
if should_exit {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Avoid redrawing all the time by forcing widgets to
|
||||
// request redraws on state changes
|
||||
//
|
||||
// Then, we can use the `interface_state` here to decide if a redraw
|
||||
// is needed right away, or simply wait until a specific time.
|
||||
let redraw_event = Event::Window(
|
||||
crate::window::Event::RedrawRequested(Instant::now()),
|
||||
);
|
||||
|
||||
let (interface_state, _) = user_interface.update(
|
||||
&[redraw_event.clone()],
|
||||
state.cursor_position(),
|
||||
&mut renderer,
|
||||
&mut clipboard,
|
||||
&mut messages,
|
||||
);
|
||||
|
||||
debug.draw_started();
|
||||
let new_mouse_interaction = user_interface.draw(
|
||||
&mut renderer,
|
||||
state.theme(),
|
||||
&renderer::Style {
|
||||
text_color: state.text_color(),
|
||||
},
|
||||
state.cursor_position(),
|
||||
);
|
||||
debug.draw_finished();
|
||||
|
||||
if new_mouse_interaction != mouse_interaction {
|
||||
context.window().set_cursor_icon(
|
||||
conversion::mouse_interaction(new_mouse_interaction),
|
||||
);
|
||||
|
||||
mouse_interaction = new_mouse_interaction;
|
||||
}
|
||||
|
||||
context.window().request_redraw();
|
||||
runtime
|
||||
.broadcast((redraw_event, crate::event::Status::Ignored));
|
||||
|
||||
let _ = control_sender.start_send(match interface_state {
|
||||
user_interface::State::Updated {
|
||||
redraw_request: Some(redraw_request),
|
||||
} => match redraw_request {
|
||||
crate::window::RedrawRequest::NextFrame => {
|
||||
ControlFlow::Poll
|
||||
}
|
||||
crate::window::RedrawRequest::At(at) => {
|
||||
ControlFlow::WaitUntil(at)
|
||||
}
|
||||
},
|
||||
_ => ControlFlow::Wait,
|
||||
});
|
||||
|
||||
redraw_pending = false;
|
||||
}
|
||||
event::Event::PlatformSpecific(event::PlatformSpecific::MacOS(
|
||||
event::MacOS::ReceivedUrl(url),
|
||||
)) => {
|
||||
use iced_native::event;
|
||||
events.push(iced_native::Event::PlatformSpecific(
|
||||
event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl(
|
||||
url,
|
||||
)),
|
||||
));
|
||||
}
|
||||
event::Event::UserEvent(message) => {
|
||||
messages.push(message);
|
||||
}
|
||||
event::Event::RedrawRequested(_) => {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Application", "FRAME").entered();
|
||||
|
||||
debug.render_started();
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
if !context.is_current() {
|
||||
context = context
|
||||
.make_current()
|
||||
.expect("Make OpenGL context current");
|
||||
}
|
||||
}
|
||||
|
||||
let current_viewport_version = state.viewport_version();
|
||||
|
||||
if viewport_version != current_viewport_version {
|
||||
let physical_size = state.physical_size();
|
||||
let logical_size = state.logical_size();
|
||||
|
||||
debug.layout_started();
|
||||
user_interface = ManuallyDrop::new(
|
||||
ManuallyDrop::into_inner(user_interface)
|
||||
.relayout(logical_size, &mut renderer),
|
||||
);
|
||||
debug.layout_finished();
|
||||
|
||||
debug.draw_started();
|
||||
let new_mouse_interaction = user_interface.draw(
|
||||
&mut renderer,
|
||||
state.theme(),
|
||||
&renderer::Style {
|
||||
text_color: state.text_color(),
|
||||
},
|
||||
state.cursor_position(),
|
||||
);
|
||||
debug.draw_finished();
|
||||
|
||||
if new_mouse_interaction != mouse_interaction {
|
||||
context.window().set_cursor_icon(
|
||||
conversion::mouse_interaction(
|
||||
new_mouse_interaction,
|
||||
),
|
||||
);
|
||||
|
||||
mouse_interaction = new_mouse_interaction;
|
||||
}
|
||||
|
||||
context.resize(glutin::dpi::PhysicalSize::new(
|
||||
physical_size.width,
|
||||
physical_size.height,
|
||||
));
|
||||
|
||||
compositor.resize_viewport(physical_size);
|
||||
|
||||
viewport_version = current_viewport_version;
|
||||
}
|
||||
|
||||
compositor.present(
|
||||
&mut renderer,
|
||||
state.viewport(),
|
||||
state.background_color(),
|
||||
&debug.overlay(),
|
||||
);
|
||||
|
||||
context.swap_buffers().expect("Swap buffers");
|
||||
|
||||
debug.render_finished();
|
||||
|
||||
// TODO: Handle animations!
|
||||
// Maybe we can use `ControlFlow::WaitUntil` for this.
|
||||
}
|
||||
event::Event::WindowEvent {
|
||||
event: window_event,
|
||||
..
|
||||
} => {
|
||||
if application::requests_exit(&window_event, state.modifiers())
|
||||
&& exit_on_close_request
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
state.update(context.window(), &window_event, &mut debug);
|
||||
|
||||
if let Some(event) = conversion::window_event(
|
||||
&window_event,
|
||||
state.scale_factor(),
|
||||
state.modifiers(),
|
||||
) {
|
||||
events.push(event);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Manually drop the user interface
|
||||
drop(ManuallyDrop::into_inner(user_interface));
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
//! A windowing shell for [`iced`], on top of [`glutin`].
|
||||
//!
|
||||
//! 
|
||||
//!
|
||||
//! [`iced`]: https://github.com/iced-rs/iced
|
||||
//! [`glutin`]: https://github.com/rust-windowing/glutin
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
|
||||
)]
|
||||
#![deny(
|
||||
missing_docs,
|
||||
missing_debug_implementations,
|
||||
unsafe_code,
|
||||
unused_results,
|
||||
clippy::extra_unused_lifetimes,
|
||||
clippy::from_over_into,
|
||||
clippy::needless_borrow,
|
||||
clippy::new_without_default,
|
||||
clippy::useless_conversion
|
||||
)]
|
||||
#![forbid(rust_2018_idioms)]
|
||||
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
pub use glutin;
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use iced_winit::*;
|
||||
|
||||
pub mod application;
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use application::Application;
|
||||
|
|
@ -26,9 +26,6 @@ dds = ["image_rs/dds"]
|
|||
farbfeld = ["image_rs/farbfeld"]
|
||||
canvas = ["lyon"]
|
||||
qr_code = ["qrcode", "canvas"]
|
||||
font-source = ["font-kit"]
|
||||
font-fallback = []
|
||||
font-icons = []
|
||||
opengl = []
|
||||
image_rs = ["kamadak-exif"]
|
||||
|
||||
|
|
@ -60,10 +57,6 @@ version = "0.12"
|
|||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.font-kit]
|
||||
version = "0.10"
|
||||
optional = true
|
||||
|
||||
[dependencies.image_rs]
|
||||
version = "0.24"
|
||||
package = "image"
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,93 +0,0 @@
|
|||
Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
|
@ -4,6 +4,8 @@ use iced_native::svg;
|
|||
use iced_native::text;
|
||||
use iced_native::{Font, Point, Size};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// The graphics backend of a [`Renderer`].
|
||||
///
|
||||
/// [`Renderer`]: crate::Renderer
|
||||
|
|
@ -31,6 +33,9 @@ pub trait Text {
|
|||
/// [`ICON_FONT`]: Self::ICON_FONT
|
||||
const ARROW_DOWN_ICON: char;
|
||||
|
||||
/// Returns the default [`Font`].
|
||||
fn default_font(&self) -> Font;
|
||||
|
||||
/// Returns the default size of text.
|
||||
fn default_size(&self) -> f32;
|
||||
|
||||
|
|
@ -61,6 +66,9 @@ pub trait Text {
|
|||
point: Point,
|
||||
nearest_only: bool,
|
||||
) -> Option<text::Hit>;
|
||||
|
||||
/// Loads a [`Font`] from its bytes.
|
||||
fn load_font(&mut self, font: Cow<'static, [u8]>);
|
||||
}
|
||||
|
||||
/// A graphics backend that supports image rendering.
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
//! 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.
|
||||
#[cfg(feature = "font-icons")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "font-icons")))]
|
||||
pub const CHECKMARK_ICON: char = '\u{F00C}';
|
||||
|
||||
/// The `char` representing a ▼ icon in the built-in [`ICONS`] font.
|
||||
#[cfg(feature = "font-icons")]
|
||||
pub const ARROW_DOWN_ICON: char = '\u{E800}';
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
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`].
|
||||
pub fn new() -> Self {
|
||||
Source {
|
||||
raw: font_kit::source::SystemSource::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds and loads a font matching the set of provided family priorities.
|
||||
pub fn load(&self, families: &[Family]) -> Result<Vec<u8>, LoadError> {
|
||||
let font = self.raw.select_best_match(
|
||||
families,
|
||||
&font_kit::properties::Properties::default(),
|
||||
)?;
|
||||
|
||||
match font {
|
||||
font_kit::handle::Handle::Path { path, .. } => {
|
||||
use std::io::Read;
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let mut reader = std::fs::File::open(path).expect("Read font");
|
||||
let _ = reader.read_to_end(&mut buf);
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
font_kit::handle::Handle::Memory { bytes, .. } => {
|
||||
Ok(bytes.as_ref().clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Source {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,8 @@ pub use text::Text;
|
|||
|
||||
use crate::alignment;
|
||||
use crate::{
|
||||
Background, Font, Point, Primitive, Rectangle, Size, Vector, Viewport,
|
||||
Background, Color, Font, Point, Primitive, Rectangle, Size, Vector,
|
||||
Viewport,
|
||||
};
|
||||
|
||||
/// A group of primitives that should be clipped together.
|
||||
|
|
@ -60,9 +61,9 @@ impl<'a> Layer<'a> {
|
|||
Point::new(11.0, 11.0 + 25.0 * i as f32),
|
||||
Size::INFINITY,
|
||||
),
|
||||
color: [0.9, 0.9, 0.9, 1.0],
|
||||
color: Color::new(0.9, 0.9, 0.9, 1.0),
|
||||
size: 20.0,
|
||||
font: Font::Default,
|
||||
font: Font::Monospace,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
};
|
||||
|
|
@ -71,7 +72,7 @@ impl<'a> Layer<'a> {
|
|||
|
||||
overlay.text.push(Text {
|
||||
bounds: text.bounds + Vector::new(-1.0, -1.0),
|
||||
color: [0.0, 0.0, 0.0, 1.0],
|
||||
color: Color::BLACK,
|
||||
..text
|
||||
});
|
||||
}
|
||||
|
|
@ -136,7 +137,7 @@ impl<'a> Layer<'a> {
|
|||
content,
|
||||
bounds: *bounds + translation,
|
||||
size: *size,
|
||||
color: color.into_linear(),
|
||||
color: *color,
|
||||
font: *font,
|
||||
horizontal_alignment: *horizontal_alignment,
|
||||
vertical_alignment: *vertical_alignment,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{alignment, Font, Rectangle};
|
||||
use crate::{alignment, Color, Font, Rectangle};
|
||||
|
||||
/// A paragraph of text.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -10,7 +10,7 @@ pub struct Text<'a> {
|
|||
pub bounds: Rectangle,
|
||||
|
||||
/// The color of the [`Text`], in __linear RGB_.
|
||||
pub color: [f32; 4],
|
||||
pub color: Color,
|
||||
|
||||
/// The size of the [`Text`].
|
||||
pub size: f32,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ mod transformation;
|
|||
mod viewport;
|
||||
|
||||
pub mod backend;
|
||||
pub mod font;
|
||||
pub mod gradient;
|
||||
pub mod image;
|
||||
pub mod layer;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use iced_native::{Background, Color, Element, Font, Point, Rectangle, Size};
|
|||
|
||||
pub use iced_native::renderer::Style;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// A backend-agnostic renderer that supports all the built-in widgets.
|
||||
|
|
@ -130,6 +131,10 @@ where
|
|||
const CHECKMARK_ICON: char = B::CHECKMARK_ICON;
|
||||
const ARROW_DOWN_ICON: char = B::ARROW_DOWN_ICON;
|
||||
|
||||
fn default_font(&self) -> Self::Font {
|
||||
self.backend().default_font()
|
||||
}
|
||||
|
||||
fn default_size(&self) -> f32 {
|
||||
self.backend().default_size()
|
||||
}
|
||||
|
|
@ -163,6 +168,10 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
||||
self.backend.load_font(bytes);
|
||||
}
|
||||
|
||||
fn fill_text(&mut self, text: Text<'_, Self::Font>) {
|
||||
self.primitives.push(Primitive::Text {
|
||||
content: text.content.to_string(),
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ impl Default for Text {
|
|||
position: Point::ORIGIN,
|
||||
color: Color::BLACK,
|
||||
size: 16.0,
|
||||
font: Font::Default,
|
||||
font: Font::SansSerif,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
use crate::clipboard;
|
||||
use crate::font;
|
||||
use crate::system;
|
||||
use crate::widget;
|
||||
use crate::window;
|
||||
|
||||
use iced_futures::MaybeSend;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
||||
/// An action that a [`Command`] can perform.
|
||||
|
|
@ -27,6 +29,15 @@ pub enum Action<T> {
|
|||
|
||||
/// Run a widget action.
|
||||
Widget(widget::Action<T>),
|
||||
|
||||
/// Load a font from its bytes.
|
||||
LoadFont {
|
||||
/// The bytes of the font to load.
|
||||
bytes: Cow<'static, [u8]>,
|
||||
|
||||
/// The message to produce when the font has been loaded.
|
||||
tagger: Box<dyn Fn(Result<(), font::Error>) -> T>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T> Action<T> {
|
||||
|
|
@ -49,6 +60,10 @@ impl<T> Action<T> {
|
|||
Self::Window(window) => Action::Window(window.map(f)),
|
||||
Self::System(system) => Action::System(system.map(f)),
|
||||
Self::Widget(widget) => Action::Widget(widget.map(f)),
|
||||
Self::LoadFont { bytes, tagger } => Action::LoadFont {
|
||||
bytes,
|
||||
tagger: Box::new(move |result| f(tagger(result))),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -63,6 +78,7 @@ impl<T> fmt::Debug for Action<T> {
|
|||
Self::Window(action) => write!(f, "Action::Window({action:?})"),
|
||||
Self::System(action) => write!(f, "Action::System({action:?})"),
|
||||
Self::Widget(_action) => write!(f, "Action::Widget"),
|
||||
Self::LoadFont { .. } => write!(f, "Action::LoadFont"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
native/src/font.rs
Normal file
19
native/src/font.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
//! Load and use fonts.
|
||||
pub use iced_core::font::*;
|
||||
|
||||
use crate::command::{self, Command};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// An error while loading a font.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Error {}
|
||||
|
||||
/// Load a font from its bytes.
|
||||
pub fn load(
|
||||
bytes: impl Into<Cow<'static, [u8]>>,
|
||||
) -> Command<Result<(), Error>> {
|
||||
Command::single(command::Action::LoadFont {
|
||||
bytes: bytes.into(),
|
||||
tagger: Box::new(std::convert::identity),
|
||||
})
|
||||
}
|
||||
|
|
@ -47,6 +47,7 @@
|
|||
pub mod clipboard;
|
||||
pub mod command;
|
||||
pub mod event;
|
||||
pub mod font;
|
||||
pub mod image;
|
||||
pub mod keyboard;
|
||||
pub mod layout;
|
||||
|
|
@ -80,8 +81,8 @@ mod debug;
|
|||
pub use iced_core::alignment;
|
||||
pub use iced_core::time;
|
||||
pub use iced_core::{
|
||||
color, Alignment, Background, Color, ContentFit, Font, Length, Padding,
|
||||
Pixels, Point, Rectangle, Size, Vector,
|
||||
color, Alignment, Background, Color, ContentFit, Length, Padding, Pixels,
|
||||
Point, Rectangle, Size, Vector,
|
||||
};
|
||||
pub use iced_futures::{executor, futures};
|
||||
pub use iced_style::application;
|
||||
|
|
@ -95,6 +96,7 @@ pub use command::Command;
|
|||
pub use debug::Debug;
|
||||
pub use element::Element;
|
||||
pub use event::Event;
|
||||
pub use font::Font;
|
||||
pub use hasher::Hasher;
|
||||
pub use layout::Layout;
|
||||
pub use overlay::Overlay;
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ where
|
|||
width: f32,
|
||||
padding: Padding,
|
||||
text_size: Option<f32>,
|
||||
font: Renderer::Font,
|
||||
font: Option<Renderer::Font>,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ where
|
|||
width: 0.0,
|
||||
padding: Padding::ZERO,
|
||||
text_size: None,
|
||||
font: Default::default(),
|
||||
font: None,
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -82,8 +82,8 @@ where
|
|||
}
|
||||
|
||||
/// Sets the font of the [`Menu`].
|
||||
pub fn font(mut self, font: Renderer::Font) -> Self {
|
||||
self.font = font;
|
||||
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
||||
self.font = Some(font.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -311,7 +311,7 @@ where
|
|||
last_selection: &'a mut Option<T>,
|
||||
padding: Padding,
|
||||
text_size: Option<f32>,
|
||||
font: Renderer::Font,
|
||||
font: Option<Renderer::Font>,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
}
|
||||
|
||||
|
|
@ -344,7 +344,7 @@ where
|
|||
let size = {
|
||||
let intrinsic = Size::new(
|
||||
0.0,
|
||||
(text_size + self.padding.vertical())
|
||||
(text_size * 1.2 + self.padding.vertical())
|
||||
* self.options.len() as f32,
|
||||
);
|
||||
|
||||
|
|
@ -386,7 +386,7 @@ where
|
|||
|
||||
*self.hovered_option = Some(
|
||||
((cursor_position.y - bounds.y)
|
||||
/ (text_size + self.padding.vertical()))
|
||||
/ (text_size * 1.2 + self.padding.vertical()))
|
||||
as usize,
|
||||
);
|
||||
}
|
||||
|
|
@ -401,7 +401,7 @@ where
|
|||
|
||||
*self.hovered_option = Some(
|
||||
((cursor_position.y - bounds.y)
|
||||
/ (text_size + self.padding.vertical()))
|
||||
/ (text_size * 1.2 + self.padding.vertical()))
|
||||
as usize,
|
||||
);
|
||||
|
||||
|
|
@ -450,7 +450,8 @@ where
|
|||
|
||||
let text_size =
|
||||
self.text_size.unwrap_or_else(|| renderer.default_size());
|
||||
let option_height = (text_size + self.padding.vertical()) as usize;
|
||||
let option_height =
|
||||
(text_size * 1.2 + self.padding.vertical()) as usize;
|
||||
|
||||
let offset = viewport.y - bounds.y;
|
||||
let start = (offset / option_height as f32) as usize;
|
||||
|
|
@ -467,7 +468,7 @@ where
|
|||
x: bounds.x,
|
||||
y: bounds.y + (option_height * i) as f32,
|
||||
width: bounds.width,
|
||||
height: text_size + self.padding.vertical(),
|
||||
height: text_size * 1.2 + self.padding.vertical(),
|
||||
};
|
||||
|
||||
if is_selected {
|
||||
|
|
@ -491,7 +492,7 @@ where
|
|||
..bounds
|
||||
},
|
||||
size: text_size,
|
||||
font: self.font.clone(),
|
||||
font: self.font.unwrap_or_else(|| renderer.default_font()),
|
||||
color: if is_selected {
|
||||
appearance.selected_text_color
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
//! Build interactive programs using The Elm Architecture.
|
||||
use crate::text;
|
||||
use crate::{Command, Element, Renderer};
|
||||
|
||||
mod state;
|
||||
|
|
@ -8,7 +9,7 @@ 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`].
|
||||
type Renderer: Renderer;
|
||||
type Renderer: Renderer + text::Renderer;
|
||||
|
||||
/// The type of __messages__ your [`Program`] will produce.
|
||||
type Message: std::fmt::Debug + Send;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ use crate::renderer::{self, Renderer};
|
|||
use crate::text::{self, Text};
|
||||
use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A renderer that does nothing.
|
||||
///
|
||||
/// It can be useful if you are writing tests!
|
||||
|
|
@ -40,14 +42,20 @@ impl Renderer for Null {
|
|||
impl text::Renderer for Null {
|
||||
type Font = Font;
|
||||
|
||||
const ICON_FONT: Font = Font::Default;
|
||||
const ICON_FONT: Font = Font::SansSerif;
|
||||
const CHECKMARK_ICON: char = '0';
|
||||
const ARROW_DOWN_ICON: char = '0';
|
||||
|
||||
fn default_size(&self) -> f32 {
|
||||
20.0
|
||||
fn default_font(&self) -> Self::Font {
|
||||
Font::SansSerif
|
||||
}
|
||||
|
||||
fn default_size(&self) -> f32 {
|
||||
16.0
|
||||
}
|
||||
|
||||
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
|
||||
|
||||
fn measure(
|
||||
&self,
|
||||
_content: &str,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
//! Draw and interact with text.
|
||||
use crate::alignment;
|
||||
use crate::{Color, Point, Rectangle, Size, Vector};
|
||||
use crate::{Color, Point, Rectangle, Size};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A paragraph.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -32,10 +34,6 @@ pub struct Text<'a, Font> {
|
|||
pub enum Hit {
|
||||
/// The point was within the bounds of the returned character index.
|
||||
CharOffset(usize),
|
||||
/// The provided point was not within the bounds of a glyph. The index
|
||||
/// of the character with the closest centeroid position is returned,
|
||||
/// as well as its delta.
|
||||
NearestCharOffset(usize, Vector),
|
||||
}
|
||||
|
||||
impl Hit {
|
||||
|
|
@ -43,13 +41,6 @@ impl Hit {
|
|||
pub fn cursor(self) -> usize {
|
||||
match self {
|
||||
Self::CharOffset(i) => i,
|
||||
Self::NearestCharOffset(i, delta) => {
|
||||
if delta.x > f32::EPSILON {
|
||||
i + 1
|
||||
} else {
|
||||
i
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +48,7 @@ impl Hit {
|
|||
/// A renderer capable of measuring and drawing [`Text`].
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// The font type used.
|
||||
type Font: Default + Clone;
|
||||
type Font: Copy;
|
||||
|
||||
/// The icon font of the backend.
|
||||
const ICON_FONT: Self::Font;
|
||||
|
|
@ -72,6 +63,9 @@ pub trait Renderer: crate::Renderer {
|
|||
/// [`ICON_FONT`]: Self::ICON_FONT
|
||||
const ARROW_DOWN_ICON: char;
|
||||
|
||||
/// Returns the default [`Self::Font`].
|
||||
fn default_font(&self) -> Self::Font;
|
||||
|
||||
/// Returns the default size of [`Text`].
|
||||
fn default_size(&self) -> f32;
|
||||
|
||||
|
|
@ -109,6 +103,9 @@ pub trait Renderer: crate::Renderer {
|
|||
nearest_only: bool,
|
||||
) -> Option<Hit>;
|
||||
|
||||
/// Loads a [`Self::Font`] from its bytes.
|
||||
fn load_font(&mut self, font: Cow<'static, [u8]>);
|
||||
|
||||
/// Draws the given [`Text`].
|
||||
fn fill_text(&mut self, text: Text<'_, Self::Font>);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ where
|
|||
size: f32,
|
||||
spacing: f32,
|
||||
text_size: Option<f32>,
|
||||
font: Renderer::Font,
|
||||
font: Option<Renderer::Font>,
|
||||
icon: Icon<Renderer::Font>,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ where
|
|||
size: Self::DEFAULT_SIZE,
|
||||
spacing: Self::DEFAULT_SPACING,
|
||||
text_size: None,
|
||||
font: Renderer::Font::default(),
|
||||
font: None,
|
||||
icon: Icon {
|
||||
font: Renderer::ICON_FONT,
|
||||
code_point: Renderer::CHECKMARK_ICON,
|
||||
|
|
@ -128,8 +128,8 @@ where
|
|||
/// Sets the [`Font`] of the text of the [`Checkbox`].
|
||||
///
|
||||
/// [`Font`]: crate::text::Renderer::Font
|
||||
pub fn font(mut self, font: Renderer::Font) -> Self {
|
||||
self.font = font;
|
||||
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
||||
self.font = Some(font.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -175,7 +175,7 @@ where
|
|||
.push(Row::new().width(self.size).height(self.size))
|
||||
.push(
|
||||
Text::new(&self.label)
|
||||
.font(self.font.clone())
|
||||
.font(self.font.unwrap_or_else(|| renderer.default_font()))
|
||||
.width(self.width)
|
||||
.size(
|
||||
self.text_size
|
||||
|
|
@ -267,12 +267,12 @@ where
|
|||
code_point,
|
||||
size,
|
||||
} = &self.icon;
|
||||
let size = size.map(f32::from).unwrap_or(bounds.height * 0.7);
|
||||
let size = size.unwrap_or(bounds.height * 0.7);
|
||||
|
||||
if self.is_checked {
|
||||
renderer.fill_text(text::Text {
|
||||
content: &code_point.to_string(),
|
||||
font: font.clone(),
|
||||
font: *font,
|
||||
size,
|
||||
bounds: Rectangle {
|
||||
x: bounds.center_x(),
|
||||
|
|
@ -295,7 +295,7 @@ where
|
|||
label_layout,
|
||||
&self.label,
|
||||
self.text_size,
|
||||
self.font.clone(),
|
||||
self.font,
|
||||
widget::text::Appearance {
|
||||
color: custom_style.text_color,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ where
|
|||
width: Length,
|
||||
padding: Padding,
|
||||
text_size: Option<f32>,
|
||||
font: Renderer::Font,
|
||||
font: Option<Renderer::Font>,
|
||||
handle: Handle<Renderer::Font>,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ where
|
|||
width: Length::Shrink,
|
||||
padding: Self::DEFAULT_PADDING,
|
||||
text_size: None,
|
||||
font: Default::default(),
|
||||
font: None,
|
||||
handle: Default::default(),
|
||||
style: Default::default(),
|
||||
}
|
||||
|
|
@ -101,8 +101,8 @@ where
|
|||
}
|
||||
|
||||
/// Sets the font of the [`PickList`].
|
||||
pub fn font(mut self, font: Renderer::Font) -> Self {
|
||||
self.font = font;
|
||||
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
||||
self.font = Some(font.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +163,7 @@ where
|
|||
self.width,
|
||||
self.padding,
|
||||
self.text_size,
|
||||
&self.font,
|
||||
self.font,
|
||||
self.placeholder.as_deref(),
|
||||
&self.options,
|
||||
)
|
||||
|
|
@ -212,6 +212,7 @@ where
|
|||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let font = self.font.unwrap_or_else(|| renderer.default_font());
|
||||
draw(
|
||||
renderer,
|
||||
theme,
|
||||
|
|
@ -219,7 +220,7 @@ where
|
|||
cursor_position,
|
||||
self.padding,
|
||||
self.text_size,
|
||||
&self.font,
|
||||
font,
|
||||
self.placeholder.as_deref(),
|
||||
self.selected.as_ref(),
|
||||
&self.handle,
|
||||
|
|
@ -232,7 +233,7 @@ where
|
|||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
let state = tree.state.downcast_mut::<State<T>>();
|
||||
|
||||
|
|
@ -241,7 +242,7 @@ where
|
|||
state,
|
||||
self.padding,
|
||||
self.text_size,
|
||||
self.font.clone(),
|
||||
self.font.unwrap_or_else(|| renderer.default_font()),
|
||||
&self.options,
|
||||
self.style.clone(),
|
||||
)
|
||||
|
|
@ -343,7 +344,7 @@ pub fn layout<Renderer, T>(
|
|||
width: Length,
|
||||
padding: Padding,
|
||||
text_size: Option<f32>,
|
||||
font: &Renderer::Font,
|
||||
font: Option<Renderer::Font>,
|
||||
placeholder: Option<&str>,
|
||||
options: &[T],
|
||||
) -> layout::Node
|
||||
|
|
@ -362,7 +363,7 @@ where
|
|||
let (width, _) = renderer.measure(
|
||||
label,
|
||||
text_size,
|
||||
font.clone(),
|
||||
font.unwrap_or_else(|| renderer.default_font()),
|
||||
Size::new(f32::INFINITY, f32::INFINITY),
|
||||
);
|
||||
|
||||
|
|
@ -384,7 +385,7 @@ where
|
|||
|
||||
let size = {
|
||||
let intrinsic =
|
||||
Size::new(max_width + text_size + padding.left, text_size);
|
||||
Size::new(max_width + text_size + padding.left, text_size * 1.2);
|
||||
|
||||
limits.resolve(intrinsic).pad(padding)
|
||||
};
|
||||
|
|
@ -560,7 +561,7 @@ pub fn draw<'a, T, Renderer>(
|
|||
cursor_position: Point,
|
||||
padding: Padding,
|
||||
text_size: Option<f32>,
|
||||
font: &Renderer::Font,
|
||||
font: Renderer::Font,
|
||||
placeholder: Option<&str>,
|
||||
selected: Option<&T>,
|
||||
handle: &Handle<Renderer::Font>,
|
||||
|
|
@ -599,12 +600,12 @@ pub fn draw<'a, T, Renderer>(
|
|||
font,
|
||||
code_point,
|
||||
size,
|
||||
}) => Some((font.clone(), *code_point, *size)),
|
||||
}) => Some((*font, *code_point, *size)),
|
||||
Handle::Dynamic { open, closed } => {
|
||||
if state().is_open {
|
||||
Some((open.font.clone(), open.code_point, open.size))
|
||||
Some((open.font, open.code_point, open.size))
|
||||
} else {
|
||||
Some((closed.font.clone(), closed.code_point, closed.size))
|
||||
Some((closed.font, closed.code_point, closed.size))
|
||||
}
|
||||
}
|
||||
Handle::None => None,
|
||||
|
|
@ -620,12 +621,12 @@ pub fn draw<'a, T, Renderer>(
|
|||
color: style.handle_color,
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + bounds.width - padding.horizontal(),
|
||||
y: bounds.center_y() - size / 2.0,
|
||||
height: size,
|
||||
y: bounds.center_y(),
|
||||
height: size * 1.2,
|
||||
..bounds
|
||||
},
|
||||
horizontal_alignment: alignment::Horizontal::Right,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -637,7 +638,7 @@ pub fn draw<'a, T, Renderer>(
|
|||
renderer.fill_text(Text {
|
||||
content: label,
|
||||
size: text_size,
|
||||
font: font.clone(),
|
||||
font,
|
||||
color: if is_selected {
|
||||
style.text_color
|
||||
} else {
|
||||
|
|
@ -645,12 +646,12 @@ pub fn draw<'a, T, Renderer>(
|
|||
},
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + padding.left,
|
||||
y: bounds.center_y() - text_size / 2.0,
|
||||
y: bounds.center_y(),
|
||||
width: bounds.width - padding.horizontal(),
|
||||
height: text_size,
|
||||
height: text_size * 1.2,
|
||||
},
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
vertical_alignment: alignment::Vertical::Center,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ where
|
|||
size: f32,
|
||||
spacing: f32,
|
||||
text_size: Option<f32>,
|
||||
font: Renderer::Font,
|
||||
font: Option<Renderer::Font>,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ where
|
|||
size: Self::DEFAULT_SIZE,
|
||||
spacing: Self::DEFAULT_SPACING, //15
|
||||
text_size: None,
|
||||
font: Default::default(),
|
||||
font: None,
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -125,8 +125,8 @@ where
|
|||
}
|
||||
|
||||
/// Sets the text font of the [`Radio`] button.
|
||||
pub fn font(mut self, font: Renderer::Font) -> Self {
|
||||
self.font = font;
|
||||
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
||||
self.font = Some(font.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -275,7 +275,7 @@ where
|
|||
label_layout,
|
||||
&self.label,
|
||||
self.text_size,
|
||||
self.font.clone(),
|
||||
self.font,
|
||||
widget::text::Appearance {
|
||||
color: custom_style.text_color,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ where
|
|||
height: Length,
|
||||
horizontal_alignment: alignment::Horizontal,
|
||||
vertical_alignment: alignment::Vertical,
|
||||
font: Renderer::Font,
|
||||
font: Option<Renderer::Font>,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ where
|
|||
Text {
|
||||
content: content.into(),
|
||||
size: None,
|
||||
font: Default::default(),
|
||||
font: None,
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
|
|
@ -70,7 +70,7 @@ where
|
|||
///
|
||||
/// [`Font`]: crate::text::Renderer::Font
|
||||
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
||||
self.font = font.into();
|
||||
self.font = Some(font.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -138,8 +138,12 @@ where
|
|||
|
||||
let bounds = limits.max();
|
||||
|
||||
let (width, height) =
|
||||
renderer.measure(&self.content, size, self.font.clone(), bounds);
|
||||
let (width, height) = renderer.measure(
|
||||
&self.content,
|
||||
size,
|
||||
self.font.unwrap_or_else(|| renderer.default_font()),
|
||||
bounds,
|
||||
);
|
||||
|
||||
let size = limits.resolve(Size::new(width, height));
|
||||
|
||||
|
|
@ -162,7 +166,7 @@ where
|
|||
layout,
|
||||
&self.content,
|
||||
self.size,
|
||||
self.font.clone(),
|
||||
self.font,
|
||||
theme.appearance(self.style),
|
||||
self.horizontal_alignment,
|
||||
self.vertical_alignment,
|
||||
|
|
@ -186,7 +190,7 @@ pub fn draw<Renderer>(
|
|||
layout: Layout<'_>,
|
||||
content: &str,
|
||||
size: Option<f32>,
|
||||
font: Renderer::Font,
|
||||
font: Option<Renderer::Font>,
|
||||
appearance: Appearance,
|
||||
horizontal_alignment: alignment::Horizontal,
|
||||
vertical_alignment: alignment::Vertical,
|
||||
|
|
@ -212,7 +216,7 @@ pub fn draw<Renderer>(
|
|||
size: size.unwrap_or_else(|| renderer.default_size()),
|
||||
bounds: Rectangle { x, y, ..bounds },
|
||||
color: appearance.color.unwrap_or(style.text_color),
|
||||
font,
|
||||
font: font.unwrap_or_else(|| renderer.default_font()),
|
||||
horizontal_alignment,
|
||||
vertical_alignment,
|
||||
});
|
||||
|
|
@ -242,7 +246,7 @@ where
|
|||
height: self.height,
|
||||
horizontal_alignment: self.horizontal_alignment,
|
||||
vertical_alignment: self.vertical_alignment,
|
||||
font: self.font.clone(),
|
||||
font: self.font,
|
||||
style: self.style,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ where
|
|||
placeholder: String,
|
||||
value: Value,
|
||||
is_secure: bool,
|
||||
font: Renderer::Font,
|
||||
font: Option<Renderer::Font>,
|
||||
width: Length,
|
||||
padding: Padding,
|
||||
size: Option<f32>,
|
||||
|
|
@ -92,7 +92,7 @@ where
|
|||
placeholder: String::from(placeholder),
|
||||
value: Value::new(value),
|
||||
is_secure: false,
|
||||
font: Default::default(),
|
||||
font: None,
|
||||
width: Length::Fill,
|
||||
padding: Padding::new(5.0),
|
||||
size: None,
|
||||
|
|
@ -129,7 +129,7 @@ where
|
|||
///
|
||||
/// [`Font`]: text::Renderer::Font
|
||||
pub fn font(mut self, font: Renderer::Font) -> Self {
|
||||
self.font = font;
|
||||
self.font = Some(font);
|
||||
self
|
||||
}
|
||||
/// Sets the width of the [`TextInput`].
|
||||
|
|
@ -188,7 +188,7 @@ where
|
|||
value.unwrap_or(&self.value),
|
||||
&self.placeholder,
|
||||
self.size,
|
||||
&self.font,
|
||||
self.font,
|
||||
self.is_secure,
|
||||
&self.style,
|
||||
)
|
||||
|
|
@ -258,7 +258,7 @@ where
|
|||
shell,
|
||||
&mut self.value,
|
||||
self.size,
|
||||
&self.font,
|
||||
self.font,
|
||||
self.is_secure,
|
||||
self.on_change.as_ref(),
|
||||
self.on_paste.as_deref(),
|
||||
|
|
@ -286,7 +286,7 @@ where
|
|||
&self.value,
|
||||
&self.placeholder,
|
||||
self.size,
|
||||
&self.font,
|
||||
self.font,
|
||||
self.is_secure,
|
||||
&self.style,
|
||||
)
|
||||
|
|
@ -385,9 +385,8 @@ where
|
|||
Renderer: text::Renderer,
|
||||
{
|
||||
let text_size = size.unwrap_or_else(|| renderer.default_size());
|
||||
|
||||
let padding = padding.fit(Size::ZERO, limits.max());
|
||||
let limits = limits.width(width).pad(padding).height(text_size);
|
||||
let limits = limits.width(width).pad(padding).height(text_size * 1.2);
|
||||
|
||||
let mut text = layout::Node::new(limits.resolve(Size::ZERO));
|
||||
text.move_to(Point::new(padding.left, padding.top));
|
||||
|
|
@ -406,7 +405,7 @@ pub fn update<'a, Message, Renderer>(
|
|||
shell: &mut Shell<'_, Message>,
|
||||
value: &mut Value,
|
||||
size: Option<f32>,
|
||||
font: &Renderer::Font,
|
||||
font: Option<Renderer::Font>,
|
||||
is_secure: bool,
|
||||
on_change: &dyn Fn(String) -> Message,
|
||||
on_paste: Option<&dyn Fn(String) -> Message>,
|
||||
|
|
@ -455,7 +454,7 @@ where
|
|||
find_cursor_position(
|
||||
renderer,
|
||||
text_layout.bounds(),
|
||||
font.clone(),
|
||||
font,
|
||||
size,
|
||||
&value,
|
||||
state,
|
||||
|
|
@ -483,7 +482,7 @@ where
|
|||
let position = find_cursor_position(
|
||||
renderer,
|
||||
text_layout.bounds(),
|
||||
font.clone(),
|
||||
font,
|
||||
size,
|
||||
value,
|
||||
state,
|
||||
|
|
@ -532,7 +531,7 @@ where
|
|||
let position = find_cursor_position(
|
||||
renderer,
|
||||
text_layout.bounds(),
|
||||
font.clone(),
|
||||
font,
|
||||
size,
|
||||
&value,
|
||||
state,
|
||||
|
|
@ -812,7 +811,7 @@ pub fn draw<Renderer>(
|
|||
value: &Value,
|
||||
placeholder: &str,
|
||||
size: Option<f32>,
|
||||
font: &Renderer::Font,
|
||||
font: Option<Renderer::Font>,
|
||||
is_secure: bool,
|
||||
style: &<Renderer::Theme as StyleSheet>::Style,
|
||||
) where
|
||||
|
|
@ -846,6 +845,7 @@ pub fn draw<Renderer>(
|
|||
);
|
||||
|
||||
let text = value.to_string();
|
||||
let font = font.unwrap_or_else(|| renderer.default_font());
|
||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
||||
|
||||
let (cursor, offset) = if let Some(focus) = &state.is_focused {
|
||||
|
|
@ -858,7 +858,7 @@ pub fn draw<Renderer>(
|
|||
value,
|
||||
size,
|
||||
position,
|
||||
font.clone(),
|
||||
font,
|
||||
);
|
||||
|
||||
let is_cursor_visible = ((focus.now - focus.updated_at)
|
||||
|
|
@ -899,7 +899,7 @@ pub fn draw<Renderer>(
|
|||
value,
|
||||
size,
|
||||
left,
|
||||
font.clone(),
|
||||
font,
|
||||
);
|
||||
|
||||
let (right_position, right_offset) =
|
||||
|
|
@ -909,7 +909,7 @@ pub fn draw<Renderer>(
|
|||
value,
|
||||
size,
|
||||
right,
|
||||
font.clone(),
|
||||
font,
|
||||
);
|
||||
|
||||
let width = right_position - left_position;
|
||||
|
|
@ -944,7 +944,7 @@ pub fn draw<Renderer>(
|
|||
let text_width = renderer.measure_width(
|
||||
if text.is_empty() { placeholder } else { &text },
|
||||
size,
|
||||
font.clone(),
|
||||
font,
|
||||
);
|
||||
|
||||
let render = |renderer: &mut Renderer| {
|
||||
|
|
@ -959,7 +959,7 @@ pub fn draw<Renderer>(
|
|||
} else {
|
||||
theme.value_color(style)
|
||||
},
|
||||
font: font.clone(),
|
||||
font,
|
||||
bounds: Rectangle {
|
||||
y: text_bounds.center_y(),
|
||||
width: f32::INFINITY,
|
||||
|
|
@ -1180,7 +1180,7 @@ where
|
|||
fn find_cursor_position<Renderer>(
|
||||
renderer: &Renderer,
|
||||
text_bounds: Rectangle,
|
||||
font: Renderer::Font,
|
||||
font: Option<Renderer::Font>,
|
||||
size: Option<f32>,
|
||||
value: &Value,
|
||||
state: &State,
|
||||
|
|
@ -1189,21 +1189,30 @@ fn find_cursor_position<Renderer>(
|
|||
where
|
||||
Renderer: text::Renderer,
|
||||
{
|
||||
let font = font.unwrap_or_else(|| renderer.default_font());
|
||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
||||
|
||||
let offset =
|
||||
offset(renderer, text_bounds, font.clone(), size, value, state);
|
||||
let offset = offset(renderer, text_bounds, font, size, value, state);
|
||||
let value = value.to_string();
|
||||
|
||||
renderer
|
||||
let char_offset = renderer
|
||||
.hit_test(
|
||||
&value.to_string(),
|
||||
&value,
|
||||
size,
|
||||
font,
|
||||
Size::INFINITY,
|
||||
Point::new(x + offset, text_bounds.height / 2.0),
|
||||
true,
|
||||
)
|
||||
.map(text::Hit::cursor)
|
||||
.map(text::Hit::cursor)?;
|
||||
|
||||
Some(
|
||||
unicode_segmentation::UnicodeSegmentation::graphemes(
|
||||
&value[..char_offset],
|
||||
true,
|
||||
)
|
||||
.count(),
|
||||
)
|
||||
}
|
||||
|
||||
const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ where
|
|||
text_size: Option<f32>,
|
||||
text_alignment: alignment::Horizontal,
|
||||
spacing: f32,
|
||||
font: Renderer::Font,
|
||||
font: Option<Renderer::Font>,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ where
|
|||
text_size: None,
|
||||
text_alignment: alignment::Horizontal::Left,
|
||||
spacing: 0.0,
|
||||
font: Renderer::Font::default(),
|
||||
font: None,
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -117,8 +117,8 @@ where
|
|||
/// Sets the [`Font`] of the text of the [`Toggler`]
|
||||
///
|
||||
/// [`Font`]: crate::text::Renderer::Font
|
||||
pub fn font(mut self, font: Renderer::Font) -> Self {
|
||||
self.font = font;
|
||||
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
||||
self.font = Some(font.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ where
|
|||
row = row.push(
|
||||
Text::new(label)
|
||||
.horizontal_alignment(self.text_alignment)
|
||||
.font(self.font.clone())
|
||||
.font(self.font.unwrap_or_else(|| renderer.default_font()))
|
||||
.width(self.width)
|
||||
.size(
|
||||
self.text_size
|
||||
|
|
@ -243,7 +243,7 @@ where
|
|||
label_layout,
|
||||
label,
|
||||
self.text_size,
|
||||
self.font.clone(),
|
||||
self.font,
|
||||
Default::default(),
|
||||
self.text_alignment,
|
||||
alignment::Vertical::Center,
|
||||
|
|
|
|||
|
|
@ -197,7 +197,6 @@ pub trait Application: Sized {
|
|||
let renderer_settings = crate::renderer::Settings {
|
||||
default_font: settings.default_font,
|
||||
default_text_size: settings.default_text_size,
|
||||
text_multithreading: settings.text_multithreading,
|
||||
antialiasing: if settings.antialiasing {
|
||||
Some(crate::renderer::settings::Antialiasing::MSAAx4)
|
||||
} else {
|
||||
|
|
|
|||
17
src/lib.rs
17
src/lib.rs
|
|
@ -182,20 +182,12 @@ pub mod touch;
|
|||
pub mod widget;
|
||||
pub mod window;
|
||||
|
||||
#[cfg(all(not(feature = "glow"), feature = "wgpu"))]
|
||||
use iced_winit as runtime;
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
use iced_glutin as runtime;
|
||||
|
||||
#[cfg(all(not(feature = "glow"), feature = "wgpu"))]
|
||||
use iced_wgpu as renderer;
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
use iced_glow as renderer;
|
||||
use iced_winit as runtime;
|
||||
|
||||
pub use iced_native::theme;
|
||||
pub use runtime::event;
|
||||
pub use runtime::font;
|
||||
pub use runtime::subscription;
|
||||
|
||||
pub use application::Application;
|
||||
|
|
@ -203,6 +195,7 @@ pub use element::Element;
|
|||
pub use error::Error;
|
||||
pub use event::Event;
|
||||
pub use executor::Executor;
|
||||
pub use font::Font;
|
||||
pub use renderer::Renderer;
|
||||
pub use result::Result;
|
||||
pub use sandbox::Sandbox;
|
||||
|
|
@ -213,8 +206,8 @@ pub use theme::Theme;
|
|||
pub use runtime::alignment;
|
||||
pub use runtime::futures;
|
||||
pub use runtime::{
|
||||
color, Alignment, Background, Color, Command, ContentFit, Font, Length,
|
||||
Padding, Point, Rectangle, Size, Vector,
|
||||
color, Alignment, Background, Color, Command, ContentFit, Length, Padding,
|
||||
Point, Rectangle, Size, Vector,
|
||||
};
|
||||
|
||||
#[cfg(feature = "system")]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//! Configure your application.
|
||||
use crate::window;
|
||||
use crate::Font;
|
||||
|
||||
/// The settings of an application.
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -20,23 +21,16 @@ pub struct Settings<Flags> {
|
|||
/// [`Application`]: crate::Application
|
||||
pub flags: Flags,
|
||||
|
||||
/// The bytes of the font that will be used by default.
|
||||
/// The default [`Font`] to be used.
|
||||
///
|
||||
/// If `None` is provided, a default system font will be chosen.
|
||||
// TODO: Add `name` for web compatibility
|
||||
pub default_font: Option<&'static [u8]>,
|
||||
/// By default, it uses [`Font::SansSerif`].
|
||||
pub default_font: Font,
|
||||
|
||||
/// The text size that will be used by default.
|
||||
///
|
||||
/// The default value is `20.0`.
|
||||
/// The default value is `16.0`.
|
||||
pub default_text_size: f32,
|
||||
|
||||
/// If enabled, spread text workload in multiple threads when multiple cores
|
||||
/// are available.
|
||||
///
|
||||
/// By default, it is disabled.
|
||||
pub text_multithreading: bool,
|
||||
|
||||
/// If set to true, the renderer will try to perform antialiasing for some
|
||||
/// primitives.
|
||||
///
|
||||
|
|
@ -55,15 +49,6 @@ pub struct Settings<Flags> {
|
|||
///
|
||||
/// [`Application`]: crate::Application
|
||||
pub exit_on_close_request: bool,
|
||||
|
||||
/// Whether the [`Application`] should try to build the context
|
||||
/// using OpenGL ES first then OpenGL.
|
||||
///
|
||||
/// By default, it is disabled.
|
||||
/// **Note:** Only works for the `glow` backend.
|
||||
///
|
||||
/// [`Application`]: crate::Application
|
||||
pub try_opengles_first: bool,
|
||||
}
|
||||
|
||||
impl<Flags> Settings<Flags> {
|
||||
|
|
@ -79,10 +64,8 @@ impl<Flags> Settings<Flags> {
|
|||
window: default_settings.window,
|
||||
default_font: default_settings.default_font,
|
||||
default_text_size: default_settings.default_text_size,
|
||||
text_multithreading: default_settings.text_multithreading,
|
||||
antialiasing: default_settings.antialiasing,
|
||||
exit_on_close_request: default_settings.exit_on_close_request,
|
||||
try_opengles_first: default_settings.try_opengles_first,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -96,12 +79,10 @@ where
|
|||
id: None,
|
||||
window: Default::default(),
|
||||
flags: Default::default(),
|
||||
default_font: Default::default(),
|
||||
default_text_size: 20.0,
|
||||
text_multithreading: false,
|
||||
default_font: Font::SansSerif,
|
||||
default_text_size: 16.0,
|
||||
antialiasing: false,
|
||||
exit_on_close_request: true,
|
||||
try_opengles_first: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -113,7 +94,6 @@ impl<Flags> From<Settings<Flags>> for iced_winit::Settings<Flags> {
|
|||
window: settings.window.into(),
|
||||
flags: settings.flags,
|
||||
exit_on_close_request: settings.exit_on_close_request,
|
||||
try_opengles_first: settings.try_opengles_first,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,19 +23,27 @@ dds = ["iced_graphics/dds"]
|
|||
farbfeld = ["iced_graphics/farbfeld"]
|
||||
canvas = ["iced_graphics/canvas"]
|
||||
qr_code = ["iced_graphics/qr_code"]
|
||||
default_system_font = ["iced_graphics/font-source"]
|
||||
spirv = ["wgpu/spirv"]
|
||||
webgl = ["wgpu/webgl"]
|
||||
|
||||
[dependencies]
|
||||
wgpu = "0.14"
|
||||
wgpu_glyph = "0.18"
|
||||
glyph_brush = "0.7"
|
||||
raw-window-handle = "0.5"
|
||||
log = "0.4"
|
||||
guillotiere = "0.6"
|
||||
futures = "0.3"
|
||||
bitflags = "1.2"
|
||||
once_cell = "1.0"
|
||||
rustc-hash = "1.1"
|
||||
ouroboros = "0.15"
|
||||
|
||||
[dependencies.twox-hash]
|
||||
version = "1.6"
|
||||
default-features = false
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash]
|
||||
version = "1.6.1"
|
||||
features = ["std"]
|
||||
|
||||
[dependencies.bytemuck]
|
||||
version = "1.9"
|
||||
|
|
@ -48,7 +56,11 @@ path = "../native"
|
|||
[dependencies.iced_graphics]
|
||||
version = "0.7"
|
||||
path = "../graphics"
|
||||
features = ["font-fallback", "font-icons"]
|
||||
|
||||
[dependencies.glyphon]
|
||||
version = "0.2"
|
||||
git = "https://github.com/hecrj/glyphon.git"
|
||||
rev = "65b481d758f50fd13fc21af2cc5ef62ddee64955"
|
||||
|
||||
[dependencies.tracing]
|
||||
version = "0.1.6"
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -4,11 +4,8 @@ use crate::triangle;
|
|||
use crate::{Settings, Transformation};
|
||||
|
||||
use iced_graphics::backend;
|
||||
use iced_graphics::font;
|
||||
use iced_graphics::layer::Layer;
|
||||
use iced_graphics::{Primitive, Viewport};
|
||||
use iced_native::alignment;
|
||||
use iced_native::{Font, Size};
|
||||
use iced_graphics::{Color, Font, Primitive, Size, Viewport};
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing::info_span;
|
||||
|
|
@ -16,11 +13,13 @@ use tracing::info_span;
|
|||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
use crate::image;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A [`wgpu`] graphics backend for [`iced`].
|
||||
///
|
||||
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||
/// [`iced`]: https://github.com/iced-rs/iced
|
||||
#[derive(Debug)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Backend {
|
||||
quad_pipeline: quad::Pipeline,
|
||||
text_pipeline: text::Pipeline,
|
||||
|
|
@ -29,6 +28,7 @@ pub struct Backend {
|
|||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
image_pipeline: image::Pipeline,
|
||||
|
||||
default_font: Font,
|
||||
default_text_size: f32,
|
||||
}
|
||||
|
||||
|
|
@ -36,16 +36,11 @@ impl Backend {
|
|||
/// Creates a new [`Backend`].
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
settings: Settings,
|
||||
format: wgpu::TextureFormat,
|
||||
) -> Self {
|
||||
let text_pipeline = text::Pipeline::new(
|
||||
device,
|
||||
format,
|
||||
settings.default_font,
|
||||
settings.text_multithreading,
|
||||
);
|
||||
|
||||
let text_pipeline = text::Pipeline::new(device, queue, format);
|
||||
let quad_pipeline = quad::Pipeline::new(device, format);
|
||||
let triangle_pipeline =
|
||||
triangle::Pipeline::new(device, format, settings.antialiasing);
|
||||
|
|
@ -61,6 +56,7 @@ impl Backend {
|
|||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
image_pipeline,
|
||||
|
||||
default_font: settings.default_font,
|
||||
default_text_size: settings.default_text_size,
|
||||
}
|
||||
}
|
||||
|
|
@ -72,8 +68,9 @@ impl Backend {
|
|||
pub fn present<T: AsRef<str>>(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
staging_belt: &mut wgpu::util::StagingBelt,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
clear_color: Option<Color>,
|
||||
frame: &wgpu::TextureView,
|
||||
primitives: &[Primitive],
|
||||
viewport: &Viewport,
|
||||
|
|
@ -90,168 +87,246 @@ impl Backend {
|
|||
let mut layers = Layer::generate(primitives, viewport);
|
||||
layers.push(Layer::overlay(overlay_text, viewport));
|
||||
|
||||
for layer in layers {
|
||||
self.flush(
|
||||
device,
|
||||
scale_factor,
|
||||
transformation,
|
||||
&layer,
|
||||
staging_belt,
|
||||
encoder,
|
||||
frame,
|
||||
target_size,
|
||||
);
|
||||
}
|
||||
self.prepare(
|
||||
device,
|
||||
queue,
|
||||
encoder,
|
||||
scale_factor,
|
||||
transformation,
|
||||
&layers,
|
||||
);
|
||||
|
||||
while !self.prepare_text(
|
||||
device,
|
||||
queue,
|
||||
scale_factor,
|
||||
target_size,
|
||||
&layers,
|
||||
) {}
|
||||
|
||||
self.render(
|
||||
device,
|
||||
encoder,
|
||||
frame,
|
||||
clear_color,
|
||||
scale_factor,
|
||||
target_size,
|
||||
&layers,
|
||||
);
|
||||
|
||||
self.quad_pipeline.end_frame();
|
||||
self.text_pipeline.end_frame();
|
||||
self.triangle_pipeline.end_frame();
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
self.image_pipeline.trim_cache(device, encoder);
|
||||
self.image_pipeline.end_frame(device, queue, encoder);
|
||||
}
|
||||
|
||||
fn flush(
|
||||
fn prepare_text(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
scale_factor: f32,
|
||||
target_size: Size<u32>,
|
||||
layers: &[Layer<'_>],
|
||||
) -> bool {
|
||||
for layer in layers {
|
||||
let bounds = (layer.bounds * scale_factor).snap();
|
||||
|
||||
if bounds.width < 1 || bounds.height < 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !layer.text.is_empty()
|
||||
&& !self.text_pipeline.prepare(
|
||||
device,
|
||||
queue,
|
||||
&layer.text,
|
||||
layer.bounds,
|
||||
scale_factor,
|
||||
target_size,
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
_encoder: &mut wgpu::CommandEncoder,
|
||||
scale_factor: f32,
|
||||
transformation: Transformation,
|
||||
layer: &Layer<'_>,
|
||||
staging_belt: &mut wgpu::util::StagingBelt,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
target_size: Size<u32>,
|
||||
layers: &[Layer<'_>],
|
||||
) {
|
||||
let bounds = (layer.bounds * scale_factor).snap();
|
||||
for layer in layers {
|
||||
let bounds = (layer.bounds * scale_factor).snap();
|
||||
|
||||
if bounds.width < 1 || bounds.height < 1 {
|
||||
return;
|
||||
}
|
||||
if bounds.width < 1 || bounds.height < 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !layer.quads.is_empty() {
|
||||
self.quad_pipeline.draw(
|
||||
device,
|
||||
staging_belt,
|
||||
encoder,
|
||||
&layer.quads,
|
||||
transformation,
|
||||
scale_factor,
|
||||
bounds,
|
||||
target,
|
||||
);
|
||||
}
|
||||
|
||||
if !layer.meshes.is_empty() {
|
||||
let scaled = transformation
|
||||
* Transformation::scale(scale_factor, scale_factor);
|
||||
|
||||
self.triangle_pipeline.draw(
|
||||
device,
|
||||
staging_belt,
|
||||
encoder,
|
||||
target,
|
||||
target_size,
|
||||
scaled,
|
||||
scale_factor,
|
||||
&layer.meshes,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
{
|
||||
if !layer.images.is_empty() {
|
||||
let scaled = transformation
|
||||
* Transformation::scale(scale_factor, scale_factor);
|
||||
|
||||
self.image_pipeline.draw(
|
||||
if !layer.quads.is_empty() {
|
||||
self.quad_pipeline.prepare(
|
||||
device,
|
||||
staging_belt,
|
||||
encoder,
|
||||
&layer.images,
|
||||
scaled,
|
||||
bounds,
|
||||
target,
|
||||
queue,
|
||||
&layer.quads,
|
||||
transformation,
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if !layer.text.is_empty() {
|
||||
for text in layer.text.iter() {
|
||||
// Target physical coordinates directly to avoid blurry text
|
||||
let text = wgpu_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![wgpu_glyph::Text {
|
||||
text: text.content,
|
||||
scale: wgpu_glyph::ab_glyph::PxScale {
|
||||
x: text.size * scale_factor,
|
||||
y: text.size * scale_factor,
|
||||
},
|
||||
font_id: self.text_pipeline.find_font(text.font),
|
||||
extra: wgpu_glyph::Extra {
|
||||
color: text.color,
|
||||
z: 0.0,
|
||||
},
|
||||
}],
|
||||
layout: wgpu_glyph::Layout::default()
|
||||
.h_align(match text.horizontal_alignment {
|
||||
alignment::Horizontal::Left => {
|
||||
wgpu_glyph::HorizontalAlign::Left
|
||||
}
|
||||
alignment::Horizontal::Center => {
|
||||
wgpu_glyph::HorizontalAlign::Center
|
||||
}
|
||||
alignment::Horizontal::Right => {
|
||||
wgpu_glyph::HorizontalAlign::Right
|
||||
}
|
||||
})
|
||||
.v_align(match text.vertical_alignment {
|
||||
alignment::Vertical::Top => {
|
||||
wgpu_glyph::VerticalAlign::Top
|
||||
}
|
||||
alignment::Vertical::Center => {
|
||||
wgpu_glyph::VerticalAlign::Center
|
||||
}
|
||||
alignment::Vertical::Bottom => {
|
||||
wgpu_glyph::VerticalAlign::Bottom
|
||||
}
|
||||
}),
|
||||
};
|
||||
if !layer.meshes.is_empty() {
|
||||
let scaled = transformation
|
||||
* Transformation::scale(scale_factor, scale_factor);
|
||||
|
||||
self.text_pipeline.queue(text);
|
||||
self.triangle_pipeline.prepare(
|
||||
device,
|
||||
queue,
|
||||
&layer.meshes,
|
||||
scaled,
|
||||
);
|
||||
}
|
||||
|
||||
self.text_pipeline.draw_queued(
|
||||
device,
|
||||
staging_belt,
|
||||
encoder,
|
||||
target,
|
||||
transformation,
|
||||
wgpu_glyph::Region {
|
||||
x: bounds.x,
|
||||
y: bounds.y,
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
},
|
||||
);
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
{
|
||||
if !layer.images.is_empty() {
|
||||
let scaled = transformation
|
||||
* Transformation::scale(scale_factor, scale_factor);
|
||||
|
||||
self.image_pipeline.prepare(
|
||||
device,
|
||||
queue,
|
||||
_encoder,
|
||||
&layer.images,
|
||||
scaled,
|
||||
scale_factor,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
clear_color: Option<Color>,
|
||||
scale_factor: f32,
|
||||
target_size: Size<u32>,
|
||||
layers: &[Layer<'_>],
|
||||
) {
|
||||
use std::mem::ManuallyDrop;
|
||||
|
||||
let mut quad_layer = 0;
|
||||
let mut triangle_layer = 0;
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
let mut image_layer = 0;
|
||||
let mut text_layer = 0;
|
||||
|
||||
let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
||||
&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu::quad render pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: match clear_color {
|
||||
Some(background_color) => wgpu::LoadOp::Clear({
|
||||
let [r, g, b, a] =
|
||||
background_color.into_linear();
|
||||
|
||||
wgpu::Color {
|
||||
r: f64::from(r),
|
||||
g: f64::from(g),
|
||||
b: f64::from(b),
|
||||
a: f64::from(a),
|
||||
}
|
||||
}),
|
||||
None => wgpu::LoadOp::Load,
|
||||
},
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
},
|
||||
));
|
||||
|
||||
for layer in layers {
|
||||
let bounds = (layer.bounds * scale_factor).snap();
|
||||
|
||||
if bounds.width < 1 || bounds.height < 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
if !layer.quads.is_empty() {
|
||||
self.quad_pipeline
|
||||
.render(quad_layer, bounds, &mut render_pass);
|
||||
|
||||
quad_layer += 1;
|
||||
}
|
||||
|
||||
if !layer.meshes.is_empty() {
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
|
||||
self.triangle_pipeline.render(
|
||||
device,
|
||||
encoder,
|
||||
target,
|
||||
triangle_layer,
|
||||
target_size,
|
||||
&layer.meshes,
|
||||
scale_factor,
|
||||
);
|
||||
|
||||
triangle_layer += 1;
|
||||
|
||||
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
|
||||
&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu::quad render pass"),
|
||||
color_attachments: &[Some(
|
||||
wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: true,
|
||||
},
|
||||
},
|
||||
)],
|
||||
depth_stencil_attachment: None,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
{
|
||||
if !layer.images.is_empty() {
|
||||
self.image_pipeline.render(
|
||||
image_layer,
|
||||
bounds,
|
||||
&mut render_pass,
|
||||
);
|
||||
|
||||
image_layer += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !layer.text.is_empty() {
|
||||
self.text_pipeline
|
||||
.render(text_layer, bounds, &mut render_pass);
|
||||
|
||||
text_layer += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let _ = ManuallyDrop::into_inner(render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
impl iced_graphics::Backend for Backend {
|
||||
|
|
@ -261,9 +336,13 @@ impl iced_graphics::Backend for Backend {
|
|||
}
|
||||
|
||||
impl backend::Text for Backend {
|
||||
const ICON_FONT: Font = font::ICONS;
|
||||
const CHECKMARK_ICON: char = font::CHECKMARK_ICON;
|
||||
const ARROW_DOWN_ICON: char = font::ARROW_DOWN_ICON;
|
||||
const ICON_FONT: Font = Font::Name("Iced-Icons");
|
||||
const CHECKMARK_ICON: char = '\u{f00c}';
|
||||
const ARROW_DOWN_ICON: char = '\u{e800}';
|
||||
|
||||
fn default_font(&self) -> Font {
|
||||
self.default_font
|
||||
}
|
||||
|
||||
fn default_size(&self) -> f32 {
|
||||
self.default_text_size
|
||||
|
|
@ -297,6 +376,10 @@ impl backend::Text for Backend {
|
|||
nearest_only,
|
||||
)
|
||||
}
|
||||
|
||||
fn load_font(&mut self, font: Cow<'static, [u8]>) {
|
||||
self.text_pipeline.load_font(font);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,89 @@
|
|||
//! Utilities for buffer operations.
|
||||
pub mod dynamic;
|
||||
pub mod r#static;
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::RangeBounds;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Buffer<T> {
|
||||
label: &'static str,
|
||||
size: u64,
|
||||
usage: wgpu::BufferUsages,
|
||||
raw: wgpu::Buffer,
|
||||
type_: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: bytemuck::Pod> Buffer<T> {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
label: &'static str,
|
||||
amount: usize,
|
||||
usage: wgpu::BufferUsages,
|
||||
) -> Self {
|
||||
let size = next_copy_size::<T>(amount);
|
||||
|
||||
let raw = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some(label),
|
||||
size,
|
||||
usage,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
Self {
|
||||
label,
|
||||
size,
|
||||
usage,
|
||||
raw,
|
||||
type_: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool {
|
||||
let new_size = (std::mem::size_of::<T>() * new_count) as u64;
|
||||
|
||||
if self.size < new_size {
|
||||
self.raw = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some(self.label),
|
||||
size: new_size,
|
||||
usage: self.usage,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
self.size = new_size;
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(
|
||||
&self,
|
||||
queue: &wgpu::Queue,
|
||||
offset_count: usize,
|
||||
contents: &[T],
|
||||
) {
|
||||
queue.write_buffer(
|
||||
&self.raw,
|
||||
(std::mem::size_of::<T>() * offset_count) as u64,
|
||||
bytemuck::cast_slice(contents),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn slice(
|
||||
&self,
|
||||
bounds: impl RangeBounds<wgpu::BufferAddress>,
|
||||
) -> wgpu::BufferSlice<'_> {
|
||||
self.raw.slice(bounds)
|
||||
}
|
||||
}
|
||||
|
||||
fn next_copy_size<T>(amount: usize) -> u64 {
|
||||
let align_mask = wgpu::COPY_BUFFER_ALIGNMENT - 1;
|
||||
|
||||
(((std::mem::size_of::<T>() * amount).next_power_of_two() as u64
|
||||
+ align_mask)
|
||||
& !align_mask)
|
||||
.max(wgpu::COPY_BUFFER_ALIGNMENT)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,25 +112,8 @@ impl<T: ShaderType + WriteInto> Buffer<T> {
|
|||
}
|
||||
|
||||
/// Write the contents of this dynamic buffer to the GPU via staging belt command.
|
||||
pub fn write(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
staging_belt: &mut wgpu::util::StagingBelt,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
) {
|
||||
let size = self.cpu.get_ref().len();
|
||||
|
||||
if let Some(buffer_size) = wgpu::BufferSize::new(size as u64) {
|
||||
let mut buffer = staging_belt.write_buffer(
|
||||
encoder,
|
||||
&self.gpu,
|
||||
0,
|
||||
buffer_size,
|
||||
device,
|
||||
);
|
||||
|
||||
buffer.copy_from_slice(self.cpu.get_ref());
|
||||
}
|
||||
pub fn write(&mut self, queue: &wgpu::Queue) {
|
||||
queue.write_buffer(&self.gpu, 0, self.cpu.get_ref());
|
||||
}
|
||||
|
||||
// Gets the aligned offset at the given index from the CPU buffer.
|
||||
|
|
@ -184,7 +167,7 @@ impl Internal {
|
|||
}
|
||||
|
||||
/// Returns bytearray of aligned CPU buffer.
|
||||
pub(super) fn get_ref(&self) -> &Vec<u8> {
|
||||
pub(super) fn get_ref(&self) -> &[u8] {
|
||||
match self {
|
||||
Internal::Uniform(buf) => buf.as_ref(),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ use bytemuck::{Pod, Zeroable};
|
|||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
|
||||
//128 triangles/indices
|
||||
const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 128;
|
||||
const DEFAULT_COUNT: wgpu::BufferAddress = 128;
|
||||
|
||||
/// A generic buffer struct useful for items which have no alignment requirements
|
||||
/// (e.g. Vertex, Index buffers) & no dynamic offsets.
|
||||
|
|
@ -25,7 +24,7 @@ impl<T: Pod + Zeroable> Buffer<T> {
|
|||
label: &'static str,
|
||||
usages: wgpu::BufferUsages,
|
||||
) -> Self {
|
||||
let size = (mem::size_of::<T>() as u64) * DEFAULT_STATIC_BUFFER_COUNT;
|
||||
let size = (mem::size_of::<T>() as u64) * DEFAULT_COUNT;
|
||||
|
||||
Self {
|
||||
offsets: Vec::new(),
|
||||
|
|
@ -57,9 +56,13 @@ impl<T: Pod + Zeroable> Buffer<T> {
|
|||
let size = (mem::size_of::<T>() * new_count) as u64;
|
||||
|
||||
if self.size < size {
|
||||
self.size =
|
||||
(mem::size_of::<T>() * (new_count + new_count / 2)) as u64;
|
||||
|
||||
self.gpu =
|
||||
Self::gpu_buffer(device, self.label, self.size, self.usages);
|
||||
|
||||
self.offsets.clear();
|
||||
self.size = size;
|
||||
self.gpu = Self::gpu_buffer(device, self.label, size, self.usages);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
|
@ -71,28 +74,15 @@ impl<T: Pod + Zeroable> Buffer<T> {
|
|||
/// Returns the size of the written bytes.
|
||||
pub fn write(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
staging_belt: &mut wgpu::util::StagingBelt,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
queue: &wgpu::Queue,
|
||||
offset: u64,
|
||||
content: &[T],
|
||||
) -> u64 {
|
||||
let bytes = bytemuck::cast_slice(content);
|
||||
let bytes_size = bytes.len() as u64;
|
||||
|
||||
if let Some(buffer_size) = wgpu::BufferSize::new(bytes_size) {
|
||||
let mut buffer = staging_belt.write_buffer(
|
||||
encoder,
|
||||
&self.gpu,
|
||||
offset,
|
||||
buffer_size,
|
||||
device,
|
||||
);
|
||||
|
||||
buffer.copy_from_slice(bytes);
|
||||
|
||||
self.offsets.push(offset);
|
||||
}
|
||||
queue.write_buffer(&self.gpu, offset, bytes);
|
||||
self.offsets.push(offset);
|
||||
|
||||
bytes_size
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use iced_graphics::image::raster;
|
|||
#[cfg(feature = "svg")]
|
||||
use iced_graphics::image::vector;
|
||||
|
||||
use crate::Transformation;
|
||||
use crate::{Buffer, Transformation};
|
||||
use atlas::Atlas;
|
||||
|
||||
use iced_graphics::layer;
|
||||
|
|
@ -34,15 +34,107 @@ pub struct Pipeline {
|
|||
vector_cache: RefCell<vector::Cache<Atlas>>,
|
||||
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
uniforms: wgpu::Buffer,
|
||||
vertices: wgpu::Buffer,
|
||||
indices: wgpu::Buffer,
|
||||
instances: wgpu::Buffer,
|
||||
constants: wgpu::BindGroup,
|
||||
sampler: wgpu::Sampler,
|
||||
texture: wgpu::BindGroup,
|
||||
texture_version: usize,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
texture_atlas: Atlas,
|
||||
texture_layout: wgpu::BindGroupLayout,
|
||||
constant_layout: wgpu::BindGroupLayout,
|
||||
|
||||
layers: Vec<Layer>,
|
||||
prepare_layer: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Layer {
|
||||
uniforms: wgpu::Buffer,
|
||||
constants: wgpu::BindGroup,
|
||||
instances: Buffer<Instance>,
|
||||
instance_count: usize,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
fn new(
|
||||
device: &wgpu::Device,
|
||||
constant_layout: &wgpu::BindGroupLayout,
|
||||
sampler: &wgpu::Sampler,
|
||||
) -> Self {
|
||||
let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("iced_wgpu::image uniforms buffer"),
|
||||
size: mem::size_of::<Uniforms>() as u64,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image constants bind group"),
|
||||
layout: constant_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer: &uniforms,
|
||||
offset: 0,
|
||||
size: None,
|
||||
},
|
||||
),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let instances = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu::image instance buffer",
|
||||
Instance::INITIAL,
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
Self {
|
||||
uniforms,
|
||||
constants,
|
||||
instances,
|
||||
instance_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
instances: &[Instance],
|
||||
transformation: Transformation,
|
||||
) {
|
||||
queue.write_buffer(
|
||||
&self.uniforms,
|
||||
0,
|
||||
bytemuck::bytes_of(&Uniforms {
|
||||
transform: transformation.into(),
|
||||
}),
|
||||
);
|
||||
|
||||
let _ = self.instances.resize(device, instances.len());
|
||||
self.instances.write(queue, 0, instances);
|
||||
|
||||
self.instance_count = instances.len();
|
||||
}
|
||||
|
||||
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
render_pass.set_bind_group(0, &self.constants, &[]);
|
||||
render_pass.set_vertex_buffer(1, self.instances.slice(..));
|
||||
|
||||
render_pass.draw_indexed(
|
||||
0..QUAD_INDICES.len() as u32,
|
||||
0,
|
||||
0..self.instance_count as u32,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
|
|
@ -86,35 +178,6 @@ impl Pipeline {
|
|||
],
|
||||
});
|
||||
|
||||
let uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("iced_wgpu::image uniforms buffer"),
|
||||
size: mem::size_of::<Uniforms>() as u64,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let constant_bind_group =
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::image constants bind group"),
|
||||
layout: &constant_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer: &uniforms_buffer,
|
||||
offset: 0,
|
||||
size: None,
|
||||
},
|
||||
),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let texture_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("iced_wgpu::image texture atlas layout"),
|
||||
|
|
@ -225,13 +288,6 @@ impl Pipeline {
|
|||
usage: wgpu::BufferUsages::INDEX,
|
||||
});
|
||||
|
||||
let instances = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("iced_wgpu::image instance buffer"),
|
||||
size: mem::size_of::<Instance>() as u64 * Instance::MAX as u64,
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let texture_atlas = Atlas::new(device);
|
||||
|
||||
let texture = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
|
|
@ -253,15 +309,17 @@ impl Pipeline {
|
|||
vector_cache: RefCell::new(vector::Cache::default()),
|
||||
|
||||
pipeline,
|
||||
uniforms: uniforms_buffer,
|
||||
vertices,
|
||||
indices,
|
||||
instances,
|
||||
constants: constant_bind_group,
|
||||
sampler,
|
||||
texture,
|
||||
texture_version: texture_atlas.layer_count(),
|
||||
texture_layout,
|
||||
texture_atlas,
|
||||
texture_layout,
|
||||
constant_layout,
|
||||
|
||||
layers: Vec::new(),
|
||||
prepare_layer: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -281,17 +339,18 @@ impl Pipeline {
|
|||
svg.viewport_dimensions()
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
staging_belt: &mut wgpu::util::StagingBelt,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
images: &[layer::Image],
|
||||
transformation: Transformation,
|
||||
bounds: Rectangle<u32>,
|
||||
target: &wgpu::TextureView,
|
||||
_scale: f32,
|
||||
) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Wgpu::Image", "PREPARE").entered();
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Wgpu::Image", "DRAW").entered();
|
||||
|
||||
|
|
@ -309,7 +368,7 @@ impl Pipeline {
|
|||
layer::Image::Raster { handle, bounds } => {
|
||||
if let Some(atlas_entry) = raster_cache.upload(
|
||||
handle,
|
||||
&mut (device, encoder),
|
||||
&mut (device, queue, encoder),
|
||||
&mut self.texture_atlas,
|
||||
) {
|
||||
add_instances(
|
||||
|
|
@ -336,7 +395,7 @@ impl Pipeline {
|
|||
*color,
|
||||
size,
|
||||
_scale,
|
||||
&mut (device, encoder),
|
||||
&mut (device, queue, encoder),
|
||||
&mut self.texture_atlas,
|
||||
) {
|
||||
add_instances(
|
||||
|
|
@ -376,68 +435,28 @@ impl Pipeline {
|
|||
self.texture_version = texture_version;
|
||||
}
|
||||
|
||||
{
|
||||
let mut uniforms_buffer = staging_belt.write_buffer(
|
||||
encoder,
|
||||
&self.uniforms,
|
||||
0,
|
||||
wgpu::BufferSize::new(mem::size_of::<Uniforms>() as u64)
|
||||
.unwrap(),
|
||||
if self.layers.len() <= self.prepare_layer {
|
||||
self.layers.push(Layer::new(
|
||||
device,
|
||||
);
|
||||
|
||||
uniforms_buffer.copy_from_slice(bytemuck::bytes_of(&Uniforms {
|
||||
transform: transformation.into(),
|
||||
}));
|
||||
&self.constant_layout,
|
||||
&self.sampler,
|
||||
));
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
let total = instances.len();
|
||||
let layer = &mut self.layers[self.prepare_layer];
|
||||
layer.prepare(device, queue, instances, transformation);
|
||||
|
||||
while i < total {
|
||||
let end = (i + Instance::MAX).min(total);
|
||||
let amount = end - i;
|
||||
|
||||
let mut instances_buffer = staging_belt.write_buffer(
|
||||
encoder,
|
||||
&self.instances,
|
||||
0,
|
||||
wgpu::BufferSize::new(
|
||||
(amount * std::mem::size_of::<Instance>()) as u64,
|
||||
)
|
||||
.unwrap(),
|
||||
device,
|
||||
);
|
||||
|
||||
instances_buffer.copy_from_slice(bytemuck::cast_slice(
|
||||
&instances[i..i + amount],
|
||||
));
|
||||
|
||||
let mut render_pass =
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu::image render pass"),
|
||||
color_attachments: &[Some(
|
||||
wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: true,
|
||||
},
|
||||
},
|
||||
)],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
self.prepare_layer += 1;
|
||||
}
|
||||
|
||||
pub fn render<'a>(
|
||||
&'a self,
|
||||
layer: usize,
|
||||
bounds: Rectangle<u32>,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
if let Some(layer) = self.layers.get(layer) {
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_bind_group(0, &self.constants, &[]);
|
||||
render_pass.set_bind_group(1, &self.texture, &[]);
|
||||
render_pass.set_index_buffer(
|
||||
self.indices.slice(..),
|
||||
wgpu::IndexFormat::Uint16,
|
||||
);
|
||||
render_pass.set_vertex_buffer(0, self.vertices.slice(..));
|
||||
render_pass.set_vertex_buffer(1, self.instances.slice(..));
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
bounds.x,
|
||||
|
|
@ -446,30 +465,34 @@ impl Pipeline {
|
|||
bounds.height,
|
||||
);
|
||||
|
||||
render_pass.draw_indexed(
|
||||
0..QUAD_INDICES.len() as u32,
|
||||
0,
|
||||
0..amount as u32,
|
||||
render_pass.set_bind_group(1, &self.texture, &[]);
|
||||
render_pass.set_index_buffer(
|
||||
self.indices.slice(..),
|
||||
wgpu::IndexFormat::Uint16,
|
||||
);
|
||||
render_pass.set_vertex_buffer(0, self.vertices.slice(..));
|
||||
|
||||
i += Instance::MAX;
|
||||
layer.render(render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trim_cache(
|
||||
pub fn end_frame(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
) {
|
||||
#[cfg(feature = "image")]
|
||||
self.raster_cache
|
||||
.borrow_mut()
|
||||
.trim(&mut self.texture_atlas, &mut (device, encoder));
|
||||
.trim(&mut self.texture_atlas, &mut (device, queue, encoder));
|
||||
|
||||
#[cfg(feature = "svg")]
|
||||
self.vector_cache
|
||||
.borrow_mut()
|
||||
.trim(&mut self.texture_atlas, &mut (device, encoder));
|
||||
.trim(&mut self.texture_atlas, &mut (device, queue, encoder));
|
||||
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -507,7 +530,7 @@ struct Instance {
|
|||
}
|
||||
|
||||
impl Instance {
|
||||
pub const MAX: usize = 1_000;
|
||||
pub const INITIAL: usize = 1_000;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
|
|||
|
|
@ -185,13 +185,13 @@ impl Atlas {
|
|||
|
||||
fn upload_allocation(
|
||||
&mut self,
|
||||
buffer: &wgpu::Buffer,
|
||||
data: &[u8],
|
||||
image_width: u32,
|
||||
image_height: u32,
|
||||
padding: u32,
|
||||
offset: usize,
|
||||
allocation: &Allocation,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
queue: &wgpu::Queue,
|
||||
) {
|
||||
let (x, y) = allocation.position();
|
||||
let Size { width, height } = allocation.size();
|
||||
|
|
@ -203,15 +203,7 @@ impl Atlas {
|
|||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
encoder.copy_buffer_to_texture(
|
||||
wgpu::ImageCopyBuffer {
|
||||
buffer,
|
||||
layout: wgpu::ImageDataLayout {
|
||||
offset: offset as u64,
|
||||
bytes_per_row: NonZeroU32::new(4 * image_width + padding),
|
||||
rows_per_image: NonZeroU32::new(image_height),
|
||||
},
|
||||
},
|
||||
queue.write_texture(
|
||||
wgpu::ImageCopyTexture {
|
||||
texture: &self.texture,
|
||||
mip_level: 0,
|
||||
|
|
@ -222,6 +214,12 @@ impl Atlas {
|
|||
},
|
||||
aspect: wgpu::TextureAspect::default(),
|
||||
},
|
||||
data,
|
||||
wgpu::ImageDataLayout {
|
||||
offset: offset as u64,
|
||||
bytes_per_row: NonZeroU32::new(4 * image_width + padding),
|
||||
rows_per_image: NonZeroU32::new(image_height),
|
||||
},
|
||||
extent,
|
||||
);
|
||||
}
|
||||
|
|
@ -301,17 +299,19 @@ impl Atlas {
|
|||
|
||||
impl image::Storage for Atlas {
|
||||
type Entry = Entry;
|
||||
type State<'a> = (&'a wgpu::Device, &'a mut wgpu::CommandEncoder);
|
||||
type State<'a> = (
|
||||
&'a wgpu::Device,
|
||||
&'a wgpu::Queue,
|
||||
&'a mut wgpu::CommandEncoder,
|
||||
);
|
||||
|
||||
fn upload(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
data: &[u8],
|
||||
(device, encoder): &mut Self::State<'_>,
|
||||
(device, queue, encoder): &mut Self::State<'_>,
|
||||
) -> Option<Self::Entry> {
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
let entry = {
|
||||
let current_size = self.layers.len();
|
||||
let entry = self.allocate(width, height)?;
|
||||
|
|
@ -344,17 +344,16 @@ impl image::Storage for Atlas {
|
|||
)
|
||||
}
|
||||
|
||||
let buffer =
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("iced_wgpu::image staging buffer"),
|
||||
contents: &padded_data,
|
||||
usage: wgpu::BufferUsages::COPY_SRC,
|
||||
});
|
||||
|
||||
match &entry {
|
||||
Entry::Contiguous(allocation) => {
|
||||
self.upload_allocation(
|
||||
&buffer, width, height, padding, 0, allocation, encoder,
|
||||
&padded_data,
|
||||
width,
|
||||
height,
|
||||
padding,
|
||||
0,
|
||||
allocation,
|
||||
queue,
|
||||
);
|
||||
}
|
||||
Entry::Fragmented { fragments, .. } => {
|
||||
|
|
@ -363,13 +362,13 @@ impl image::Storage for Atlas {
|
|||
let offset = (y * padded_width as u32 + 4 * x) as usize;
|
||||
|
||||
self.upload_allocation(
|
||||
&buffer,
|
||||
&padded_data,
|
||||
width,
|
||||
height,
|
||||
padding,
|
||||
offset,
|
||||
&fragment.allocation,
|
||||
encoder,
|
||||
queue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,14 +47,17 @@ mod quad;
|
|||
mod text;
|
||||
mod triangle;
|
||||
|
||||
pub use iced_graphics::{Antialiasing, Color, Error, Primitive, Viewport};
|
||||
pub use iced_graphics::{
|
||||
Antialiasing, Color, Error, Font, Primitive, Viewport,
|
||||
};
|
||||
pub use iced_native::Theme;
|
||||
pub use wgpu;
|
||||
|
||||
pub use backend::Backend;
|
||||
pub use settings::Settings;
|
||||
|
||||
pub(crate) use iced_graphics::Transformation;
|
||||
use crate::buffer::Buffer;
|
||||
use iced_graphics::Transformation;
|
||||
|
||||
#[cfg(any(feature = "image", feature = "svg"))]
|
||||
mod image;
|
||||
|
|
|
|||
232
wgpu/src/quad.rs
232
wgpu/src/quad.rs
|
|
@ -1,4 +1,4 @@
|
|||
use crate::Transformation;
|
||||
use crate::{Buffer, Transformation};
|
||||
use iced_graphics::layer;
|
||||
use iced_native::Rectangle;
|
||||
|
||||
|
|
@ -12,11 +12,11 @@ use tracing::info_span;
|
|||
#[derive(Debug)]
|
||||
pub struct Pipeline {
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
constants: wgpu::BindGroup,
|
||||
constants_buffer: wgpu::Buffer,
|
||||
constant_layout: wgpu::BindGroupLayout,
|
||||
vertices: wgpu::Buffer,
|
||||
indices: wgpu::Buffer,
|
||||
instances: wgpu::Buffer,
|
||||
layers: Vec<Layer>,
|
||||
prepare_layer: usize,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
|
|
@ -38,22 +38,6 @@ impl Pipeline {
|
|||
}],
|
||||
});
|
||||
|
||||
let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("iced_wgpu::quad uniforms buffer"),
|
||||
size: mem::size_of::<Uniforms>() as wgpu::BufferAddress,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::quad uniforms bind group"),
|
||||
layout: &constant_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: constants_buffer.as_entire_binding(),
|
||||
}],
|
||||
});
|
||||
|
||||
let layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu::quad pipeline layout"),
|
||||
|
|
@ -148,117 +132,145 @@ impl Pipeline {
|
|||
usage: wgpu::BufferUsages::INDEX,
|
||||
});
|
||||
|
||||
let instances = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("iced_wgpu::quad instance buffer"),
|
||||
size: mem::size_of::<layer::Quad>() as u64 * MAX_INSTANCES as u64,
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
Pipeline {
|
||||
pipeline,
|
||||
constants,
|
||||
constants_buffer,
|
||||
constant_layout,
|
||||
vertices,
|
||||
indices,
|
||||
instances,
|
||||
layers: Vec::new(),
|
||||
prepare_layer: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
staging_belt: &mut wgpu::util::StagingBelt,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
queue: &wgpu::Queue,
|
||||
instances: &[layer::Quad],
|
||||
transformation: Transformation,
|
||||
scale: f32,
|
||||
) {
|
||||
if self.layers.len() <= self.prepare_layer {
|
||||
self.layers.push(Layer::new(device, &self.constant_layout));
|
||||
}
|
||||
|
||||
let layer = &mut self.layers[self.prepare_layer];
|
||||
layer.prepare(device, queue, instances, transformation, scale);
|
||||
|
||||
self.prepare_layer += 1;
|
||||
}
|
||||
|
||||
pub fn render<'a>(
|
||||
&'a self,
|
||||
layer: usize,
|
||||
bounds: Rectangle<u32>,
|
||||
target: &wgpu::TextureView,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
if let Some(layer) = self.layers.get(layer) {
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
bounds.width,
|
||||
bounds.height,
|
||||
);
|
||||
|
||||
render_pass.set_index_buffer(
|
||||
self.indices.slice(..),
|
||||
wgpu::IndexFormat::Uint16,
|
||||
);
|
||||
render_pass.set_vertex_buffer(0, self.vertices.slice(..));
|
||||
|
||||
layer.draw(render_pass);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Layer {
|
||||
constants: wgpu::BindGroup,
|
||||
constants_buffer: wgpu::Buffer,
|
||||
instances: Buffer<layer::Quad>,
|
||||
instance_count: usize,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
constant_layout: &wgpu::BindGroupLayout,
|
||||
) -> Self {
|
||||
let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("iced_wgpu::quad uniforms buffer"),
|
||||
size: mem::size_of::<Uniforms>() as wgpu::BufferAddress,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::quad uniforms bind group"),
|
||||
layout: constant_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: constants_buffer.as_entire_binding(),
|
||||
}],
|
||||
});
|
||||
|
||||
let instances = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu::quad instance buffer",
|
||||
INITIAL_INSTANCES,
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
Self {
|
||||
constants,
|
||||
constants_buffer,
|
||||
instances,
|
||||
instance_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
instances: &[layer::Quad],
|
||||
transformation: Transformation,
|
||||
scale: f32,
|
||||
) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Wgpu::Quad", "DRAW").entered();
|
||||
let _ = info_span!("Wgpu::Quad", "PREPARE").entered();
|
||||
|
||||
let uniforms = Uniforms::new(transformation, scale);
|
||||
|
||||
{
|
||||
let mut constants_buffer = staging_belt.write_buffer(
|
||||
encoder,
|
||||
&self.constants_buffer,
|
||||
0,
|
||||
wgpu::BufferSize::new(mem::size_of::<Uniforms>() as u64)
|
||||
.unwrap(),
|
||||
device,
|
||||
);
|
||||
queue.write_buffer(
|
||||
&self.constants_buffer,
|
||||
0,
|
||||
bytemuck::bytes_of(&uniforms),
|
||||
);
|
||||
|
||||
constants_buffer.copy_from_slice(bytemuck::bytes_of(&uniforms));
|
||||
}
|
||||
let _ = self.instances.resize(device, instances.len());
|
||||
self.instances.write(queue, 0, instances);
|
||||
self.instance_count = instances.len();
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
let total = instances.len();
|
||||
pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Wgpu::Quad", "DRAW").entered();
|
||||
|
||||
while i < total {
|
||||
let end = (i + MAX_INSTANCES).min(total);
|
||||
let amount = end - i;
|
||||
render_pass.set_bind_group(0, &self.constants, &[]);
|
||||
render_pass.set_vertex_buffer(1, self.instances.slice(..));
|
||||
|
||||
let instance_bytes = bytemuck::cast_slice(&instances[i..end]);
|
||||
|
||||
let mut instance_buffer = staging_belt.write_buffer(
|
||||
encoder,
|
||||
&self.instances,
|
||||
0,
|
||||
wgpu::BufferSize::new(instance_bytes.len() as u64).unwrap(),
|
||||
device,
|
||||
);
|
||||
|
||||
instance_buffer.copy_from_slice(instance_bytes);
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Wgpu::Quad", "BEGIN_RENDER_PASS").enter();
|
||||
|
||||
{
|
||||
let mut render_pass =
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("iced_wgpu::quad render pass"),
|
||||
color_attachments: &[Some(
|
||||
wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: true,
|
||||
},
|
||||
},
|
||||
)],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_bind_group(0, &self.constants, &[]);
|
||||
render_pass.set_index_buffer(
|
||||
self.indices.slice(..),
|
||||
wgpu::IndexFormat::Uint16,
|
||||
);
|
||||
render_pass.set_vertex_buffer(0, self.vertices.slice(..));
|
||||
render_pass.set_vertex_buffer(1, self.instances.slice(..));
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
bounds.width,
|
||||
// TODO: Address anti-aliasing adjustments properly
|
||||
bounds.height,
|
||||
);
|
||||
|
||||
render_pass.draw_indexed(
|
||||
0..QUAD_INDICES.len() as u32,
|
||||
0,
|
||||
0..amount as u32,
|
||||
);
|
||||
}
|
||||
|
||||
i += MAX_INSTANCES;
|
||||
}
|
||||
render_pass.draw_indexed(
|
||||
0..QUAD_INDICES.len() as u32,
|
||||
0,
|
||||
0..self.instance_count as u32,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,7 +297,7 @@ const QUAD_VERTS: [Vertex; 4] = [
|
|||
},
|
||||
];
|
||||
|
||||
const MAX_INSTANCES: usize = 100_000;
|
||||
const INITIAL_INSTANCES: usize = 10_000;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
//! Configure a renderer.
|
||||
use std::fmt;
|
||||
|
||||
pub use crate::Antialiasing;
|
||||
|
||||
use crate::Font;
|
||||
|
||||
/// The settings of a [`Backend`].
|
||||
///
|
||||
/// [`Backend`]: crate::Backend
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Settings {
|
||||
/// The present mode of the [`Backend`].
|
||||
///
|
||||
|
|
@ -16,42 +16,20 @@ pub struct Settings {
|
|||
/// The internal graphics backend to use.
|
||||
pub internal_backend: wgpu::Backends,
|
||||
|
||||
/// 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 default [`Font`] to use.
|
||||
pub default_font: Font,
|
||||
|
||||
/// The default size of text.
|
||||
///
|
||||
/// By default, it will be set to `16.0`.
|
||||
pub default_text_size: f32,
|
||||
|
||||
/// If enabled, spread text workload in multiple threads when multiple cores
|
||||
/// are available.
|
||||
///
|
||||
/// By default, it is disabled.
|
||||
pub text_multithreading: bool,
|
||||
|
||||
/// The antialiasing strategy that will be used for triangle primitives.
|
||||
///
|
||||
/// By default, it is `None`.
|
||||
pub antialiasing: Option<Antialiasing>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Settings {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Settings")
|
||||
.field("present_mode", &self.present_mode)
|
||||
.field("internal_backend", &self.internal_backend)
|
||||
// Instead of printing the font bytes, we simply show a `bool` indicating if using a default font or not.
|
||||
.field("default_font", &self.default_font.is_some())
|
||||
.field("default_text_size", &self.default_text_size)
|
||||
.field("text_multithreading", &self.text_multithreading)
|
||||
.field("antialiasing", &self.antialiasing)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
/// Creates new [`Settings`] using environment configuration.
|
||||
///
|
||||
|
|
@ -81,9 +59,8 @@ impl Default for Settings {
|
|||
Settings {
|
||||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
internal_backend: wgpu::Backends::all(),
|
||||
default_font: None,
|
||||
default_text_size: 20.0,
|
||||
text_multithreading: false,
|
||||
default_font: Font::SansSerif,
|
||||
default_text_size: 16.0,
|
||||
antialiasing: None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
565
wgpu/src/text.rs
565
wgpu/src/text.rs
|
|
@ -1,128 +1,267 @@
|
|||
use crate::Transformation;
|
||||
|
||||
use iced_graphics::font;
|
||||
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
use wgpu_glyph::ab_glyph;
|
||||
|
||||
pub use iced_native::text::Hit;
|
||||
|
||||
#[derive(Debug)]
|
||||
use iced_graphics::layer::Text;
|
||||
use iced_native::alignment;
|
||||
use iced_native::{Color, Font, Rectangle, Size};
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::hash_map;
|
||||
use std::hash::{BuildHasher, Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Pipeline {
|
||||
draw_brush: RefCell<wgpu_glyph::GlyphBrush<()>>,
|
||||
draw_font_map: RefCell<HashMap<String, wgpu_glyph::FontId>>,
|
||||
measure_brush: RefCell<glyph_brush::GlyphBrush<()>>,
|
||||
system: Option<System>,
|
||||
renderers: Vec<glyphon::TextRenderer>,
|
||||
atlas: glyphon::TextAtlas,
|
||||
prepare_layer: usize,
|
||||
}
|
||||
|
||||
#[ouroboros::self_referencing]
|
||||
struct System {
|
||||
fonts: glyphon::FontSystem,
|
||||
|
||||
#[borrows(fonts)]
|
||||
#[not_covariant]
|
||||
measurement_cache: RefCell<Cache<'this>>,
|
||||
|
||||
#[borrows(fonts)]
|
||||
#[not_covariant]
|
||||
render_cache: Cache<'this>,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
format: wgpu::TextureFormat,
|
||||
default_font: Option<&[u8]>,
|
||||
multithreading: bool,
|
||||
) -> Self {
|
||||
let default_font = default_font.map(|slice| slice.to_vec());
|
||||
|
||||
// TODO: Font customization
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
#[cfg(feature = "default_system_font")]
|
||||
let default_font = {
|
||||
default_font.or_else(|| {
|
||||
font::Source::new()
|
||||
.load(&[font::Family::SansSerif, font::Family::Serif])
|
||||
.ok()
|
||||
})
|
||||
};
|
||||
|
||||
let default_font =
|
||||
default_font.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_builder =
|
||||
wgpu_glyph::GlyphBrushBuilder::using_font(font.clone())
|
||||
.initial_cache_size((2048, 2048))
|
||||
.draw_cache_multithread(multithreading);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true);
|
||||
|
||||
let draw_brush = draw_brush_builder.build(device, format);
|
||||
|
||||
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),
|
||||
system: Some(
|
||||
SystemBuilder {
|
||||
fonts: glyphon::FontSystem::new_with_fonts(
|
||||
[glyphon::fontdb::Source::Binary(Arc::new(
|
||||
include_bytes!("../fonts/Iced-Icons.ttf")
|
||||
.as_slice(),
|
||||
))]
|
||||
.into_iter(),
|
||||
),
|
||||
measurement_cache_builder: |_| RefCell::new(Cache::new()),
|
||||
render_cache_builder: |_| Cache::new(),
|
||||
}
|
||||
.build(),
|
||||
),
|
||||
renderers: Vec::new(),
|
||||
atlas: glyphon::TextAtlas::new(device, queue, format),
|
||||
prepare_layer: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queue(&mut self, section: wgpu_glyph::Section<'_>) {
|
||||
self.draw_brush.borrow_mut().queue(section);
|
||||
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
||||
let heads = self.system.take().unwrap().into_heads();
|
||||
|
||||
let (locale, mut db) = heads.fonts.into_locale_and_db();
|
||||
|
||||
db.load_font_source(glyphon::fontdb::Source::Binary(Arc::new(
|
||||
bytes.into_owned(),
|
||||
)));
|
||||
|
||||
self.system = Some(
|
||||
SystemBuilder {
|
||||
fonts: glyphon::FontSystem::new_with_locale_and_db(locale, db),
|
||||
measurement_cache_builder: |_| RefCell::new(Cache::new()),
|
||||
render_cache_builder: |_| Cache::new(),
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn draw_queued(
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
staging_belt: &mut wgpu::util::StagingBelt,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
transformation: Transformation,
|
||||
region: wgpu_glyph::Region,
|
||||
) {
|
||||
self.draw_brush
|
||||
.borrow_mut()
|
||||
.draw_queued_with_transform_and_scissoring(
|
||||
queue: &wgpu::Queue,
|
||||
sections: &[Text<'_>],
|
||||
bounds: Rectangle,
|
||||
scale_factor: f32,
|
||||
target_size: Size<u32>,
|
||||
) -> bool {
|
||||
self.system.as_mut().unwrap().with_mut(|fields| {
|
||||
if self.renderers.len() <= self.prepare_layer {
|
||||
self.renderers
|
||||
.push(glyphon::TextRenderer::new(device, queue));
|
||||
}
|
||||
|
||||
let renderer = &mut self.renderers[self.prepare_layer];
|
||||
|
||||
let keys: Vec<_> = sections
|
||||
.iter()
|
||||
.map(|section| {
|
||||
let (key, _) = fields.render_cache.allocate(
|
||||
fields.fonts,
|
||||
Key {
|
||||
content: section.content,
|
||||
size: section.size * scale_factor,
|
||||
font: section.font,
|
||||
bounds: Size {
|
||||
width: (section.bounds.width * scale_factor)
|
||||
.ceil(),
|
||||
height: (section.bounds.height * scale_factor)
|
||||
.ceil(),
|
||||
},
|
||||
color: section.color,
|
||||
},
|
||||
);
|
||||
|
||||
key
|
||||
})
|
||||
.collect();
|
||||
|
||||
let bounds = glyphon::TextBounds {
|
||||
left: (bounds.x * scale_factor) as i32,
|
||||
top: (bounds.y * scale_factor) as i32,
|
||||
right: ((bounds.x + bounds.width) * scale_factor) as i32,
|
||||
bottom: ((bounds.y + bounds.height) * scale_factor) as i32,
|
||||
};
|
||||
|
||||
let text_areas =
|
||||
sections.iter().zip(keys.iter()).map(|(section, key)| {
|
||||
let buffer = fields
|
||||
.render_cache
|
||||
.get(key)
|
||||
.expect("Get cached buffer");
|
||||
|
||||
let x = section.bounds.x * scale_factor;
|
||||
let y = section.bounds.y * scale_factor;
|
||||
|
||||
let (total_lines, max_width) = buffer
|
||||
.layout_runs()
|
||||
.enumerate()
|
||||
.fold((0, 0.0), |(_, max), (i, buffer)| {
|
||||
(i + 1, buffer.line_w.max(max))
|
||||
});
|
||||
|
||||
let total_height =
|
||||
total_lines as f32 * section.size * 1.2 * scale_factor;
|
||||
|
||||
let left = match section.horizontal_alignment {
|
||||
alignment::Horizontal::Left => x,
|
||||
alignment::Horizontal::Center => x - max_width / 2.0,
|
||||
alignment::Horizontal::Right => x - max_width,
|
||||
};
|
||||
|
||||
let top = match section.vertical_alignment {
|
||||
alignment::Vertical::Top => y,
|
||||
alignment::Vertical::Center => y - total_height / 2.0,
|
||||
alignment::Vertical::Bottom => y - total_height,
|
||||
};
|
||||
|
||||
glyphon::TextArea {
|
||||
buffer,
|
||||
left: left as i32,
|
||||
top: top as i32,
|
||||
bounds,
|
||||
}
|
||||
});
|
||||
|
||||
let result = renderer.prepare(
|
||||
device,
|
||||
staging_belt,
|
||||
encoder,
|
||||
target,
|
||||
transformation.into(),
|
||||
region,
|
||||
)
|
||||
.expect("Draw text");
|
||||
queue,
|
||||
&mut self.atlas,
|
||||
glyphon::Resolution {
|
||||
width: target_size.width,
|
||||
height: target_size.height,
|
||||
},
|
||||
text_areas,
|
||||
glyphon::Color::rgb(0, 0, 0),
|
||||
&mut glyphon::SwashCache::new(fields.fonts),
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(()) => {
|
||||
self.prepare_layer += 1;
|
||||
|
||||
true
|
||||
}
|
||||
Err(glyphon::PrepareError::AtlasFull(content_type)) => {
|
||||
self.prepare_layer = 0;
|
||||
|
||||
#[allow(clippy::needless_bool)]
|
||||
if self.atlas.grow(device, content_type) {
|
||||
false
|
||||
} else {
|
||||
// If the atlas cannot grow, then all bets are off.
|
||||
// Instead of panicking, we will just pray that the result
|
||||
// will be somewhat readable...
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render<'a>(
|
||||
&'a self,
|
||||
layer: usize,
|
||||
bounds: Rectangle<u32>,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
let renderer = &self.renderers[layer];
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
bounds.width,
|
||||
bounds.height,
|
||||
);
|
||||
|
||||
renderer
|
||||
.render(&self.atlas, render_pass)
|
||||
.expect("Render text");
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.atlas.trim();
|
||||
|
||||
self.system
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.with_render_cache_mut(|cache| cache.trim());
|
||||
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
|
||||
pub fn measure(
|
||||
&self,
|
||||
content: &str,
|
||||
size: f32,
|
||||
font: iced_native::Font,
|
||||
bounds: iced_native::Size,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
) -> (f32, f32) {
|
||||
use wgpu_glyph::GlyphCruncher;
|
||||
self.system.as_ref().unwrap().with(|fields| {
|
||||
let mut measurement_cache = fields.measurement_cache.borrow_mut();
|
||||
|
||||
let wgpu_glyph::FontId(font_id) = self.find_font(font);
|
||||
let (_, paragraph) = measurement_cache.allocate(
|
||||
fields.fonts,
|
||||
Key {
|
||||
content,
|
||||
size,
|
||||
font,
|
||||
bounds,
|
||||
color: Color::BLACK,
|
||||
},
|
||||
);
|
||||
|
||||
let section = wgpu_glyph::Section {
|
||||
bounds: (bounds.width, bounds.height),
|
||||
text: vec![wgpu_glyph::Text {
|
||||
text: content,
|
||||
scale: size.into(),
|
||||
font_id: wgpu_glyph::FontId(font_id),
|
||||
extra: wgpu_glyph::Extra::default(),
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
let (total_lines, max_width) = paragraph
|
||||
.layout_runs()
|
||||
.enumerate()
|
||||
.fold((0, 0.0), |(_, max), (i, buffer)| {
|
||||
(i + 1, buffer.line_w.max(max))
|
||||
});
|
||||
|
||||
if let Some(bounds) =
|
||||
self.measure_brush.borrow_mut().glyph_bounds(section)
|
||||
{
|
||||
(bounds.width().ceil(), bounds.height().ceil())
|
||||
} else {
|
||||
(0.0, 0.0)
|
||||
}
|
||||
(max_width, size * 1.2 * total_lines as f32)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn hit_test(
|
||||
|
|
@ -132,134 +271,148 @@ impl Pipeline {
|
|||
font: iced_native::Font,
|
||||
bounds: iced_native::Size,
|
||||
point: iced_native::Point,
|
||||
nearest_only: bool,
|
||||
_nearest_only: bool,
|
||||
) -> Option<Hit> {
|
||||
use wgpu_glyph::GlyphCruncher;
|
||||
self.system.as_ref().unwrap().with(|fields| {
|
||||
let mut measurement_cache = fields.measurement_cache.borrow_mut();
|
||||
|
||||
let wgpu_glyph::FontId(font_id) = self.find_font(font);
|
||||
let (_, paragraph) = measurement_cache.allocate(
|
||||
fields.fonts,
|
||||
Key {
|
||||
content,
|
||||
size,
|
||||
font,
|
||||
bounds,
|
||||
color: Color::BLACK,
|
||||
},
|
||||
);
|
||||
|
||||
let section = wgpu_glyph::Section {
|
||||
bounds: (bounds.width, bounds.height),
|
||||
text: vec![wgpu_glyph::Text {
|
||||
text: content,
|
||||
scale: size.into(),
|
||||
font_id: wgpu_glyph::FontId(font_id),
|
||||
extra: wgpu_glyph::Extra::default(),
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
let cursor = paragraph.hit(point.x, point.y)?;
|
||||
|
||||
let mut mb = self.measure_brush.borrow_mut();
|
||||
|
||||
// The underlying type is FontArc, so clones are cheap.
|
||||
use wgpu_glyph::ab_glyph::{Font, ScaleFont};
|
||||
let font = mb.fonts()[font_id].clone().into_scaled(size);
|
||||
|
||||
// Implements an iterator over the glyph bounding boxes.
|
||||
let bounds = mb.glyphs(section).map(
|
||||
|wgpu_glyph::SectionGlyph {
|
||||
byte_index, glyph, ..
|
||||
}| {
|
||||
(
|
||||
*byte_index,
|
||||
iced_native::Rectangle::new(
|
||||
iced_native::Point::new(
|
||||
glyph.position.x - font.h_side_bearing(glyph.id),
|
||||
glyph.position.y - font.ascent(),
|
||||
),
|
||||
iced_native::Size::new(
|
||||
font.h_advance(glyph.id),
|
||||
font.ascent() - font.descent(),
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
// Implements computation of the character index based on the byte index
|
||||
// within the input string.
|
||||
let char_index = |byte_index| {
|
||||
let mut b_count = 0;
|
||||
for (i, utf8_len) in
|
||||
content.chars().map(|c| c.len_utf8()).enumerate()
|
||||
{
|
||||
if byte_index < (b_count + utf8_len) {
|
||||
return i;
|
||||
}
|
||||
b_count += utf8_len;
|
||||
}
|
||||
|
||||
byte_index
|
||||
};
|
||||
|
||||
if !nearest_only {
|
||||
for (idx, bounds) in bounds.clone() {
|
||||
if bounds.contains(point) {
|
||||
return Some(Hit::CharOffset(char_index(idx)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nearest = bounds
|
||||
.map(|(index, bounds)| (index, bounds.center()))
|
||||
.min_by(|(_, center_a), (_, center_b)| {
|
||||
center_a
|
||||
.distance(point)
|
||||
.partial_cmp(¢er_b.distance(point))
|
||||
.unwrap_or(std::cmp::Ordering::Greater)
|
||||
});
|
||||
|
||||
nearest.map(|(idx, center)| {
|
||||
Hit::NearestCharOffset(char_index(idx), point - center)
|
||||
Some(Hit::CharOffset(cursor.index))
|
||||
})
|
||||
}
|
||||
|
||||
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(|_, _| {}, |_| {});
|
||||
self.system
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.with_measurement_cache_mut(|cache| cache.borrow_mut().trim());
|
||||
}
|
||||
}
|
||||
|
||||
match action {
|
||||
Ok(_) => break,
|
||||
Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => {
|
||||
let (width, height) = suggested;
|
||||
fn to_family(font: Font) -> glyphon::Family<'static> {
|
||||
match font {
|
||||
Font::Name(name) => glyphon::Family::Name(name),
|
||||
Font::SansSerif => glyphon::Family::SansSerif,
|
||||
Font::Serif => glyphon::Family::Serif,
|
||||
Font::Cursive => glyphon::Family::Cursive,
|
||||
Font::Fantasy => glyphon::Family::Fantasy,
|
||||
Font::Monospace => glyphon::Family::Monospace,
|
||||
}
|
||||
}
|
||||
|
||||
self.measure_brush
|
||||
.borrow_mut()
|
||||
.resize_texture(width, height);
|
||||
}
|
||||
}
|
||||
struct Cache<'a> {
|
||||
entries: FxHashMap<KeyHash, glyphon::Buffer<'a>>,
|
||||
recently_used: FxHashSet<KeyHash>,
|
||||
hasher: HashBuilder,
|
||||
trim_count: usize,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
type HashBuilder = twox_hash::RandomXxHashBuilder64;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>;
|
||||
|
||||
impl<'a> Cache<'a> {
|
||||
const TRIM_INTERVAL: usize = 300;
|
||||
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
entries: FxHashMap::default(),
|
||||
recently_used: FxHashSet::default(),
|
||||
hasher: HashBuilder::default(),
|
||||
trim_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_font(&self, font: iced_native::Font) -> wgpu_glyph::FontId {
|
||||
match font {
|
||||
iced_native::Font::Default => wgpu_glyph::FontId(0),
|
||||
iced_native::Font::External { name, bytes } => {
|
||||
if let Some(font_id) = self.draw_font_map.borrow().get(name) {
|
||||
return *font_id;
|
||||
}
|
||||
fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'a>> {
|
||||
self.entries.get(key)
|
||||
}
|
||||
|
||||
let font = ab_glyph::FontArc::try_from_slice(bytes)
|
||||
.expect("Load font");
|
||||
fn allocate(
|
||||
&mut self,
|
||||
fonts: &'a glyphon::FontSystem,
|
||||
key: Key<'_>,
|
||||
) -> (KeyHash, &mut glyphon::Buffer<'a>) {
|
||||
let hash = {
|
||||
let mut hasher = self.hasher.build_hasher();
|
||||
|
||||
let _ = self.measure_brush.borrow_mut().add_font(font.clone());
|
||||
key.content.hash(&mut hasher);
|
||||
key.size.to_bits().hash(&mut hasher);
|
||||
key.font.hash(&mut hasher);
|
||||
key.bounds.width.to_bits().hash(&mut hasher);
|
||||
key.bounds.height.to_bits().hash(&mut hasher);
|
||||
key.color.into_rgba8().hash(&mut hasher);
|
||||
|
||||
let font_id = self.draw_brush.borrow_mut().add_font(font);
|
||||
hasher.finish()
|
||||
};
|
||||
|
||||
let _ = self
|
||||
.draw_font_map
|
||||
.borrow_mut()
|
||||
.insert(String::from(name), font_id);
|
||||
if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) {
|
||||
let metrics = glyphon::Metrics::new(key.size, key.size * 1.2);
|
||||
let mut buffer = glyphon::Buffer::new(fonts, metrics);
|
||||
|
||||
font_id
|
||||
}
|
||||
buffer.set_size(
|
||||
key.bounds.width,
|
||||
key.bounds.height.max(key.size * 1.2),
|
||||
);
|
||||
buffer.set_text(
|
||||
key.content,
|
||||
glyphon::Attrs::new()
|
||||
.family(to_family(key.font))
|
||||
.color({
|
||||
let [r, g, b, a] = key.color.into_linear();
|
||||
|
||||
glyphon::Color::rgba(
|
||||
(r * 255.0) as u8,
|
||||
(g * 255.0) as u8,
|
||||
(b * 255.0) as u8,
|
||||
(a * 255.0) as u8,
|
||||
)
|
||||
})
|
||||
.monospaced(matches!(key.font, Font::Monospace)),
|
||||
);
|
||||
|
||||
let _ = entry.insert(buffer);
|
||||
}
|
||||
|
||||
let _ = self.recently_used.insert(hash);
|
||||
|
||||
(hash, self.entries.get_mut(&hash).unwrap())
|
||||
}
|
||||
|
||||
fn trim(&mut self) {
|
||||
if self.trim_count >= Self::TRIM_INTERVAL {
|
||||
self.entries
|
||||
.retain(|key, _| self.recently_used.contains(key));
|
||||
|
||||
self.recently_used.clear();
|
||||
|
||||
self.trim_count = 0;
|
||||
} else {
|
||||
self.trim_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Key<'a> {
|
||||
content: &'a str,
|
||||
size: f32,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
type KeyHash = u64;
|
||||
|
|
|
|||
|
|
@ -14,50 +14,55 @@ use tracing::info_span;
|
|||
#[derive(Debug)]
|
||||
pub struct Pipeline {
|
||||
blit: Option<msaa::Blit>,
|
||||
index_buffer: Buffer<u32>,
|
||||
index_strides: Vec<u32>,
|
||||
solid: solid::Pipeline,
|
||||
|
||||
/// Gradients are currently not supported on WASM targets due to their need of storage buffers.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
gradient: gradient::Pipeline,
|
||||
|
||||
layers: Vec<Layer>,
|
||||
prepare_layer: usize,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
#[derive(Debug)]
|
||||
struct Layer {
|
||||
index_buffer: Buffer<u32>,
|
||||
index_strides: Vec<u32>,
|
||||
solid: solid::Layer,
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
gradient: gradient::Layer,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
antialiasing: Option<settings::Antialiasing>,
|
||||
) -> Pipeline {
|
||||
Pipeline {
|
||||
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
|
||||
solid: &solid::Pipeline,
|
||||
#[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline,
|
||||
) -> Self {
|
||||
Self {
|
||||
index_buffer: Buffer::new(
|
||||
device,
|
||||
"iced_wgpu::triangle vertex buffer",
|
||||
"iced_wgpu::triangle index buffer",
|
||||
wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
|
||||
),
|
||||
index_strides: Vec::new(),
|
||||
solid: solid::Pipeline::new(device, format, antialiasing),
|
||||
solid: solid::Layer::new(device, &solid.constants_layout),
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
gradient: gradient::Pipeline::new(device, format, antialiasing),
|
||||
gradient: gradient::Layer::new(device, &gradient.constants_layout),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
staging_belt: &mut wgpu::util::StagingBelt,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
target_size: Size<u32>,
|
||||
transformation: Transformation,
|
||||
scale_factor: f32,
|
||||
queue: &wgpu::Queue,
|
||||
solid: &solid::Pipeline,
|
||||
#[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline,
|
||||
meshes: &[Mesh<'_>],
|
||||
transformation: Transformation,
|
||||
) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Wgpu::Triangle", "DRAW").entered();
|
||||
|
||||
// Count the total amount of vertices & indices we need to handle
|
||||
let count = mesh::attribute_count_of(meshes);
|
||||
|
||||
|
|
@ -75,6 +80,7 @@ impl Pipeline {
|
|||
.resize(device, count.gradient_vertices);
|
||||
|
||||
// Prepare dynamic buffers & data store for writing
|
||||
self.index_buffer.clear();
|
||||
self.index_strides.clear();
|
||||
self.solid.vertices.clear();
|
||||
self.solid.uniforms.clear();
|
||||
|
|
@ -99,13 +105,8 @@ impl Pipeline {
|
|||
let transform =
|
||||
transformation * Transformation::translate(origin.x, origin.y);
|
||||
|
||||
let new_index_offset = self.index_buffer.write(
|
||||
device,
|
||||
staging_belt,
|
||||
encoder,
|
||||
index_offset,
|
||||
indices,
|
||||
);
|
||||
let new_index_offset =
|
||||
self.index_buffer.write(queue, index_offset, indices);
|
||||
|
||||
index_offset += new_index_offset;
|
||||
self.index_strides.push(indices.len() as u32);
|
||||
|
|
@ -116,9 +117,7 @@ impl Pipeline {
|
|||
self.solid.uniforms.push(&solid::Uniforms::new(transform));
|
||||
|
||||
let written_bytes = self.solid.vertices.write(
|
||||
device,
|
||||
staging_belt,
|
||||
encoder,
|
||||
queue,
|
||||
solid_vertex_offset,
|
||||
&buffers.vertices,
|
||||
);
|
||||
|
|
@ -130,9 +129,7 @@ impl Pipeline {
|
|||
buffers, gradient, ..
|
||||
} => {
|
||||
let written_bytes = self.gradient.vertices.write(
|
||||
device,
|
||||
staging_belt,
|
||||
encoder,
|
||||
queue,
|
||||
gradient_vertex_offset,
|
||||
&buffers.vertices,
|
||||
);
|
||||
|
|
@ -196,14 +193,14 @@ impl Pipeline {
|
|||
let uniforms_resized = self.solid.uniforms.resize(device);
|
||||
|
||||
if uniforms_resized {
|
||||
self.solid.bind_group = solid::Pipeline::bind_group(
|
||||
self.solid.constants = solid::Layer::bind_group(
|
||||
device,
|
||||
self.solid.uniforms.raw(),
|
||||
&self.solid.bind_group_layout,
|
||||
&solid.constants_layout,
|
||||
)
|
||||
}
|
||||
|
||||
self.solid.uniforms.write(device, staging_belt, encoder);
|
||||
self.solid.uniforms.write(queue);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
|
@ -218,22 +215,169 @@ impl Pipeline {
|
|||
let storage_resized = self.gradient.storage.resize(device);
|
||||
|
||||
if uniforms_resized || storage_resized {
|
||||
self.gradient.bind_group = gradient::Pipeline::bind_group(
|
||||
self.gradient.constants = gradient::Layer::bind_group(
|
||||
device,
|
||||
self.gradient.uniforms.raw(),
|
||||
self.gradient.storage.raw(),
|
||||
&self.gradient.bind_group_layout,
|
||||
&gradient.constants_layout,
|
||||
);
|
||||
}
|
||||
|
||||
// Write to GPU
|
||||
self.gradient.uniforms.write(device, staging_belt, encoder);
|
||||
self.gradient.storage.write(device, staging_belt, encoder);
|
||||
self.gradient.uniforms.write(queue);
|
||||
self.gradient.storage.write(queue);
|
||||
|
||||
// Cleanup
|
||||
self.gradient.color_stop_offset = 0;
|
||||
self.gradient.color_stops_pending_write.color_stops.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn render<'a>(
|
||||
&'a self,
|
||||
solid: &'a solid::Pipeline,
|
||||
#[cfg(not(target_arch = "wasm32"))] gradient: &'a gradient::Pipeline,
|
||||
meshes: &[Mesh<'_>],
|
||||
scale_factor: f32,
|
||||
render_pass: &mut wgpu::RenderPass<'a>,
|
||||
) {
|
||||
let mut num_solids = 0;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let mut num_gradients = 0;
|
||||
let mut last_is_solid = None;
|
||||
|
||||
for (index, mesh) in meshes.iter().enumerate() {
|
||||
let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
clip_bounds.x,
|
||||
clip_bounds.y,
|
||||
clip_bounds.width,
|
||||
clip_bounds.height,
|
||||
);
|
||||
|
||||
match mesh {
|
||||
Mesh::Solid { .. } => {
|
||||
if !last_is_solid.unwrap_or(false) {
|
||||
render_pass.set_pipeline(&solid.pipeline);
|
||||
|
||||
last_is_solid = Some(true);
|
||||
}
|
||||
|
||||
render_pass.set_bind_group(
|
||||
0,
|
||||
&self.solid.constants,
|
||||
&[self.solid.uniforms.offset_at_index(num_solids)],
|
||||
);
|
||||
|
||||
render_pass.set_vertex_buffer(
|
||||
0,
|
||||
self.solid.vertices.slice_from_index(num_solids),
|
||||
);
|
||||
|
||||
num_solids += 1;
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
Mesh::Gradient { .. } => {
|
||||
if last_is_solid.unwrap_or(true) {
|
||||
render_pass.set_pipeline(&gradient.pipeline);
|
||||
|
||||
last_is_solid = Some(false);
|
||||
}
|
||||
|
||||
render_pass.set_bind_group(
|
||||
0,
|
||||
&self.gradient.constants,
|
||||
&[self
|
||||
.gradient
|
||||
.uniforms
|
||||
.offset_at_index(num_gradients)],
|
||||
);
|
||||
|
||||
render_pass.set_vertex_buffer(
|
||||
0,
|
||||
self.gradient.vertices.slice_from_index(num_gradients),
|
||||
);
|
||||
|
||||
num_gradients += 1;
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Mesh::Gradient { .. } => {}
|
||||
};
|
||||
|
||||
render_pass.set_index_buffer(
|
||||
self.index_buffer.slice_from_index(index),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
);
|
||||
|
||||
render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
antialiasing: Option<settings::Antialiasing>,
|
||||
) -> Pipeline {
|
||||
Pipeline {
|
||||
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
|
||||
solid: solid::Pipeline::new(device, format, antialiasing),
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
gradient: gradient::Pipeline::new(device, format, antialiasing),
|
||||
|
||||
layers: Vec::new(),
|
||||
prepare_layer: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
meshes: &[Mesh<'_>],
|
||||
transformation: Transformation,
|
||||
) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Wgpu::Triangle", "PREPARE").entered();
|
||||
|
||||
if self.layers.len() <= self.prepare_layer {
|
||||
self.layers.push(Layer::new(
|
||||
device,
|
||||
&self.solid,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
&self.gradient,
|
||||
));
|
||||
}
|
||||
|
||||
let layer = &mut self.layers[self.prepare_layer];
|
||||
layer.prepare(
|
||||
device,
|
||||
queue,
|
||||
&self.solid,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
&self.gradient,
|
||||
meshes,
|
||||
transformation,
|
||||
);
|
||||
|
||||
self.prepare_layer += 1;
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
layer: usize,
|
||||
target_size: Size<u32>,
|
||||
meshes: &[Mesh<'_>],
|
||||
scale_factor: f32,
|
||||
) {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _ = info_span!("Wgpu::Triangle", "DRAW").entered();
|
||||
|
||||
// Configure render pass
|
||||
{
|
||||
|
|
@ -268,87 +412,26 @@ impl Pipeline {
|
|||
depth_stencil_attachment: None,
|
||||
});
|
||||
|
||||
let mut num_solids = 0;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let mut num_gradients = 0;
|
||||
let mut last_is_solid = None;
|
||||
let layer = &mut self.layers[layer];
|
||||
|
||||
for (index, mesh) in meshes.iter().enumerate() {
|
||||
let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
|
||||
|
||||
render_pass.set_scissor_rect(
|
||||
clip_bounds.x,
|
||||
clip_bounds.y,
|
||||
clip_bounds.width,
|
||||
clip_bounds.height,
|
||||
);
|
||||
|
||||
match mesh {
|
||||
Mesh::Solid { .. } => {
|
||||
if !last_is_solid.unwrap_or(false) {
|
||||
render_pass.set_pipeline(&self.solid.pipeline);
|
||||
|
||||
last_is_solid = Some(true);
|
||||
}
|
||||
|
||||
render_pass.set_bind_group(
|
||||
0,
|
||||
&self.solid.bind_group,
|
||||
&[self.solid.uniforms.offset_at_index(num_solids)],
|
||||
);
|
||||
|
||||
render_pass.set_vertex_buffer(
|
||||
0,
|
||||
self.solid.vertices.slice_from_index(num_solids),
|
||||
);
|
||||
|
||||
num_solids += 1;
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
Mesh::Gradient { .. } => {
|
||||
if last_is_solid.unwrap_or(true) {
|
||||
render_pass.set_pipeline(&self.gradient.pipeline);
|
||||
|
||||
last_is_solid = Some(false);
|
||||
}
|
||||
|
||||
render_pass.set_bind_group(
|
||||
0,
|
||||
&self.gradient.bind_group,
|
||||
&[self
|
||||
.gradient
|
||||
.uniforms
|
||||
.offset_at_index(num_gradients)],
|
||||
);
|
||||
|
||||
render_pass.set_vertex_buffer(
|
||||
0,
|
||||
self.gradient
|
||||
.vertices
|
||||
.slice_from_index(num_gradients),
|
||||
);
|
||||
|
||||
num_gradients += 1;
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Mesh::Gradient { .. } => {}
|
||||
};
|
||||
|
||||
render_pass.set_index_buffer(
|
||||
self.index_buffer.slice_from_index(index),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
);
|
||||
|
||||
render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1);
|
||||
}
|
||||
layer.render(
|
||||
&self.solid,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
&self.gradient,
|
||||
meshes,
|
||||
scale_factor,
|
||||
&mut render_pass,
|
||||
);
|
||||
}
|
||||
|
||||
self.index_buffer.clear();
|
||||
|
||||
if let Some(blit) = &mut self.blit {
|
||||
blit.draw(encoder, target);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.prepare_layer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn fragment_target(
|
||||
|
|
@ -390,10 +473,62 @@ mod solid {
|
|||
#[derive(Debug)]
|
||||
pub struct Pipeline {
|
||||
pub pipeline: wgpu::RenderPipeline,
|
||||
pub constants_layout: wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Layer {
|
||||
pub vertices: Buffer<triangle::ColoredVertex2D>,
|
||||
pub uniforms: dynamic::Buffer<Uniforms>,
|
||||
pub bind_group_layout: wgpu::BindGroupLayout,
|
||||
pub bind_group: wgpu::BindGroup,
|
||||
pub constants: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
constants_layout: &wgpu::BindGroupLayout,
|
||||
) -> Self {
|
||||
let vertices = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu::triangle::solid vertex buffer",
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
let uniforms = dynamic::Buffer::uniform(
|
||||
device,
|
||||
"iced_wgpu::triangle::solid uniforms",
|
||||
);
|
||||
|
||||
let constants =
|
||||
Self::bind_group(device, uniforms.raw(), constants_layout);
|
||||
|
||||
Self {
|
||||
vertices,
|
||||
uniforms,
|
||||
constants,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_group(
|
||||
device: &wgpu::Device,
|
||||
buffer: &wgpu::Buffer,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::BindGroup {
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::triangle::solid bind group"),
|
||||
layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer,
|
||||
offset: 0,
|
||||
size: Some(Uniforms::min_size()),
|
||||
},
|
||||
),
|
||||
}],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ShaderType)]
|
||||
|
|
@ -416,18 +551,7 @@ mod solid {
|
|||
format: wgpu::TextureFormat,
|
||||
antialiasing: Option<settings::Antialiasing>,
|
||||
) -> Self {
|
||||
let vertices = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu::triangle::solid vertex buffer",
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
let uniforms = dynamic::Buffer::uniform(
|
||||
device,
|
||||
"iced_wgpu::triangle::solid uniforms",
|
||||
);
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(
|
||||
let constants_layout = device.create_bind_group_layout(
|
||||
&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("iced_wgpu::triangle::solid bind group layout"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
|
|
@ -443,13 +567,10 @@ mod solid {
|
|||
},
|
||||
);
|
||||
|
||||
let bind_group =
|
||||
Self::bind_group(device, uniforms.raw(), &bind_group_layout);
|
||||
|
||||
let layout = device.create_pipeline_layout(
|
||||
&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("iced_wgpu::triangle::solid pipeline layout"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
bind_group_layouts: &[&constants_layout],
|
||||
push_constant_ranges: &[],
|
||||
},
|
||||
);
|
||||
|
|
@ -501,33 +622,9 @@ mod solid {
|
|||
|
||||
Self {
|
||||
pipeline,
|
||||
vertices,
|
||||
uniforms,
|
||||
bind_group_layout,
|
||||
bind_group,
|
||||
constants_layout,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_group(
|
||||
device: &wgpu::Device,
|
||||
buffer: &wgpu::Buffer,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::BindGroup {
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::triangle::solid bind group"),
|
||||
layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer,
|
||||
offset: 0,
|
||||
size: Some(Uniforms::min_size()),
|
||||
},
|
||||
),
|
||||
}],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -545,15 +642,90 @@ mod gradient {
|
|||
#[derive(Debug)]
|
||||
pub struct Pipeline {
|
||||
pub pipeline: wgpu::RenderPipeline,
|
||||
pub constants_layout: wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Layer {
|
||||
pub vertices: Buffer<Vertex2D>,
|
||||
pub uniforms: dynamic::Buffer<Uniforms>,
|
||||
pub storage: dynamic::Buffer<Storage>,
|
||||
pub constants: wgpu::BindGroup,
|
||||
pub color_stop_offset: i32,
|
||||
//Need to store these and then write them all at once
|
||||
//or else they will be padded to 256 and cause gaps in the storage buffer
|
||||
pub color_stops_pending_write: Storage,
|
||||
pub bind_group_layout: wgpu::BindGroupLayout,
|
||||
pub bind_group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
constants_layout: &wgpu::BindGroupLayout,
|
||||
) -> Self {
|
||||
let vertices = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu::triangle::gradient vertex buffer",
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
let uniforms = dynamic::Buffer::uniform(
|
||||
device,
|
||||
"iced_wgpu::triangle::gradient uniforms",
|
||||
);
|
||||
|
||||
// Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static
|
||||
// sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work
|
||||
let storage = dynamic::Buffer::storage(
|
||||
device,
|
||||
"iced_wgpu::triangle::gradient storage",
|
||||
);
|
||||
|
||||
let constants = Self::bind_group(
|
||||
device,
|
||||
uniforms.raw(),
|
||||
storage.raw(),
|
||||
constants_layout,
|
||||
);
|
||||
|
||||
Self {
|
||||
vertices,
|
||||
uniforms,
|
||||
storage,
|
||||
constants,
|
||||
color_stop_offset: 0,
|
||||
color_stops_pending_write: Storage {
|
||||
color_stops: vec![],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_group(
|
||||
device: &wgpu::Device,
|
||||
uniform_buffer: &wgpu::Buffer,
|
||||
storage_buffer: &wgpu::Buffer,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::BindGroup {
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::triangle::gradient bind group"),
|
||||
layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer: uniform_buffer,
|
||||
offset: 0,
|
||||
size: Some(Uniforms::min_size()),
|
||||
},
|
||||
),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: storage_buffer.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, ShaderType)]
|
||||
|
|
@ -584,25 +756,7 @@ mod gradient {
|
|||
format: wgpu::TextureFormat,
|
||||
antialiasing: Option<settings::Antialiasing>,
|
||||
) -> Self {
|
||||
let vertices = Buffer::new(
|
||||
device,
|
||||
"iced_wgpu::triangle::gradient vertex buffer",
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
let uniforms = dynamic::Buffer::uniform(
|
||||
device,
|
||||
"iced_wgpu::triangle::gradient uniforms",
|
||||
);
|
||||
|
||||
//Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static
|
||||
// sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work
|
||||
let storage = dynamic::Buffer::storage(
|
||||
device,
|
||||
"iced_wgpu::triangle::gradient storage",
|
||||
);
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(
|
||||
let constants_layout = device.create_bind_group_layout(
|
||||
&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some(
|
||||
"iced_wgpu::triangle::gradient bind group layout",
|
||||
|
|
@ -634,19 +788,12 @@ mod gradient {
|
|||
},
|
||||
);
|
||||
|
||||
let bind_group = Pipeline::bind_group(
|
||||
device,
|
||||
uniforms.raw(),
|
||||
storage.raw(),
|
||||
&bind_group_layout,
|
||||
);
|
||||
|
||||
let layout = device.create_pipeline_layout(
|
||||
&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some(
|
||||
"iced_wgpu::triangle::gradient pipeline layout",
|
||||
),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
bind_group_layouts: &[&constants_layout],
|
||||
push_constant_ranges: &[],
|
||||
},
|
||||
);
|
||||
|
|
@ -694,44 +841,8 @@ mod gradient {
|
|||
|
||||
Self {
|
||||
pipeline,
|
||||
vertices,
|
||||
uniforms,
|
||||
storage,
|
||||
color_stop_offset: 0,
|
||||
color_stops_pending_write: Storage {
|
||||
color_stops: vec![],
|
||||
},
|
||||
bind_group_layout,
|
||||
bind_group,
|
||||
constants_layout,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind_group(
|
||||
device: &wgpu::Device,
|
||||
uniform_buffer: &wgpu::Buffer,
|
||||
storage_buffer: &wgpu::Buffer,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::BindGroup {
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("iced_wgpu::triangle::gradient bind group"),
|
||||
layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer: uniform_buffer,
|
||||
offset: 0,
|
||||
size: Some(Uniforms::min_size()),
|
||||
},
|
||||
),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: storage_buffer.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,11 @@ pub struct Compositor<Theme> {
|
|||
adapter: wgpu::Adapter,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
staging_belt: wgpu::util::StagingBelt,
|
||||
format: wgpu::TextureFormat,
|
||||
theme: PhantomData<Theme>,
|
||||
}
|
||||
|
||||
impl<Theme> Compositor<Theme> {
|
||||
const CHUNK_SIZE: u64 = 10 * 1024;
|
||||
|
||||
/// Requests a new [`Compositor`] with the given [`Settings`].
|
||||
///
|
||||
/// Returns `None` if no compatible graphics adapter could be found.
|
||||
|
|
@ -98,15 +95,12 @@ impl<Theme> Compositor<Theme> {
|
|||
.next()
|
||||
.await?;
|
||||
|
||||
let staging_belt = wgpu::util::StagingBelt::new(Self::CHUNK_SIZE);
|
||||
|
||||
Some(Compositor {
|
||||
instance,
|
||||
settings,
|
||||
adapter,
|
||||
device,
|
||||
queue,
|
||||
staging_belt,
|
||||
format,
|
||||
theme: PhantomData,
|
||||
})
|
||||
|
|
@ -114,7 +108,7 @@ impl<Theme> Compositor<Theme> {
|
|||
|
||||
/// Creates a new rendering [`Backend`] for this [`Compositor`].
|
||||
pub fn create_backend(&self) -> Backend {
|
||||
Backend::new(&self.device, self.settings, self.format)
|
||||
Backend::new(&self.device, &self.queue, self.settings, self.format)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -196,39 +190,12 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {
|
|||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let _ =
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some(
|
||||
"iced_wgpu::window::Compositor render pass",
|
||||
),
|
||||
color_attachments: &[Some(
|
||||
wgpu::RenderPassColorAttachment {
|
||||
view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear({
|
||||
let [r, g, b, a] =
|
||||
background_color.into_linear();
|
||||
|
||||
wgpu::Color {
|
||||
r: f64::from(r),
|
||||
g: f64::from(g),
|
||||
b: f64::from(b),
|
||||
a: f64::from(a),
|
||||
}
|
||||
}),
|
||||
store: true,
|
||||
},
|
||||
},
|
||||
)],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
|
||||
renderer.with_primitives(|backend, primitives| {
|
||||
backend.present(
|
||||
&self.device,
|
||||
&mut self.staging_belt,
|
||||
&self.queue,
|
||||
&mut encoder,
|
||||
Some(background_color),
|
||||
view,
|
||||
primitives,
|
||||
viewport,
|
||||
|
|
@ -237,13 +204,9 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {
|
|||
});
|
||||
|
||||
// Submit work
|
||||
self.staging_belt.finish();
|
||||
let _submission = self.queue.submit(Some(encoder.finish()));
|
||||
frame.present();
|
||||
|
||||
// Recall staging buffers
|
||||
self.staging_belt.recall();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => match error {
|
||||
|
|
|
|||
|
|
@ -851,6 +851,16 @@ pub fn run_command<A, E>(
|
|||
current_cache = user_interface.into_cache();
|
||||
*cache = current_cache;
|
||||
}
|
||||
command::Action::LoadFont { bytes, tagger } => {
|
||||
use crate::text::Renderer;
|
||||
|
||||
// TODO: Error handling (?)
|
||||
renderer.load_font(bytes);
|
||||
|
||||
proxy
|
||||
.send_event(tagger(Ok(())))
|
||||
.expect("Send message to event loop");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,14 +51,6 @@ pub struct Settings<Flags> {
|
|||
///
|
||||
/// [`Application`]: crate::Application
|
||||
pub exit_on_close_request: bool,
|
||||
|
||||
/// Whether the [`Application`] should try to build the context
|
||||
/// using OpenGL ES first then OpenGL.
|
||||
///
|
||||
/// NOTE: Only works for the `glow` backend.
|
||||
///
|
||||
/// [`Application`]: crate::Application
|
||||
pub try_opengles_first: bool,
|
||||
}
|
||||
|
||||
/// The window settings of an application.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue