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
|
- name: Check compilation of `todos` example
|
||||||
run: cargo build --package todos --target wasm32-unknown-unknown
|
run: cargo build --package todos --target wasm32-unknown-unknown
|
||||||
- name: Check compilation of `integration_wgpu` example
|
- 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"]
|
categories = ["gui"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["wgpu"]
|
|
||||||
# Enables the `Image` widget
|
# Enables the `Image` widget
|
||||||
image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"]
|
image = ["iced_wgpu/image", "image_rs"]
|
||||||
# Enables the `Svg` widget
|
# Enables the `Svg` widget
|
||||||
svg = ["iced_wgpu?/svg", "iced_glow?/svg"]
|
svg = ["iced_wgpu/svg"]
|
||||||
# Enables the `Canvas` widget
|
# Enables the `Canvas` widget
|
||||||
canvas = ["iced_graphics/canvas"]
|
canvas = ["iced_graphics/canvas"]
|
||||||
# Enables the `QRCode` widget
|
# Enables the `QRCode` widget
|
||||||
qr_code = ["iced_graphics/qr_code"]
|
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)
|
# Enables a debug view in native platforms (press F12)
|
||||||
debug = ["iced_winit/debug"]
|
debug = ["iced_winit/debug"]
|
||||||
# Enables `tokio` as the `executor::Default` on native platforms
|
# Enables `tokio` as the `executor::Default` on native platforms
|
||||||
|
|
@ -42,9 +35,7 @@ system = ["iced_winit/system"]
|
||||||
# Enables chrome traces
|
# Enables chrome traces
|
||||||
chrome-trace = [
|
chrome-trace = [
|
||||||
"iced_winit/chrome-trace",
|
"iced_winit/chrome-trace",
|
||||||
"iced_glutin?/trace",
|
"iced_wgpu/tracing",
|
||||||
"iced_wgpu?/tracing",
|
|
||||||
"iced_glow?/tracing",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
|
|
@ -55,8 +46,6 @@ members = [
|
||||||
"core",
|
"core",
|
||||||
"futures",
|
"futures",
|
||||||
"graphics",
|
"graphics",
|
||||||
"glow",
|
|
||||||
"glutin",
|
|
||||||
"lazy",
|
"lazy",
|
||||||
"native",
|
"native",
|
||||||
"style",
|
"style",
|
||||||
|
|
@ -71,8 +60,6 @@ iced_futures = { version = "0.6", path = "futures" }
|
||||||
iced_native = { version = "0.9", path = "native" }
|
iced_native = { version = "0.9", path = "native" }
|
||||||
iced_graphics = { version = "0.7", path = "graphics" }
|
iced_graphics = { version = "0.7", path = "graphics" }
|
||||||
iced_winit = { version = "0.8", path = "winit", features = ["application"] }
|
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"
|
thiserror = "1.0"
|
||||||
|
|
||||||
[dependencies.image_rs]
|
[dependencies.image_rs]
|
||||||
|
|
@ -81,10 +68,10 @@ package = "image"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[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]
|
[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]
|
[package.metadata.docs.rs]
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,45 @@
|
||||||
|
//! Load and use fonts.
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
/// A font.
|
/// A font.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum Font {
|
pub enum Font {
|
||||||
/// The default font.
|
/// The name of a font family of choice.
|
||||||
///
|
Name(&'static str),
|
||||||
/// This is normally a font configured in a renderer or loaded from the
|
|
||||||
/// system.
|
|
||||||
Default,
|
|
||||||
|
|
||||||
/// An external font.
|
/// Serif fonts represent the formal text style for a script.
|
||||||
External {
|
Serif,
|
||||||
/// The name of the external font
|
|
||||||
name: &'static str,
|
|
||||||
|
|
||||||
/// The bytes of the external font
|
/// Glyphs in sans-serif fonts, as the term is used in CSS, are generally low
|
||||||
bytes: &'static [u8],
|
/// 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 {
|
/// The weight of some text.
|
||||||
fn default() -> Font {
|
#[allow(missing_docs)]
|
||||||
Font::Default
|
#[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)]
|
#![forbid(unsafe_code, rust_2018_idioms)]
|
||||||
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
|
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
|
||||||
pub mod alignment;
|
pub mod alignment;
|
||||||
|
pub mod font;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
pub mod mouse;
|
pub mod mouse;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
|
|
@ -32,7 +33,6 @@ pub mod time;
|
||||||
mod background;
|
mod background;
|
||||||
mod color;
|
mod color;
|
||||||
mod content_fit;
|
mod content_fit;
|
||||||
mod font;
|
|
||||||
mod length;
|
mod length;
|
||||||
mod padding;
|
mod padding;
|
||||||
mod pixels;
|
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::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 {
|
const ICON_FONT: Font = Font::Name("icons");
|
||||||
name: "Icons",
|
|
||||||
bytes: include_bytes!("../fonts/icons.ttf"),
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn main() -> iced::Result {
|
pub fn main() -> iced::Result {
|
||||||
Example::run(Settings::default())
|
Example::run(Settings::default())
|
||||||
|
|
@ -20,24 +19,35 @@ struct Example {
|
||||||
enum Message {
|
enum Message {
|
||||||
DefaultChecked(bool),
|
DefaultChecked(bool),
|
||||||
CustomChecked(bool),
|
CustomChecked(bool),
|
||||||
|
FontLoaded(Result<(), font::Error>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sandbox for Example {
|
impl Application for Example {
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
|
type Flags = ();
|
||||||
|
type Executor = executor::Default;
|
||||||
|
type Theme = Theme;
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
|
||||||
Default::default()
|
(
|
||||||
|
Self::default(),
|
||||||
|
font::load(include_bytes!("../fonts/icons.ttf").as_ref())
|
||||||
|
.map(Message::FontLoaded),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
String::from("Checkbox - Iced")
|
String::from("Checkbox - Iced")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) {
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::DefaultChecked(value) => self.default_checkbox = value,
|
Message::DefaultChecked(value) => self.default_checkbox = value,
|
||||||
Message::CustomChecked(value) => self.custom_checkbox = value,
|
Message::CustomChecked(value) => self.custom_checkbox = value,
|
||||||
|
Message::FontLoaded(_) => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element<Message> {
|
fn view(&self) -> Element<Message> {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "integration_wgpu"
|
name = "integration"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
@ -8,8 +8,8 @@
|
||||||
<h1>integration_wgpu</h1>
|
<h1>integration_wgpu</h1>
|
||||||
<canvas id="iced_canvas"></canvas>
|
<canvas id="iced_canvas"></canvas>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import init from "./integration_wgpu.js";
|
import init from "./integration.js";
|
||||||
init('./integration_wgpu_bg.wasm');
|
init('./integration_bg.wasm');
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
|
@ -125,17 +125,18 @@ pub fn main() {
|
||||||
|
|
||||||
let mut resized = false;
|
let mut resized = false;
|
||||||
|
|
||||||
// Initialize staging belt
|
|
||||||
let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024);
|
|
||||||
|
|
||||||
// Initialize scene and GUI controls
|
// Initialize scene and GUI controls
|
||||||
let scene = Scene::new(&device, format);
|
let scene = Scene::new(&device, format);
|
||||||
let controls = Controls::new();
|
let controls = Controls::new();
|
||||||
|
|
||||||
// Initialize iced
|
// Initialize iced
|
||||||
let mut debug = Debug::new();
|
let mut debug = Debug::new();
|
||||||
let mut renderer =
|
let mut renderer = Renderer::new(Backend::new(
|
||||||
Renderer::new(Backend::new(&device, Settings::default(), format));
|
&device,
|
||||||
|
&queue,
|
||||||
|
Settings::default(),
|
||||||
|
format,
|
||||||
|
));
|
||||||
|
|
||||||
let mut state = program::State::new(
|
let mut state = program::State::new(
|
||||||
controls,
|
controls,
|
||||||
|
|
@ -247,8 +248,9 @@ pub fn main() {
|
||||||
renderer.with_primitives(|backend, primitive| {
|
renderer.with_primitives(|backend, primitive| {
|
||||||
backend.present(
|
backend.present(
|
||||||
&device,
|
&device,
|
||||||
&mut staging_belt,
|
&queue,
|
||||||
&mut encoder,
|
&mut encoder,
|
||||||
|
None,
|
||||||
&view,
|
&view,
|
||||||
primitive,
|
primitive,
|
||||||
&viewport,
|
&viewport,
|
||||||
|
|
@ -257,7 +259,6 @@ pub fn main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Then we submit the work
|
// Then we submit the work
|
||||||
staging_belt.finish();
|
|
||||||
queue.submit(Some(encoder.finish()));
|
queue.submit(Some(encoder.finish()));
|
||||||
frame.present();
|
frame.present();
|
||||||
|
|
||||||
|
|
@ -267,10 +268,6 @@ pub fn main() {
|
||||||
state.mouse_interaction(),
|
state.mouse_interaction(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// And recall staging buffers
|
|
||||||
staging_belt.recall();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
Err(error) => match error {
|
Err(error) => match error {
|
||||||
wgpu::SurfaceError::OutOfMemory => {
|
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::alignment::{self, Alignment};
|
||||||
use iced::event::{self, Event};
|
use iced::event::{self, Event};
|
||||||
|
use iced::font::{self, Font};
|
||||||
use iced::keyboard;
|
use iced::keyboard;
|
||||||
use iced::subscription;
|
use iced::subscription;
|
||||||
use iced::theme::{self, Theme};
|
use iced::theme::{self, Theme};
|
||||||
|
|
@ -9,7 +10,7 @@ use iced::widget::{
|
||||||
};
|
};
|
||||||
use iced::window;
|
use iced::window;
|
||||||
use iced::{Application, Element};
|
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 once_cell::sync::Lazy;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -44,6 +45,7 @@ struct State {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Message {
|
enum Message {
|
||||||
Loaded(Result<SavedState, LoadError>),
|
Loaded(Result<SavedState, LoadError>),
|
||||||
|
FontLoaded(Result<(), font::Error>),
|
||||||
Saved(Result<(), SaveError>),
|
Saved(Result<(), SaveError>),
|
||||||
InputChanged(String),
|
InputChanged(String),
|
||||||
CreateTask,
|
CreateTask,
|
||||||
|
|
@ -61,7 +63,11 @@ impl Application for Todos {
|
||||||
fn new(_flags: ()) -> (Todos, Command<Message>) {
|
fn new(_flags: ()) -> (Todos, Command<Message>) {
|
||||||
(
|
(
|
||||||
Todos::Loading,
|
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 tasks_left = tasks.iter().filter(|task| !task.completed).count();
|
||||||
|
|
||||||
let filter_button = |label, filter, current_filter| {
|
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 {
|
let button = button(label).style(if filter == current_filter {
|
||||||
theme::Button::Primary
|
theme::Button::Primary
|
||||||
|
|
@ -401,8 +407,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> {
|
||||||
tasks_left,
|
tasks_left,
|
||||||
if tasks_left == 1 { "task" } else { "tasks" }
|
if tasks_left == 1 { "task" } else { "tasks" }
|
||||||
))
|
))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill),
|
||||||
.size(16),
|
|
||||||
row![
|
row![
|
||||||
filter_button("All", Filter::All, current_filter),
|
filter_button("All", Filter::All, current_filter),
|
||||||
filter_button("Active", Filter::Active, current_filter),
|
filter_button("Active", Filter::Active, current_filter),
|
||||||
|
|
@ -466,10 +471,7 @@ fn empty_message(message: &str) -> Element<'_, Message> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fonts
|
// Fonts
|
||||||
const ICONS: Font = Font::External {
|
const ICONS: Font = Font::Name("Iced-Todos-Icons");
|
||||||
name: "Icons",
|
|
||||||
bytes: include_bytes!("../../todos/fonts/icons.ttf"),
|
|
||||||
};
|
|
||||||
|
|
||||||
fn icon(unicode: char) -> Text<'static> {
|
fn icon(unicode: char) -> Text<'static> {
|
||||||
text(unicode.to_string())
|
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"]
|
farbfeld = ["image_rs/farbfeld"]
|
||||||
canvas = ["lyon"]
|
canvas = ["lyon"]
|
||||||
qr_code = ["qrcode", "canvas"]
|
qr_code = ["qrcode", "canvas"]
|
||||||
font-source = ["font-kit"]
|
|
||||||
font-fallback = []
|
|
||||||
font-icons = []
|
|
||||||
opengl = []
|
opengl = []
|
||||||
image_rs = ["kamadak-exif"]
|
image_rs = ["kamadak-exif"]
|
||||||
|
|
||||||
|
|
@ -60,10 +57,6 @@ version = "0.12"
|
||||||
optional = true
|
optional = true
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.font-kit]
|
|
||||||
version = "0.10"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[dependencies.image_rs]
|
[dependencies.image_rs]
|
||||||
version = "0.24"
|
version = "0.24"
|
||||||
package = "image"
|
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::text;
|
||||||
use iced_native::{Font, Point, Size};
|
use iced_native::{Font, Point, Size};
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// The graphics backend of a [`Renderer`].
|
/// The graphics backend of a [`Renderer`].
|
||||||
///
|
///
|
||||||
/// [`Renderer`]: crate::Renderer
|
/// [`Renderer`]: crate::Renderer
|
||||||
|
|
@ -31,6 +33,9 @@ pub trait Text {
|
||||||
/// [`ICON_FONT`]: Self::ICON_FONT
|
/// [`ICON_FONT`]: Self::ICON_FONT
|
||||||
const ARROW_DOWN_ICON: char;
|
const ARROW_DOWN_ICON: char;
|
||||||
|
|
||||||
|
/// Returns the default [`Font`].
|
||||||
|
fn default_font(&self) -> Font;
|
||||||
|
|
||||||
/// Returns the default size of text.
|
/// Returns the default size of text.
|
||||||
fn default_size(&self) -> f32;
|
fn default_size(&self) -> f32;
|
||||||
|
|
||||||
|
|
@ -61,6 +66,9 @@ pub trait Text {
|
||||||
point: Point,
|
point: Point,
|
||||||
nearest_only: bool,
|
nearest_only: bool,
|
||||||
) -> Option<text::Hit>;
|
) -> 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.
|
/// 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::alignment;
|
||||||
use crate::{
|
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.
|
/// 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),
|
Point::new(11.0, 11.0 + 25.0 * i as f32),
|
||||||
Size::INFINITY,
|
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,
|
size: 20.0,
|
||||||
font: Font::Default,
|
font: Font::Monospace,
|
||||||
horizontal_alignment: alignment::Horizontal::Left,
|
horizontal_alignment: alignment::Horizontal::Left,
|
||||||
vertical_alignment: alignment::Vertical::Top,
|
vertical_alignment: alignment::Vertical::Top,
|
||||||
};
|
};
|
||||||
|
|
@ -71,7 +72,7 @@ impl<'a> Layer<'a> {
|
||||||
|
|
||||||
overlay.text.push(Text {
|
overlay.text.push(Text {
|
||||||
bounds: text.bounds + Vector::new(-1.0, -1.0),
|
bounds: text.bounds + Vector::new(-1.0, -1.0),
|
||||||
color: [0.0, 0.0, 0.0, 1.0],
|
color: Color::BLACK,
|
||||||
..text
|
..text
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -136,7 +137,7 @@ impl<'a> Layer<'a> {
|
||||||
content,
|
content,
|
||||||
bounds: *bounds + translation,
|
bounds: *bounds + translation,
|
||||||
size: *size,
|
size: *size,
|
||||||
color: color.into_linear(),
|
color: *color,
|
||||||
font: *font,
|
font: *font,
|
||||||
horizontal_alignment: *horizontal_alignment,
|
horizontal_alignment: *horizontal_alignment,
|
||||||
vertical_alignment: *vertical_alignment,
|
vertical_alignment: *vertical_alignment,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{alignment, Font, Rectangle};
|
use crate::{alignment, Color, Font, Rectangle};
|
||||||
|
|
||||||
/// A paragraph of text.
|
/// A paragraph of text.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -10,7 +10,7 @@ pub struct Text<'a> {
|
||||||
pub bounds: Rectangle,
|
pub bounds: Rectangle,
|
||||||
|
|
||||||
/// The color of the [`Text`], in __linear RGB_.
|
/// The color of the [`Text`], in __linear RGB_.
|
||||||
pub color: [f32; 4],
|
pub color: Color,
|
||||||
|
|
||||||
/// The size of the [`Text`].
|
/// The size of the [`Text`].
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ mod transformation;
|
||||||
mod viewport;
|
mod viewport;
|
||||||
|
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
pub mod font;
|
|
||||||
pub mod gradient;
|
pub mod gradient;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod layer;
|
pub mod layer;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use iced_native::{Background, Color, Element, Font, Point, Rectangle, Size};
|
||||||
|
|
||||||
pub use iced_native::renderer::Style;
|
pub use iced_native::renderer::Style;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
/// A backend-agnostic renderer that supports all the built-in widgets.
|
/// A backend-agnostic renderer that supports all the built-in widgets.
|
||||||
|
|
@ -130,6 +131,10 @@ where
|
||||||
const CHECKMARK_ICON: char = B::CHECKMARK_ICON;
|
const CHECKMARK_ICON: char = B::CHECKMARK_ICON;
|
||||||
const ARROW_DOWN_ICON: char = B::ARROW_DOWN_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 {
|
fn default_size(&self) -> f32 {
|
||||||
self.backend().default_size()
|
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>) {
|
fn fill_text(&mut self, text: Text<'_, Self::Font>) {
|
||||||
self.primitives.push(Primitive::Text {
|
self.primitives.push(Primitive::Text {
|
||||||
content: text.content.to_string(),
|
content: text.content.to_string(),
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ impl Default for Text {
|
||||||
position: Point::ORIGIN,
|
position: Point::ORIGIN,
|
||||||
color: Color::BLACK,
|
color: Color::BLACK,
|
||||||
size: 16.0,
|
size: 16.0,
|
||||||
font: Font::Default,
|
font: Font::SansSerif,
|
||||||
horizontal_alignment: alignment::Horizontal::Left,
|
horizontal_alignment: alignment::Horizontal::Left,
|
||||||
vertical_alignment: alignment::Vertical::Top,
|
vertical_alignment: alignment::Vertical::Top,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
use crate::clipboard;
|
use crate::clipboard;
|
||||||
|
use crate::font;
|
||||||
use crate::system;
|
use crate::system;
|
||||||
use crate::widget;
|
use crate::widget;
|
||||||
use crate::window;
|
use crate::window;
|
||||||
|
|
||||||
use iced_futures::MaybeSend;
|
use iced_futures::MaybeSend;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// An action that a [`Command`] can perform.
|
/// An action that a [`Command`] can perform.
|
||||||
|
|
@ -27,6 +29,15 @@ pub enum Action<T> {
|
||||||
|
|
||||||
/// Run a widget action.
|
/// Run a widget action.
|
||||||
Widget(widget::Action<T>),
|
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> {
|
impl<T> Action<T> {
|
||||||
|
|
@ -49,6 +60,10 @@ impl<T> Action<T> {
|
||||||
Self::Window(window) => Action::Window(window.map(f)),
|
Self::Window(window) => Action::Window(window.map(f)),
|
||||||
Self::System(system) => Action::System(system.map(f)),
|
Self::System(system) => Action::System(system.map(f)),
|
||||||
Self::Widget(widget) => Action::Widget(widget.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::Window(action) => write!(f, "Action::Window({action:?})"),
|
||||||
Self::System(action) => write!(f, "Action::System({action:?})"),
|
Self::System(action) => write!(f, "Action::System({action:?})"),
|
||||||
Self::Widget(_action) => write!(f, "Action::Widget"),
|
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 clipboard;
|
||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
|
pub mod font;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
|
@ -80,8 +81,8 @@ mod debug;
|
||||||
pub use iced_core::alignment;
|
pub use iced_core::alignment;
|
||||||
pub use iced_core::time;
|
pub use iced_core::time;
|
||||||
pub use iced_core::{
|
pub use iced_core::{
|
||||||
color, Alignment, Background, Color, ContentFit, Font, Length, Padding,
|
color, Alignment, Background, Color, ContentFit, Length, Padding, Pixels,
|
||||||
Pixels, Point, Rectangle, Size, Vector,
|
Point, Rectangle, Size, Vector,
|
||||||
};
|
};
|
||||||
pub use iced_futures::{executor, futures};
|
pub use iced_futures::{executor, futures};
|
||||||
pub use iced_style::application;
|
pub use iced_style::application;
|
||||||
|
|
@ -95,6 +96,7 @@ pub use command::Command;
|
||||||
pub use debug::Debug;
|
pub use debug::Debug;
|
||||||
pub use element::Element;
|
pub use element::Element;
|
||||||
pub use event::Event;
|
pub use event::Event;
|
||||||
|
pub use font::Font;
|
||||||
pub use hasher::Hasher;
|
pub use hasher::Hasher;
|
||||||
pub use layout::Layout;
|
pub use layout::Layout;
|
||||||
pub use overlay::Overlay;
|
pub use overlay::Overlay;
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ where
|
||||||
width: f32,
|
width: f32,
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
text_size: Option<f32>,
|
text_size: Option<f32>,
|
||||||
font: Renderer::Font,
|
font: Option<Renderer::Font>,
|
||||||
style: <Renderer::Theme as StyleSheet>::Style,
|
style: <Renderer::Theme as StyleSheet>::Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ where
|
||||||
width: 0.0,
|
width: 0.0,
|
||||||
padding: Padding::ZERO,
|
padding: Padding::ZERO,
|
||||||
text_size: None,
|
text_size: None,
|
||||||
font: Default::default(),
|
font: None,
|
||||||
style: Default::default(),
|
style: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -82,8 +82,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the font of the [`Menu`].
|
/// Sets the font of the [`Menu`].
|
||||||
pub fn font(mut self, font: Renderer::Font) -> Self {
|
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
||||||
self.font = font;
|
self.font = Some(font.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -311,7 +311,7 @@ where
|
||||||
last_selection: &'a mut Option<T>,
|
last_selection: &'a mut Option<T>,
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
text_size: Option<f32>,
|
text_size: Option<f32>,
|
||||||
font: Renderer::Font,
|
font: Option<Renderer::Font>,
|
||||||
style: <Renderer::Theme as StyleSheet>::Style,
|
style: <Renderer::Theme as StyleSheet>::Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -344,7 +344,7 @@ where
|
||||||
let size = {
|
let size = {
|
||||||
let intrinsic = Size::new(
|
let intrinsic = Size::new(
|
||||||
0.0,
|
0.0,
|
||||||
(text_size + self.padding.vertical())
|
(text_size * 1.2 + self.padding.vertical())
|
||||||
* self.options.len() as f32,
|
* self.options.len() as f32,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -386,7 +386,7 @@ where
|
||||||
|
|
||||||
*self.hovered_option = Some(
|
*self.hovered_option = Some(
|
||||||
((cursor_position.y - bounds.y)
|
((cursor_position.y - bounds.y)
|
||||||
/ (text_size + self.padding.vertical()))
|
/ (text_size * 1.2 + self.padding.vertical()))
|
||||||
as usize,
|
as usize,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -401,7 +401,7 @@ where
|
||||||
|
|
||||||
*self.hovered_option = Some(
|
*self.hovered_option = Some(
|
||||||
((cursor_position.y - bounds.y)
|
((cursor_position.y - bounds.y)
|
||||||
/ (text_size + self.padding.vertical()))
|
/ (text_size * 1.2 + self.padding.vertical()))
|
||||||
as usize,
|
as usize,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -450,7 +450,8 @@ where
|
||||||
|
|
||||||
let text_size =
|
let text_size =
|
||||||
self.text_size.unwrap_or_else(|| renderer.default_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 offset = viewport.y - bounds.y;
|
||||||
let start = (offset / option_height as f32) as usize;
|
let start = (offset / option_height as f32) as usize;
|
||||||
|
|
@ -467,7 +468,7 @@ where
|
||||||
x: bounds.x,
|
x: bounds.x,
|
||||||
y: bounds.y + (option_height * i) as f32,
|
y: bounds.y + (option_height * i) as f32,
|
||||||
width: bounds.width,
|
width: bounds.width,
|
||||||
height: text_size + self.padding.vertical(),
|
height: text_size * 1.2 + self.padding.vertical(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_selected {
|
if is_selected {
|
||||||
|
|
@ -491,7 +492,7 @@ where
|
||||||
..bounds
|
..bounds
|
||||||
},
|
},
|
||||||
size: text_size,
|
size: text_size,
|
||||||
font: self.font.clone(),
|
font: self.font.unwrap_or_else(|| renderer.default_font()),
|
||||||
color: if is_selected {
|
color: if is_selected {
|
||||||
appearance.selected_text_color
|
appearance.selected_text_color
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
//! Build interactive programs using The Elm Architecture.
|
//! Build interactive programs using The Elm Architecture.
|
||||||
|
use crate::text;
|
||||||
use crate::{Command, Element, Renderer};
|
use crate::{Command, Element, Renderer};
|
||||||
|
|
||||||
mod state;
|
mod state;
|
||||||
|
|
@ -8,7 +9,7 @@ pub use state::State;
|
||||||
/// The core of a user interface application following The Elm Architecture.
|
/// The core of a user interface application following The Elm Architecture.
|
||||||
pub trait Program: Sized {
|
pub trait Program: Sized {
|
||||||
/// The graphics backend to use to draw the [`Program`].
|
/// 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.
|
/// The type of __messages__ your [`Program`] will produce.
|
||||||
type Message: std::fmt::Debug + Send;
|
type Message: std::fmt::Debug + Send;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ use crate::renderer::{self, Renderer};
|
||||||
use crate::text::{self, Text};
|
use crate::text::{self, Text};
|
||||||
use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector};
|
use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector};
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// A renderer that does nothing.
|
/// A renderer that does nothing.
|
||||||
///
|
///
|
||||||
/// It can be useful if you are writing tests!
|
/// It can be useful if you are writing tests!
|
||||||
|
|
@ -40,14 +42,20 @@ impl Renderer for Null {
|
||||||
impl text::Renderer for Null {
|
impl text::Renderer for Null {
|
||||||
type Font = Font;
|
type Font = Font;
|
||||||
|
|
||||||
const ICON_FONT: Font = Font::Default;
|
const ICON_FONT: Font = Font::SansSerif;
|
||||||
const CHECKMARK_ICON: char = '0';
|
const CHECKMARK_ICON: char = '0';
|
||||||
const ARROW_DOWN_ICON: char = '0';
|
const ARROW_DOWN_ICON: char = '0';
|
||||||
|
|
||||||
fn default_size(&self) -> f32 {
|
fn default_font(&self) -> Self::Font {
|
||||||
20.0
|
Font::SansSerif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_size(&self) -> f32 {
|
||||||
|
16.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
|
||||||
|
|
||||||
fn measure(
|
fn measure(
|
||||||
&self,
|
&self,
|
||||||
_content: &str,
|
_content: &str,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
//! Draw and interact with text.
|
//! Draw and interact with text.
|
||||||
use crate::alignment;
|
use crate::alignment;
|
||||||
use crate::{Color, Point, Rectangle, Size, Vector};
|
use crate::{Color, Point, Rectangle, Size};
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// A paragraph.
|
/// A paragraph.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -32,10 +34,6 @@ pub struct Text<'a, Font> {
|
||||||
pub enum Hit {
|
pub enum Hit {
|
||||||
/// The point was within the bounds of the returned character index.
|
/// The point was within the bounds of the returned character index.
|
||||||
CharOffset(usize),
|
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 {
|
impl Hit {
|
||||||
|
|
@ -43,13 +41,6 @@ impl Hit {
|
||||||
pub fn cursor(self) -> usize {
|
pub fn cursor(self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Self::CharOffset(i) => i,
|
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`].
|
/// A renderer capable of measuring and drawing [`Text`].
|
||||||
pub trait Renderer: crate::Renderer {
|
pub trait Renderer: crate::Renderer {
|
||||||
/// The font type used.
|
/// The font type used.
|
||||||
type Font: Default + Clone;
|
type Font: Copy;
|
||||||
|
|
||||||
/// The icon font of the backend.
|
/// The icon font of the backend.
|
||||||
const ICON_FONT: Self::Font;
|
const ICON_FONT: Self::Font;
|
||||||
|
|
@ -72,6 +63,9 @@ pub trait Renderer: crate::Renderer {
|
||||||
/// [`ICON_FONT`]: Self::ICON_FONT
|
/// [`ICON_FONT`]: Self::ICON_FONT
|
||||||
const ARROW_DOWN_ICON: char;
|
const ARROW_DOWN_ICON: char;
|
||||||
|
|
||||||
|
/// Returns the default [`Self::Font`].
|
||||||
|
fn default_font(&self) -> Self::Font;
|
||||||
|
|
||||||
/// Returns the default size of [`Text`].
|
/// Returns the default size of [`Text`].
|
||||||
fn default_size(&self) -> f32;
|
fn default_size(&self) -> f32;
|
||||||
|
|
||||||
|
|
@ -109,6 +103,9 @@ pub trait Renderer: crate::Renderer {
|
||||||
nearest_only: bool,
|
nearest_only: bool,
|
||||||
) -> Option<Hit>;
|
) -> Option<Hit>;
|
||||||
|
|
||||||
|
/// Loads a [`Self::Font`] from its bytes.
|
||||||
|
fn load_font(&mut self, font: Cow<'static, [u8]>);
|
||||||
|
|
||||||
/// Draws the given [`Text`].
|
/// Draws the given [`Text`].
|
||||||
fn fill_text(&mut self, text: Text<'_, Self::Font>);
|
fn fill_text(&mut self, text: Text<'_, Self::Font>);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ where
|
||||||
size: f32,
|
size: f32,
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
text_size: Option<f32>,
|
text_size: Option<f32>,
|
||||||
font: Renderer::Font,
|
font: Option<Renderer::Font>,
|
||||||
icon: Icon<Renderer::Font>,
|
icon: Icon<Renderer::Font>,
|
||||||
style: <Renderer::Theme as StyleSheet>::Style,
|
style: <Renderer::Theme as StyleSheet>::Style,
|
||||||
}
|
}
|
||||||
|
|
@ -91,7 +91,7 @@ where
|
||||||
size: Self::DEFAULT_SIZE,
|
size: Self::DEFAULT_SIZE,
|
||||||
spacing: Self::DEFAULT_SPACING,
|
spacing: Self::DEFAULT_SPACING,
|
||||||
text_size: None,
|
text_size: None,
|
||||||
font: Renderer::Font::default(),
|
font: None,
|
||||||
icon: Icon {
|
icon: Icon {
|
||||||
font: Renderer::ICON_FONT,
|
font: Renderer::ICON_FONT,
|
||||||
code_point: Renderer::CHECKMARK_ICON,
|
code_point: Renderer::CHECKMARK_ICON,
|
||||||
|
|
@ -128,8 +128,8 @@ where
|
||||||
/// Sets the [`Font`] of the text of the [`Checkbox`].
|
/// Sets the [`Font`] of the text of the [`Checkbox`].
|
||||||
///
|
///
|
||||||
/// [`Font`]: crate::text::Renderer::Font
|
/// [`Font`]: crate::text::Renderer::Font
|
||||||
pub fn font(mut self, font: Renderer::Font) -> Self {
|
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
||||||
self.font = font;
|
self.font = Some(font.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,7 +175,7 @@ where
|
||||||
.push(Row::new().width(self.size).height(self.size))
|
.push(Row::new().width(self.size).height(self.size))
|
||||||
.push(
|
.push(
|
||||||
Text::new(&self.label)
|
Text::new(&self.label)
|
||||||
.font(self.font.clone())
|
.font(self.font.unwrap_or_else(|| renderer.default_font()))
|
||||||
.width(self.width)
|
.width(self.width)
|
||||||
.size(
|
.size(
|
||||||
self.text_size
|
self.text_size
|
||||||
|
|
@ -267,12 +267,12 @@ where
|
||||||
code_point,
|
code_point,
|
||||||
size,
|
size,
|
||||||
} = &self.icon;
|
} = &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 {
|
if self.is_checked {
|
||||||
renderer.fill_text(text::Text {
|
renderer.fill_text(text::Text {
|
||||||
content: &code_point.to_string(),
|
content: &code_point.to_string(),
|
||||||
font: font.clone(),
|
font: *font,
|
||||||
size,
|
size,
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
x: bounds.center_x(),
|
x: bounds.center_x(),
|
||||||
|
|
@ -295,7 +295,7 @@ where
|
||||||
label_layout,
|
label_layout,
|
||||||
&self.label,
|
&self.label,
|
||||||
self.text_size,
|
self.text_size,
|
||||||
self.font.clone(),
|
self.font,
|
||||||
widget::text::Appearance {
|
widget::text::Appearance {
|
||||||
color: custom_style.text_color,
|
color: custom_style.text_color,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ where
|
||||||
width: Length,
|
width: Length,
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
text_size: Option<f32>,
|
text_size: Option<f32>,
|
||||||
font: Renderer::Font,
|
font: Option<Renderer::Font>,
|
||||||
handle: Handle<Renderer::Font>,
|
handle: Handle<Renderer::Font>,
|
||||||
style: <Renderer::Theme as StyleSheet>::Style,
|
style: <Renderer::Theme as StyleSheet>::Style,
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +70,7 @@ where
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
padding: Self::DEFAULT_PADDING,
|
padding: Self::DEFAULT_PADDING,
|
||||||
text_size: None,
|
text_size: None,
|
||||||
font: Default::default(),
|
font: None,
|
||||||
handle: Default::default(),
|
handle: Default::default(),
|
||||||
style: Default::default(),
|
style: Default::default(),
|
||||||
}
|
}
|
||||||
|
|
@ -101,8 +101,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the font of the [`PickList`].
|
/// Sets the font of the [`PickList`].
|
||||||
pub fn font(mut self, font: Renderer::Font) -> Self {
|
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
||||||
self.font = font;
|
self.font = Some(font.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,7 +163,7 @@ where
|
||||||
self.width,
|
self.width,
|
||||||
self.padding,
|
self.padding,
|
||||||
self.text_size,
|
self.text_size,
|
||||||
&self.font,
|
self.font,
|
||||||
self.placeholder.as_deref(),
|
self.placeholder.as_deref(),
|
||||||
&self.options,
|
&self.options,
|
||||||
)
|
)
|
||||||
|
|
@ -212,6 +212,7 @@ where
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
|
let font = self.font.unwrap_or_else(|| renderer.default_font());
|
||||||
draw(
|
draw(
|
||||||
renderer,
|
renderer,
|
||||||
theme,
|
theme,
|
||||||
|
|
@ -219,7 +220,7 @@ where
|
||||||
cursor_position,
|
cursor_position,
|
||||||
self.padding,
|
self.padding,
|
||||||
self.text_size,
|
self.text_size,
|
||||||
&self.font,
|
font,
|
||||||
self.placeholder.as_deref(),
|
self.placeholder.as_deref(),
|
||||||
self.selected.as_ref(),
|
self.selected.as_ref(),
|
||||||
&self.handle,
|
&self.handle,
|
||||||
|
|
@ -232,7 +233,7 @@ where
|
||||||
&'b mut self,
|
&'b mut self,
|
||||||
tree: &'b mut Tree,
|
tree: &'b mut Tree,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||||
let state = tree.state.downcast_mut::<State<T>>();
|
let state = tree.state.downcast_mut::<State<T>>();
|
||||||
|
|
||||||
|
|
@ -241,7 +242,7 @@ where
|
||||||
state,
|
state,
|
||||||
self.padding,
|
self.padding,
|
||||||
self.text_size,
|
self.text_size,
|
||||||
self.font.clone(),
|
self.font.unwrap_or_else(|| renderer.default_font()),
|
||||||
&self.options,
|
&self.options,
|
||||||
self.style.clone(),
|
self.style.clone(),
|
||||||
)
|
)
|
||||||
|
|
@ -343,7 +344,7 @@ pub fn layout<Renderer, T>(
|
||||||
width: Length,
|
width: Length,
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
text_size: Option<f32>,
|
text_size: Option<f32>,
|
||||||
font: &Renderer::Font,
|
font: Option<Renderer::Font>,
|
||||||
placeholder: Option<&str>,
|
placeholder: Option<&str>,
|
||||||
options: &[T],
|
options: &[T],
|
||||||
) -> layout::Node
|
) -> layout::Node
|
||||||
|
|
@ -362,7 +363,7 @@ where
|
||||||
let (width, _) = renderer.measure(
|
let (width, _) = renderer.measure(
|
||||||
label,
|
label,
|
||||||
text_size,
|
text_size,
|
||||||
font.clone(),
|
font.unwrap_or_else(|| renderer.default_font()),
|
||||||
Size::new(f32::INFINITY, f32::INFINITY),
|
Size::new(f32::INFINITY, f32::INFINITY),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -384,7 +385,7 @@ where
|
||||||
|
|
||||||
let size = {
|
let size = {
|
||||||
let intrinsic =
|
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)
|
limits.resolve(intrinsic).pad(padding)
|
||||||
};
|
};
|
||||||
|
|
@ -560,7 +561,7 @@ pub fn draw<'a, T, Renderer>(
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
text_size: Option<f32>,
|
text_size: Option<f32>,
|
||||||
font: &Renderer::Font,
|
font: Renderer::Font,
|
||||||
placeholder: Option<&str>,
|
placeholder: Option<&str>,
|
||||||
selected: Option<&T>,
|
selected: Option<&T>,
|
||||||
handle: &Handle<Renderer::Font>,
|
handle: &Handle<Renderer::Font>,
|
||||||
|
|
@ -599,12 +600,12 @@ pub fn draw<'a, T, Renderer>(
|
||||||
font,
|
font,
|
||||||
code_point,
|
code_point,
|
||||||
size,
|
size,
|
||||||
}) => Some((font.clone(), *code_point, *size)),
|
}) => Some((*font, *code_point, *size)),
|
||||||
Handle::Dynamic { open, closed } => {
|
Handle::Dynamic { open, closed } => {
|
||||||
if state().is_open {
|
if state().is_open {
|
||||||
Some((open.font.clone(), open.code_point, open.size))
|
Some((open.font, open.code_point, open.size))
|
||||||
} else {
|
} else {
|
||||||
Some((closed.font.clone(), closed.code_point, closed.size))
|
Some((closed.font, closed.code_point, closed.size))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Handle::None => None,
|
Handle::None => None,
|
||||||
|
|
@ -620,12 +621,12 @@ pub fn draw<'a, T, Renderer>(
|
||||||
color: style.handle_color,
|
color: style.handle_color,
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
x: bounds.x + bounds.width - padding.horizontal(),
|
x: bounds.x + bounds.width - padding.horizontal(),
|
||||||
y: bounds.center_y() - size / 2.0,
|
y: bounds.center_y(),
|
||||||
height: size,
|
height: size * 1.2,
|
||||||
..bounds
|
..bounds
|
||||||
},
|
},
|
||||||
horizontal_alignment: alignment::Horizontal::Right,
|
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 {
|
renderer.fill_text(Text {
|
||||||
content: label,
|
content: label,
|
||||||
size: text_size,
|
size: text_size,
|
||||||
font: font.clone(),
|
font,
|
||||||
color: if is_selected {
|
color: if is_selected {
|
||||||
style.text_color
|
style.text_color
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -645,12 +646,12 @@ pub fn draw<'a, T, Renderer>(
|
||||||
},
|
},
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
x: bounds.x + padding.left,
|
x: bounds.x + padding.left,
|
||||||
y: bounds.center_y() - text_size / 2.0,
|
y: bounds.center_y(),
|
||||||
width: bounds.width - padding.horizontal(),
|
width: bounds.width - padding.horizontal(),
|
||||||
height: text_size,
|
height: text_size * 1.2,
|
||||||
},
|
},
|
||||||
horizontal_alignment: alignment::Horizontal::Left,
|
horizontal_alignment: alignment::Horizontal::Left,
|
||||||
vertical_alignment: alignment::Vertical::Top,
|
vertical_alignment: alignment::Vertical::Center,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ where
|
||||||
size: f32,
|
size: f32,
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
text_size: Option<f32>,
|
text_size: Option<f32>,
|
||||||
font: Renderer::Font,
|
font: Option<Renderer::Font>,
|
||||||
style: <Renderer::Theme as StyleSheet>::Style,
|
style: <Renderer::Theme as StyleSheet>::Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,7 +95,7 @@ where
|
||||||
size: Self::DEFAULT_SIZE,
|
size: Self::DEFAULT_SIZE,
|
||||||
spacing: Self::DEFAULT_SPACING, //15
|
spacing: Self::DEFAULT_SPACING, //15
|
||||||
text_size: None,
|
text_size: None,
|
||||||
font: Default::default(),
|
font: None,
|
||||||
style: Default::default(),
|
style: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,8 +125,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the text font of the [`Radio`] button.
|
/// Sets the text font of the [`Radio`] button.
|
||||||
pub fn font(mut self, font: Renderer::Font) -> Self {
|
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
||||||
self.font = font;
|
self.font = Some(font.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -275,7 +275,7 @@ where
|
||||||
label_layout,
|
label_layout,
|
||||||
&self.label,
|
&self.label,
|
||||||
self.text_size,
|
self.text_size,
|
||||||
self.font.clone(),
|
self.font,
|
||||||
widget::text::Appearance {
|
widget::text::Appearance {
|
||||||
color: custom_style.text_color,
|
color: custom_style.text_color,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ where
|
||||||
height: Length,
|
height: Length,
|
||||||
horizontal_alignment: alignment::Horizontal,
|
horizontal_alignment: alignment::Horizontal,
|
||||||
vertical_alignment: alignment::Vertical,
|
vertical_alignment: alignment::Vertical,
|
||||||
font: Renderer::Font,
|
font: Option<Renderer::Font>,
|
||||||
style: <Renderer::Theme as StyleSheet>::Style,
|
style: <Renderer::Theme as StyleSheet>::Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ where
|
||||||
Text {
|
Text {
|
||||||
content: content.into(),
|
content: content.into(),
|
||||||
size: None,
|
size: None,
|
||||||
font: Default::default(),
|
font: None,
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
height: Length::Shrink,
|
height: Length::Shrink,
|
||||||
horizontal_alignment: alignment::Horizontal::Left,
|
horizontal_alignment: alignment::Horizontal::Left,
|
||||||
|
|
@ -70,7 +70,7 @@ where
|
||||||
///
|
///
|
||||||
/// [`Font`]: crate::text::Renderer::Font
|
/// [`Font`]: crate::text::Renderer::Font
|
||||||
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
||||||
self.font = font.into();
|
self.font = Some(font.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,8 +138,12 @@ where
|
||||||
|
|
||||||
let bounds = limits.max();
|
let bounds = limits.max();
|
||||||
|
|
||||||
let (width, height) =
|
let (width, height) = renderer.measure(
|
||||||
renderer.measure(&self.content, size, self.font.clone(), bounds);
|
&self.content,
|
||||||
|
size,
|
||||||
|
self.font.unwrap_or_else(|| renderer.default_font()),
|
||||||
|
bounds,
|
||||||
|
);
|
||||||
|
|
||||||
let size = limits.resolve(Size::new(width, height));
|
let size = limits.resolve(Size::new(width, height));
|
||||||
|
|
||||||
|
|
@ -162,7 +166,7 @@ where
|
||||||
layout,
|
layout,
|
||||||
&self.content,
|
&self.content,
|
||||||
self.size,
|
self.size,
|
||||||
self.font.clone(),
|
self.font,
|
||||||
theme.appearance(self.style),
|
theme.appearance(self.style),
|
||||||
self.horizontal_alignment,
|
self.horizontal_alignment,
|
||||||
self.vertical_alignment,
|
self.vertical_alignment,
|
||||||
|
|
@ -186,7 +190,7 @@ pub fn draw<Renderer>(
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
content: &str,
|
content: &str,
|
||||||
size: Option<f32>,
|
size: Option<f32>,
|
||||||
font: Renderer::Font,
|
font: Option<Renderer::Font>,
|
||||||
appearance: Appearance,
|
appearance: Appearance,
|
||||||
horizontal_alignment: alignment::Horizontal,
|
horizontal_alignment: alignment::Horizontal,
|
||||||
vertical_alignment: alignment::Vertical,
|
vertical_alignment: alignment::Vertical,
|
||||||
|
|
@ -212,7 +216,7 @@ pub fn draw<Renderer>(
|
||||||
size: size.unwrap_or_else(|| renderer.default_size()),
|
size: size.unwrap_or_else(|| renderer.default_size()),
|
||||||
bounds: Rectangle { x, y, ..bounds },
|
bounds: Rectangle { x, y, ..bounds },
|
||||||
color: appearance.color.unwrap_or(style.text_color),
|
color: appearance.color.unwrap_or(style.text_color),
|
||||||
font,
|
font: font.unwrap_or_else(|| renderer.default_font()),
|
||||||
horizontal_alignment,
|
horizontal_alignment,
|
||||||
vertical_alignment,
|
vertical_alignment,
|
||||||
});
|
});
|
||||||
|
|
@ -242,7 +246,7 @@ where
|
||||||
height: self.height,
|
height: self.height,
|
||||||
horizontal_alignment: self.horizontal_alignment,
|
horizontal_alignment: self.horizontal_alignment,
|
||||||
vertical_alignment: self.vertical_alignment,
|
vertical_alignment: self.vertical_alignment,
|
||||||
font: self.font.clone(),
|
font: self.font,
|
||||||
style: self.style,
|
style: self.style,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ where
|
||||||
placeholder: String,
|
placeholder: String,
|
||||||
value: Value,
|
value: Value,
|
||||||
is_secure: bool,
|
is_secure: bool,
|
||||||
font: Renderer::Font,
|
font: Option<Renderer::Font>,
|
||||||
width: Length,
|
width: Length,
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
size: Option<f32>,
|
size: Option<f32>,
|
||||||
|
|
@ -92,7 +92,7 @@ where
|
||||||
placeholder: String::from(placeholder),
|
placeholder: String::from(placeholder),
|
||||||
value: Value::new(value),
|
value: Value::new(value),
|
||||||
is_secure: false,
|
is_secure: false,
|
||||||
font: Default::default(),
|
font: None,
|
||||||
width: Length::Fill,
|
width: Length::Fill,
|
||||||
padding: Padding::new(5.0),
|
padding: Padding::new(5.0),
|
||||||
size: None,
|
size: None,
|
||||||
|
|
@ -129,7 +129,7 @@ where
|
||||||
///
|
///
|
||||||
/// [`Font`]: text::Renderer::Font
|
/// [`Font`]: text::Renderer::Font
|
||||||
pub fn font(mut self, font: Renderer::Font) -> Self {
|
pub fn font(mut self, font: Renderer::Font) -> Self {
|
||||||
self.font = font;
|
self.font = Some(font);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
/// Sets the width of the [`TextInput`].
|
/// Sets the width of the [`TextInput`].
|
||||||
|
|
@ -188,7 +188,7 @@ where
|
||||||
value.unwrap_or(&self.value),
|
value.unwrap_or(&self.value),
|
||||||
&self.placeholder,
|
&self.placeholder,
|
||||||
self.size,
|
self.size,
|
||||||
&self.font,
|
self.font,
|
||||||
self.is_secure,
|
self.is_secure,
|
||||||
&self.style,
|
&self.style,
|
||||||
)
|
)
|
||||||
|
|
@ -258,7 +258,7 @@ where
|
||||||
shell,
|
shell,
|
||||||
&mut self.value,
|
&mut self.value,
|
||||||
self.size,
|
self.size,
|
||||||
&self.font,
|
self.font,
|
||||||
self.is_secure,
|
self.is_secure,
|
||||||
self.on_change.as_ref(),
|
self.on_change.as_ref(),
|
||||||
self.on_paste.as_deref(),
|
self.on_paste.as_deref(),
|
||||||
|
|
@ -286,7 +286,7 @@ where
|
||||||
&self.value,
|
&self.value,
|
||||||
&self.placeholder,
|
&self.placeholder,
|
||||||
self.size,
|
self.size,
|
||||||
&self.font,
|
self.font,
|
||||||
self.is_secure,
|
self.is_secure,
|
||||||
&self.style,
|
&self.style,
|
||||||
)
|
)
|
||||||
|
|
@ -385,9 +385,8 @@ where
|
||||||
Renderer: text::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
let text_size = size.unwrap_or_else(|| renderer.default_size());
|
let text_size = size.unwrap_or_else(|| renderer.default_size());
|
||||||
|
|
||||||
let padding = padding.fit(Size::ZERO, limits.max());
|
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));
|
let mut text = layout::Node::new(limits.resolve(Size::ZERO));
|
||||||
text.move_to(Point::new(padding.left, padding.top));
|
text.move_to(Point::new(padding.left, padding.top));
|
||||||
|
|
@ -406,7 +405,7 @@ pub fn update<'a, Message, Renderer>(
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
value: &mut Value,
|
value: &mut Value,
|
||||||
size: Option<f32>,
|
size: Option<f32>,
|
||||||
font: &Renderer::Font,
|
font: Option<Renderer::Font>,
|
||||||
is_secure: bool,
|
is_secure: bool,
|
||||||
on_change: &dyn Fn(String) -> Message,
|
on_change: &dyn Fn(String) -> Message,
|
||||||
on_paste: Option<&dyn Fn(String) -> Message>,
|
on_paste: Option<&dyn Fn(String) -> Message>,
|
||||||
|
|
@ -455,7 +454,7 @@ where
|
||||||
find_cursor_position(
|
find_cursor_position(
|
||||||
renderer,
|
renderer,
|
||||||
text_layout.bounds(),
|
text_layout.bounds(),
|
||||||
font.clone(),
|
font,
|
||||||
size,
|
size,
|
||||||
&value,
|
&value,
|
||||||
state,
|
state,
|
||||||
|
|
@ -483,7 +482,7 @@ where
|
||||||
let position = find_cursor_position(
|
let position = find_cursor_position(
|
||||||
renderer,
|
renderer,
|
||||||
text_layout.bounds(),
|
text_layout.bounds(),
|
||||||
font.clone(),
|
font,
|
||||||
size,
|
size,
|
||||||
value,
|
value,
|
||||||
state,
|
state,
|
||||||
|
|
@ -532,7 +531,7 @@ where
|
||||||
let position = find_cursor_position(
|
let position = find_cursor_position(
|
||||||
renderer,
|
renderer,
|
||||||
text_layout.bounds(),
|
text_layout.bounds(),
|
||||||
font.clone(),
|
font,
|
||||||
size,
|
size,
|
||||||
&value,
|
&value,
|
||||||
state,
|
state,
|
||||||
|
|
@ -812,7 +811,7 @@ pub fn draw<Renderer>(
|
||||||
value: &Value,
|
value: &Value,
|
||||||
placeholder: &str,
|
placeholder: &str,
|
||||||
size: Option<f32>,
|
size: Option<f32>,
|
||||||
font: &Renderer::Font,
|
font: Option<Renderer::Font>,
|
||||||
is_secure: bool,
|
is_secure: bool,
|
||||||
style: &<Renderer::Theme as StyleSheet>::Style,
|
style: &<Renderer::Theme as StyleSheet>::Style,
|
||||||
) where
|
) where
|
||||||
|
|
@ -846,6 +845,7 @@ pub fn draw<Renderer>(
|
||||||
);
|
);
|
||||||
|
|
||||||
let text = value.to_string();
|
let text = value.to_string();
|
||||||
|
let font = font.unwrap_or_else(|| renderer.default_font());
|
||||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
let size = size.unwrap_or_else(|| renderer.default_size());
|
||||||
|
|
||||||
let (cursor, offset) = if let Some(focus) = &state.is_focused {
|
let (cursor, offset) = if let Some(focus) = &state.is_focused {
|
||||||
|
|
@ -858,7 +858,7 @@ pub fn draw<Renderer>(
|
||||||
value,
|
value,
|
||||||
size,
|
size,
|
||||||
position,
|
position,
|
||||||
font.clone(),
|
font,
|
||||||
);
|
);
|
||||||
|
|
||||||
let is_cursor_visible = ((focus.now - focus.updated_at)
|
let is_cursor_visible = ((focus.now - focus.updated_at)
|
||||||
|
|
@ -899,7 +899,7 @@ pub fn draw<Renderer>(
|
||||||
value,
|
value,
|
||||||
size,
|
size,
|
||||||
left,
|
left,
|
||||||
font.clone(),
|
font,
|
||||||
);
|
);
|
||||||
|
|
||||||
let (right_position, right_offset) =
|
let (right_position, right_offset) =
|
||||||
|
|
@ -909,7 +909,7 @@ pub fn draw<Renderer>(
|
||||||
value,
|
value,
|
||||||
size,
|
size,
|
||||||
right,
|
right,
|
||||||
font.clone(),
|
font,
|
||||||
);
|
);
|
||||||
|
|
||||||
let width = right_position - left_position;
|
let width = right_position - left_position;
|
||||||
|
|
@ -944,7 +944,7 @@ pub fn draw<Renderer>(
|
||||||
let text_width = renderer.measure_width(
|
let text_width = renderer.measure_width(
|
||||||
if text.is_empty() { placeholder } else { &text },
|
if text.is_empty() { placeholder } else { &text },
|
||||||
size,
|
size,
|
||||||
font.clone(),
|
font,
|
||||||
);
|
);
|
||||||
|
|
||||||
let render = |renderer: &mut Renderer| {
|
let render = |renderer: &mut Renderer| {
|
||||||
|
|
@ -959,7 +959,7 @@ pub fn draw<Renderer>(
|
||||||
} else {
|
} else {
|
||||||
theme.value_color(style)
|
theme.value_color(style)
|
||||||
},
|
},
|
||||||
font: font.clone(),
|
font,
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
y: text_bounds.center_y(),
|
y: text_bounds.center_y(),
|
||||||
width: f32::INFINITY,
|
width: f32::INFINITY,
|
||||||
|
|
@ -1180,7 +1180,7 @@ where
|
||||||
fn find_cursor_position<Renderer>(
|
fn find_cursor_position<Renderer>(
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
text_bounds: Rectangle,
|
text_bounds: Rectangle,
|
||||||
font: Renderer::Font,
|
font: Option<Renderer::Font>,
|
||||||
size: Option<f32>,
|
size: Option<f32>,
|
||||||
value: &Value,
|
value: &Value,
|
||||||
state: &State,
|
state: &State,
|
||||||
|
|
@ -1189,21 +1189,30 @@ fn find_cursor_position<Renderer>(
|
||||||
where
|
where
|
||||||
Renderer: text::Renderer,
|
Renderer: text::Renderer,
|
||||||
{
|
{
|
||||||
|
let font = font.unwrap_or_else(|| renderer.default_font());
|
||||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
let size = size.unwrap_or_else(|| renderer.default_size());
|
||||||
|
|
||||||
let offset =
|
let offset = offset(renderer, text_bounds, font, size, value, state);
|
||||||
offset(renderer, text_bounds, font.clone(), size, value, state);
|
let value = value.to_string();
|
||||||
|
|
||||||
renderer
|
let char_offset = renderer
|
||||||
.hit_test(
|
.hit_test(
|
||||||
&value.to_string(),
|
&value,
|
||||||
size,
|
size,
|
||||||
font,
|
font,
|
||||||
Size::INFINITY,
|
Size::INFINITY,
|
||||||
Point::new(x + offset, text_bounds.height / 2.0),
|
Point::new(x + offset, text_bounds.height / 2.0),
|
||||||
true,
|
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;
|
const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ where
|
||||||
text_size: Option<f32>,
|
text_size: Option<f32>,
|
||||||
text_alignment: alignment::Horizontal,
|
text_alignment: alignment::Horizontal,
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
font: Renderer::Font,
|
font: Option<Renderer::Font>,
|
||||||
style: <Renderer::Theme as StyleSheet>::Style,
|
style: <Renderer::Theme as StyleSheet>::Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ where
|
||||||
text_size: None,
|
text_size: None,
|
||||||
text_alignment: alignment::Horizontal::Left,
|
text_alignment: alignment::Horizontal::Left,
|
||||||
spacing: 0.0,
|
spacing: 0.0,
|
||||||
font: Renderer::Font::default(),
|
font: None,
|
||||||
style: Default::default(),
|
style: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,8 +117,8 @@ where
|
||||||
/// Sets the [`Font`] of the text of the [`Toggler`]
|
/// Sets the [`Font`] of the text of the [`Toggler`]
|
||||||
///
|
///
|
||||||
/// [`Font`]: crate::text::Renderer::Font
|
/// [`Font`]: crate::text::Renderer::Font
|
||||||
pub fn font(mut self, font: Renderer::Font) -> Self {
|
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
||||||
self.font = font;
|
self.font = Some(font.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,7 +160,7 @@ where
|
||||||
row = row.push(
|
row = row.push(
|
||||||
Text::new(label)
|
Text::new(label)
|
||||||
.horizontal_alignment(self.text_alignment)
|
.horizontal_alignment(self.text_alignment)
|
||||||
.font(self.font.clone())
|
.font(self.font.unwrap_or_else(|| renderer.default_font()))
|
||||||
.width(self.width)
|
.width(self.width)
|
||||||
.size(
|
.size(
|
||||||
self.text_size
|
self.text_size
|
||||||
|
|
@ -243,7 +243,7 @@ where
|
||||||
label_layout,
|
label_layout,
|
||||||
label,
|
label,
|
||||||
self.text_size,
|
self.text_size,
|
||||||
self.font.clone(),
|
self.font,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
self.text_alignment,
|
self.text_alignment,
|
||||||
alignment::Vertical::Center,
|
alignment::Vertical::Center,
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,6 @@ pub trait Application: Sized {
|
||||||
let renderer_settings = crate::renderer::Settings {
|
let renderer_settings = crate::renderer::Settings {
|
||||||
default_font: settings.default_font,
|
default_font: settings.default_font,
|
||||||
default_text_size: settings.default_text_size,
|
default_text_size: settings.default_text_size,
|
||||||
text_multithreading: settings.text_multithreading,
|
|
||||||
antialiasing: if settings.antialiasing {
|
antialiasing: if settings.antialiasing {
|
||||||
Some(crate::renderer::settings::Antialiasing::MSAAx4)
|
Some(crate::renderer::settings::Antialiasing::MSAAx4)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
17
src/lib.rs
17
src/lib.rs
|
|
@ -182,20 +182,12 @@ pub mod touch;
|
||||||
pub mod widget;
|
pub mod widget;
|
||||||
pub mod window;
|
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;
|
use iced_wgpu as renderer;
|
||||||
|
use iced_winit as runtime;
|
||||||
#[cfg(feature = "glow")]
|
|
||||||
use iced_glow as renderer;
|
|
||||||
|
|
||||||
pub use iced_native::theme;
|
pub use iced_native::theme;
|
||||||
pub use runtime::event;
|
pub use runtime::event;
|
||||||
|
pub use runtime::font;
|
||||||
pub use runtime::subscription;
|
pub use runtime::subscription;
|
||||||
|
|
||||||
pub use application::Application;
|
pub use application::Application;
|
||||||
|
|
@ -203,6 +195,7 @@ pub use element::Element;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use event::Event;
|
pub use event::Event;
|
||||||
pub use executor::Executor;
|
pub use executor::Executor;
|
||||||
|
pub use font::Font;
|
||||||
pub use renderer::Renderer;
|
pub use renderer::Renderer;
|
||||||
pub use result::Result;
|
pub use result::Result;
|
||||||
pub use sandbox::Sandbox;
|
pub use sandbox::Sandbox;
|
||||||
|
|
@ -213,8 +206,8 @@ pub use theme::Theme;
|
||||||
pub use runtime::alignment;
|
pub use runtime::alignment;
|
||||||
pub use runtime::futures;
|
pub use runtime::futures;
|
||||||
pub use runtime::{
|
pub use runtime::{
|
||||||
color, Alignment, Background, Color, Command, ContentFit, Font, Length,
|
color, Alignment, Background, Color, Command, ContentFit, Length, Padding,
|
||||||
Padding, Point, Rectangle, Size, Vector,
|
Point, Rectangle, Size, Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "system")]
|
#[cfg(feature = "system")]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
//! Configure your application.
|
//! Configure your application.
|
||||||
use crate::window;
|
use crate::window;
|
||||||
|
use crate::Font;
|
||||||
|
|
||||||
/// The settings of an application.
|
/// The settings of an application.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -20,23 +21,16 @@ pub struct Settings<Flags> {
|
||||||
/// [`Application`]: crate::Application
|
/// [`Application`]: crate::Application
|
||||||
pub flags: Flags,
|
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.
|
/// By default, it uses [`Font::SansSerif`].
|
||||||
// TODO: Add `name` for web compatibility
|
pub default_font: Font,
|
||||||
pub default_font: Option<&'static [u8]>,
|
|
||||||
|
|
||||||
/// The text size that will be used by default.
|
/// 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,
|
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
|
/// If set to true, the renderer will try to perform antialiasing for some
|
||||||
/// primitives.
|
/// primitives.
|
||||||
///
|
///
|
||||||
|
|
@ -55,15 +49,6 @@ pub struct Settings<Flags> {
|
||||||
///
|
///
|
||||||
/// [`Application`]: crate::Application
|
/// [`Application`]: crate::Application
|
||||||
pub exit_on_close_request: bool,
|
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> {
|
impl<Flags> Settings<Flags> {
|
||||||
|
|
@ -79,10 +64,8 @@ impl<Flags> Settings<Flags> {
|
||||||
window: default_settings.window,
|
window: default_settings.window,
|
||||||
default_font: default_settings.default_font,
|
default_font: default_settings.default_font,
|
||||||
default_text_size: default_settings.default_text_size,
|
default_text_size: default_settings.default_text_size,
|
||||||
text_multithreading: default_settings.text_multithreading,
|
|
||||||
antialiasing: default_settings.antialiasing,
|
antialiasing: default_settings.antialiasing,
|
||||||
exit_on_close_request: default_settings.exit_on_close_request,
|
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,
|
id: None,
|
||||||
window: Default::default(),
|
window: Default::default(),
|
||||||
flags: Default::default(),
|
flags: Default::default(),
|
||||||
default_font: Default::default(),
|
default_font: Font::SansSerif,
|
||||||
default_text_size: 20.0,
|
default_text_size: 16.0,
|
||||||
text_multithreading: false,
|
|
||||||
antialiasing: false,
|
antialiasing: false,
|
||||||
exit_on_close_request: true,
|
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(),
|
window: settings.window.into(),
|
||||||
flags: settings.flags,
|
flags: settings.flags,
|
||||||
exit_on_close_request: settings.exit_on_close_request,
|
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"]
|
farbfeld = ["iced_graphics/farbfeld"]
|
||||||
canvas = ["iced_graphics/canvas"]
|
canvas = ["iced_graphics/canvas"]
|
||||||
qr_code = ["iced_graphics/qr_code"]
|
qr_code = ["iced_graphics/qr_code"]
|
||||||
default_system_font = ["iced_graphics/font-source"]
|
|
||||||
spirv = ["wgpu/spirv"]
|
spirv = ["wgpu/spirv"]
|
||||||
webgl = ["wgpu/webgl"]
|
webgl = ["wgpu/webgl"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wgpu = "0.14"
|
wgpu = "0.14"
|
||||||
wgpu_glyph = "0.18"
|
|
||||||
glyph_brush = "0.7"
|
|
||||||
raw-window-handle = "0.5"
|
raw-window-handle = "0.5"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
guillotiere = "0.6"
|
guillotiere = "0.6"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
bitflags = "1.2"
|
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]
|
[dependencies.bytemuck]
|
||||||
version = "1.9"
|
version = "1.9"
|
||||||
|
|
@ -48,7 +56,11 @@ path = "../native"
|
||||||
[dependencies.iced_graphics]
|
[dependencies.iced_graphics]
|
||||||
version = "0.7"
|
version = "0.7"
|
||||||
path = "../graphics"
|
path = "../graphics"
|
||||||
features = ["font-fallback", "font-icons"]
|
|
||||||
|
[dependencies.glyphon]
|
||||||
|
version = "0.2"
|
||||||
|
git = "https://github.com/hecrj/glyphon.git"
|
||||||
|
rev = "65b481d758f50fd13fc21af2cc5ef62ddee64955"
|
||||||
|
|
||||||
[dependencies.tracing]
|
[dependencies.tracing]
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -4,11 +4,8 @@ use crate::triangle;
|
||||||
use crate::{Settings, Transformation};
|
use crate::{Settings, Transformation};
|
||||||
|
|
||||||
use iced_graphics::backend;
|
use iced_graphics::backend;
|
||||||
use iced_graphics::font;
|
|
||||||
use iced_graphics::layer::Layer;
|
use iced_graphics::layer::Layer;
|
||||||
use iced_graphics::{Primitive, Viewport};
|
use iced_graphics::{Color, Font, Primitive, Size, Viewport};
|
||||||
use iced_native::alignment;
|
|
||||||
use iced_native::{Font, Size};
|
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
use tracing::info_span;
|
use tracing::info_span;
|
||||||
|
|
@ -16,11 +13,13 @@ use tracing::info_span;
|
||||||
#[cfg(any(feature = "image", feature = "svg"))]
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
use crate::image;
|
use crate::image;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// A [`wgpu`] graphics backend for [`iced`].
|
/// A [`wgpu`] graphics backend for [`iced`].
|
||||||
///
|
///
|
||||||
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||||
/// [`iced`]: https://github.com/iced-rs/iced
|
/// [`iced`]: https://github.com/iced-rs/iced
|
||||||
#[derive(Debug)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Backend {
|
pub struct Backend {
|
||||||
quad_pipeline: quad::Pipeline,
|
quad_pipeline: quad::Pipeline,
|
||||||
text_pipeline: text::Pipeline,
|
text_pipeline: text::Pipeline,
|
||||||
|
|
@ -29,6 +28,7 @@ pub struct Backend {
|
||||||
#[cfg(any(feature = "image", feature = "svg"))]
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
image_pipeline: image::Pipeline,
|
image_pipeline: image::Pipeline,
|
||||||
|
|
||||||
|
default_font: Font,
|
||||||
default_text_size: f32,
|
default_text_size: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,16 +36,11 @@ impl Backend {
|
||||||
/// Creates a new [`Backend`].
|
/// Creates a new [`Backend`].
|
||||||
pub fn new(
|
pub fn new(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
format: wgpu::TextureFormat,
|
format: wgpu::TextureFormat,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let text_pipeline = text::Pipeline::new(
|
let text_pipeline = text::Pipeline::new(device, queue, format);
|
||||||
device,
|
|
||||||
format,
|
|
||||||
settings.default_font,
|
|
||||||
settings.text_multithreading,
|
|
||||||
);
|
|
||||||
|
|
||||||
let quad_pipeline = quad::Pipeline::new(device, format);
|
let quad_pipeline = quad::Pipeline::new(device, format);
|
||||||
let triangle_pipeline =
|
let triangle_pipeline =
|
||||||
triangle::Pipeline::new(device, format, settings.antialiasing);
|
triangle::Pipeline::new(device, format, settings.antialiasing);
|
||||||
|
|
@ -61,6 +56,7 @@ impl Backend {
|
||||||
#[cfg(any(feature = "image", feature = "svg"))]
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
image_pipeline,
|
image_pipeline,
|
||||||
|
|
||||||
|
default_font: settings.default_font,
|
||||||
default_text_size: settings.default_text_size,
|
default_text_size: settings.default_text_size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,8 +68,9 @@ impl Backend {
|
||||||
pub fn present<T: AsRef<str>>(
|
pub fn present<T: AsRef<str>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
staging_belt: &mut wgpu::util::StagingBelt,
|
queue: &wgpu::Queue,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
clear_color: Option<Color>,
|
||||||
frame: &wgpu::TextureView,
|
frame: &wgpu::TextureView,
|
||||||
primitives: &[Primitive],
|
primitives: &[Primitive],
|
||||||
viewport: &Viewport,
|
viewport: &Viewport,
|
||||||
|
|
@ -90,168 +87,246 @@ impl Backend {
|
||||||
let mut layers = Layer::generate(primitives, viewport);
|
let mut layers = Layer::generate(primitives, viewport);
|
||||||
layers.push(Layer::overlay(overlay_text, viewport));
|
layers.push(Layer::overlay(overlay_text, viewport));
|
||||||
|
|
||||||
for layer in layers {
|
self.prepare(
|
||||||
self.flush(
|
device,
|
||||||
device,
|
queue,
|
||||||
scale_factor,
|
encoder,
|
||||||
transformation,
|
scale_factor,
|
||||||
&layer,
|
transformation,
|
||||||
staging_belt,
|
&layers,
|
||||||
encoder,
|
);
|
||||||
frame,
|
|
||||||
target_size,
|
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"))]
|
#[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,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
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,
|
scale_factor: f32,
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
layer: &Layer<'_>,
|
layers: &[Layer<'_>],
|
||||||
staging_belt: &mut wgpu::util::StagingBelt,
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
|
||||||
target: &wgpu::TextureView,
|
|
||||||
target_size: Size<u32>,
|
|
||||||
) {
|
) {
|
||||||
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 {
|
if bounds.width < 1 || bounds.height < 1 {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !layer.quads.is_empty() {
|
if !layer.quads.is_empty() {
|
||||||
self.quad_pipeline.draw(
|
self.quad_pipeline.prepare(
|
||||||
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(
|
|
||||||
device,
|
device,
|
||||||
staging_belt,
|
queue,
|
||||||
encoder,
|
&layer.quads,
|
||||||
&layer.images,
|
transformation,
|
||||||
scaled,
|
|
||||||
bounds,
|
|
||||||
target,
|
|
||||||
scale_factor,
|
scale_factor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !layer.text.is_empty() {
|
if !layer.meshes.is_empty() {
|
||||||
for text in layer.text.iter() {
|
let scaled = transformation
|
||||||
// Target physical coordinates directly to avoid blurry text
|
* Transformation::scale(scale_factor, scale_factor);
|
||||||
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
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.text_pipeline.queue(text);
|
self.triangle_pipeline.prepare(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
&layer.meshes,
|
||||||
|
scaled,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.text_pipeline.draw_queued(
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
device,
|
{
|
||||||
staging_belt,
|
if !layer.images.is_empty() {
|
||||||
encoder,
|
let scaled = transformation
|
||||||
target,
|
* Transformation::scale(scale_factor, scale_factor);
|
||||||
transformation,
|
|
||||||
wgpu_glyph::Region {
|
self.image_pipeline.prepare(
|
||||||
x: bounds.x,
|
device,
|
||||||
y: bounds.y,
|
queue,
|
||||||
width: bounds.width,
|
_encoder,
|
||||||
height: bounds.height,
|
&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 {
|
impl iced_graphics::Backend for Backend {
|
||||||
|
|
@ -261,9 +336,13 @@ impl iced_graphics::Backend for Backend {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl backend::Text for Backend {
|
impl backend::Text for Backend {
|
||||||
const ICON_FONT: Font = font::ICONS;
|
const ICON_FONT: Font = Font::Name("Iced-Icons");
|
||||||
const CHECKMARK_ICON: char = font::CHECKMARK_ICON;
|
const CHECKMARK_ICON: char = '\u{f00c}';
|
||||||
const ARROW_DOWN_ICON: char = font::ARROW_DOWN_ICON;
|
const ARROW_DOWN_ICON: char = '\u{e800}';
|
||||||
|
|
||||||
|
fn default_font(&self) -> Font {
|
||||||
|
self.default_font
|
||||||
|
}
|
||||||
|
|
||||||
fn default_size(&self) -> f32 {
|
fn default_size(&self) -> f32 {
|
||||||
self.default_text_size
|
self.default_text_size
|
||||||
|
|
@ -297,6 +376,10 @@ impl backend::Text for Backend {
|
||||||
nearest_only,
|
nearest_only,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_font(&mut self, font: Cow<'static, [u8]>) {
|
||||||
|
self.text_pipeline.load_font(font);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,89 @@
|
||||||
//! Utilities for buffer operations.
|
//! Utilities for buffer operations.
|
||||||
pub mod dynamic;
|
pub mod dynamic;
|
||||||
pub mod r#static;
|
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.
|
/// Write the contents of this dynamic buffer to the GPU via staging belt command.
|
||||||
pub fn write(
|
pub fn write(&mut self, queue: &wgpu::Queue) {
|
||||||
&mut self,
|
queue.write_buffer(&self.gpu, 0, self.cpu.get_ref());
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the aligned offset at the given index from the CPU buffer.
|
// Gets the aligned offset at the given index from the CPU buffer.
|
||||||
|
|
@ -184,7 +167,7 @@ impl Internal {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns bytearray of aligned CPU buffer.
|
/// Returns bytearray of aligned CPU buffer.
|
||||||
pub(super) fn get_ref(&self) -> &Vec<u8> {
|
pub(super) fn get_ref(&self) -> &[u8] {
|
||||||
match self {
|
match self {
|
||||||
Internal::Uniform(buf) => buf.as_ref(),
|
Internal::Uniform(buf) => buf.as_ref(),
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ use bytemuck::{Pod, Zeroable};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
//128 triangles/indices
|
const DEFAULT_COUNT: wgpu::BufferAddress = 128;
|
||||||
const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 128;
|
|
||||||
|
|
||||||
/// A generic buffer struct useful for items which have no alignment requirements
|
/// A generic buffer struct useful for items which have no alignment requirements
|
||||||
/// (e.g. Vertex, Index buffers) & no dynamic offsets.
|
/// (e.g. Vertex, Index buffers) & no dynamic offsets.
|
||||||
|
|
@ -25,7 +24,7 @@ impl<T: Pod + Zeroable> Buffer<T> {
|
||||||
label: &'static str,
|
label: &'static str,
|
||||||
usages: wgpu::BufferUsages,
|
usages: wgpu::BufferUsages,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let size = (mem::size_of::<T>() as u64) * DEFAULT_STATIC_BUFFER_COUNT;
|
let size = (mem::size_of::<T>() as u64) * DEFAULT_COUNT;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
offsets: Vec::new(),
|
offsets: Vec::new(),
|
||||||
|
|
@ -57,9 +56,13 @@ impl<T: Pod + Zeroable> Buffer<T> {
|
||||||
let size = (mem::size_of::<T>() * new_count) as u64;
|
let size = (mem::size_of::<T>() * new_count) as u64;
|
||||||
|
|
||||||
if self.size < size {
|
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.offsets.clear();
|
||||||
self.size = size;
|
|
||||||
self.gpu = Self::gpu_buffer(device, self.label, size, self.usages);
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
@ -71,28 +74,15 @@ impl<T: Pod + Zeroable> Buffer<T> {
|
||||||
/// Returns the size of the written bytes.
|
/// Returns the size of the written bytes.
|
||||||
pub fn write(
|
pub fn write(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
queue: &wgpu::Queue,
|
||||||
staging_belt: &mut wgpu::util::StagingBelt,
|
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
|
||||||
offset: u64,
|
offset: u64,
|
||||||
content: &[T],
|
content: &[T],
|
||||||
) -> u64 {
|
) -> u64 {
|
||||||
let bytes = bytemuck::cast_slice(content);
|
let bytes = bytemuck::cast_slice(content);
|
||||||
let bytes_size = bytes.len() as u64;
|
let bytes_size = bytes.len() as u64;
|
||||||
|
|
||||||
if let Some(buffer_size) = wgpu::BufferSize::new(bytes_size) {
|
queue.write_buffer(&self.gpu, offset, bytes);
|
||||||
let mut buffer = staging_belt.write_buffer(
|
self.offsets.push(offset);
|
||||||
encoder,
|
|
||||||
&self.gpu,
|
|
||||||
offset,
|
|
||||||
buffer_size,
|
|
||||||
device,
|
|
||||||
);
|
|
||||||
|
|
||||||
buffer.copy_from_slice(bytes);
|
|
||||||
|
|
||||||
self.offsets.push(offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes_size
|
bytes_size
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use iced_graphics::image::raster;
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
use iced_graphics::image::vector;
|
use iced_graphics::image::vector;
|
||||||
|
|
||||||
use crate::Transformation;
|
use crate::{Buffer, Transformation};
|
||||||
use atlas::Atlas;
|
use atlas::Atlas;
|
||||||
|
|
||||||
use iced_graphics::layer;
|
use iced_graphics::layer;
|
||||||
|
|
@ -34,15 +34,107 @@ pub struct Pipeline {
|
||||||
vector_cache: RefCell<vector::Cache<Atlas>>,
|
vector_cache: RefCell<vector::Cache<Atlas>>,
|
||||||
|
|
||||||
pipeline: wgpu::RenderPipeline,
|
pipeline: wgpu::RenderPipeline,
|
||||||
uniforms: wgpu::Buffer,
|
|
||||||
vertices: wgpu::Buffer,
|
vertices: wgpu::Buffer,
|
||||||
indices: wgpu::Buffer,
|
indices: wgpu::Buffer,
|
||||||
instances: wgpu::Buffer,
|
sampler: wgpu::Sampler,
|
||||||
constants: wgpu::BindGroup,
|
|
||||||
texture: wgpu::BindGroup,
|
texture: wgpu::BindGroup,
|
||||||
texture_version: usize,
|
texture_version: usize,
|
||||||
texture_layout: wgpu::BindGroupLayout,
|
|
||||||
texture_atlas: Atlas,
|
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 {
|
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 =
|
let texture_layout =
|
||||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
label: Some("iced_wgpu::image texture atlas layout"),
|
label: Some("iced_wgpu::image texture atlas layout"),
|
||||||
|
|
@ -225,13 +288,6 @@ impl Pipeline {
|
||||||
usage: wgpu::BufferUsages::INDEX,
|
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_atlas = Atlas::new(device);
|
||||||
|
|
||||||
let texture = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
let texture = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
|
@ -253,15 +309,17 @@ impl Pipeline {
|
||||||
vector_cache: RefCell::new(vector::Cache::default()),
|
vector_cache: RefCell::new(vector::Cache::default()),
|
||||||
|
|
||||||
pipeline,
|
pipeline,
|
||||||
uniforms: uniforms_buffer,
|
|
||||||
vertices,
|
vertices,
|
||||||
indices,
|
indices,
|
||||||
instances,
|
sampler,
|
||||||
constants: constant_bind_group,
|
|
||||||
texture,
|
texture,
|
||||||
texture_version: texture_atlas.layer_count(),
|
texture_version: texture_atlas.layer_count(),
|
||||||
texture_layout,
|
|
||||||
texture_atlas,
|
texture_atlas,
|
||||||
|
texture_layout,
|
||||||
|
constant_layout,
|
||||||
|
|
||||||
|
layers: Vec::new(),
|
||||||
|
prepare_layer: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -281,17 +339,18 @@ impl Pipeline {
|
||||||
svg.viewport_dimensions()
|
svg.viewport_dimensions()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(
|
pub fn prepare(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
staging_belt: &mut wgpu::util::StagingBelt,
|
queue: &wgpu::Queue,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
images: &[layer::Image],
|
images: &[layer::Image],
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
bounds: Rectangle<u32>,
|
|
||||||
target: &wgpu::TextureView,
|
|
||||||
_scale: f32,
|
_scale: f32,
|
||||||
) {
|
) {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
let _ = info_span!("Wgpu::Image", "PREPARE").entered();
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
let _ = info_span!("Wgpu::Image", "DRAW").entered();
|
let _ = info_span!("Wgpu::Image", "DRAW").entered();
|
||||||
|
|
||||||
|
|
@ -309,7 +368,7 @@ impl Pipeline {
|
||||||
layer::Image::Raster { handle, bounds } => {
|
layer::Image::Raster { handle, bounds } => {
|
||||||
if let Some(atlas_entry) = raster_cache.upload(
|
if let Some(atlas_entry) = raster_cache.upload(
|
||||||
handle,
|
handle,
|
||||||
&mut (device, encoder),
|
&mut (device, queue, encoder),
|
||||||
&mut self.texture_atlas,
|
&mut self.texture_atlas,
|
||||||
) {
|
) {
|
||||||
add_instances(
|
add_instances(
|
||||||
|
|
@ -336,7 +395,7 @@ impl Pipeline {
|
||||||
*color,
|
*color,
|
||||||
size,
|
size,
|
||||||
_scale,
|
_scale,
|
||||||
&mut (device, encoder),
|
&mut (device, queue, encoder),
|
||||||
&mut self.texture_atlas,
|
&mut self.texture_atlas,
|
||||||
) {
|
) {
|
||||||
add_instances(
|
add_instances(
|
||||||
|
|
@ -376,68 +435,28 @@ impl Pipeline {
|
||||||
self.texture_version = texture_version;
|
self.texture_version = texture_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
if self.layers.len() <= self.prepare_layer {
|
||||||
let mut uniforms_buffer = staging_belt.write_buffer(
|
self.layers.push(Layer::new(
|
||||||
encoder,
|
|
||||||
&self.uniforms,
|
|
||||||
0,
|
|
||||||
wgpu::BufferSize::new(mem::size_of::<Uniforms>() as u64)
|
|
||||||
.unwrap(),
|
|
||||||
device,
|
device,
|
||||||
);
|
&self.constant_layout,
|
||||||
|
&self.sampler,
|
||||||
uniforms_buffer.copy_from_slice(bytemuck::bytes_of(&Uniforms {
|
));
|
||||||
transform: transformation.into(),
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut i = 0;
|
let layer = &mut self.layers[self.prepare_layer];
|
||||||
let total = instances.len();
|
layer.prepare(device, queue, instances, transformation);
|
||||||
|
|
||||||
while i < total {
|
self.prepare_layer += 1;
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
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_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(
|
render_pass.set_scissor_rect(
|
||||||
bounds.x,
|
bounds.x,
|
||||||
|
|
@ -446,30 +465,34 @@ impl Pipeline {
|
||||||
bounds.height,
|
bounds.height,
|
||||||
);
|
);
|
||||||
|
|
||||||
render_pass.draw_indexed(
|
render_pass.set_bind_group(1, &self.texture, &[]);
|
||||||
0..QUAD_INDICES.len() as u32,
|
render_pass.set_index_buffer(
|
||||||
0,
|
self.indices.slice(..),
|
||||||
0..amount as u32,
|
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,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
) {
|
) {
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
self.raster_cache
|
self.raster_cache
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.trim(&mut self.texture_atlas, &mut (device, encoder));
|
.trim(&mut self.texture_atlas, &mut (device, queue, encoder));
|
||||||
|
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
self.vector_cache
|
self.vector_cache
|
||||||
.borrow_mut()
|
.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 {
|
impl Instance {
|
||||||
pub const MAX: usize = 1_000;
|
pub const INITIAL: usize = 1_000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
|
|
||||||
|
|
@ -185,13 +185,13 @@ impl Atlas {
|
||||||
|
|
||||||
fn upload_allocation(
|
fn upload_allocation(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &wgpu::Buffer,
|
data: &[u8],
|
||||||
image_width: u32,
|
image_width: u32,
|
||||||
image_height: u32,
|
image_height: u32,
|
||||||
padding: u32,
|
padding: u32,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
allocation: &Allocation,
|
allocation: &Allocation,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
queue: &wgpu::Queue,
|
||||||
) {
|
) {
|
||||||
let (x, y) = allocation.position();
|
let (x, y) = allocation.position();
|
||||||
let Size { width, height } = allocation.size();
|
let Size { width, height } = allocation.size();
|
||||||
|
|
@ -203,15 +203,7 @@ impl Atlas {
|
||||||
depth_or_array_layers: 1,
|
depth_or_array_layers: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
encoder.copy_buffer_to_texture(
|
queue.write_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),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wgpu::ImageCopyTexture {
|
wgpu::ImageCopyTexture {
|
||||||
texture: &self.texture,
|
texture: &self.texture,
|
||||||
mip_level: 0,
|
mip_level: 0,
|
||||||
|
|
@ -222,6 +214,12 @@ impl Atlas {
|
||||||
},
|
},
|
||||||
aspect: wgpu::TextureAspect::default(),
|
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,
|
extent,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -301,17 +299,19 @@ impl Atlas {
|
||||||
|
|
||||||
impl image::Storage for Atlas {
|
impl image::Storage for Atlas {
|
||||||
type Entry = Entry;
|
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(
|
fn upload(
|
||||||
&mut self,
|
&mut self,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
(device, encoder): &mut Self::State<'_>,
|
(device, queue, encoder): &mut Self::State<'_>,
|
||||||
) -> Option<Self::Entry> {
|
) -> Option<Self::Entry> {
|
||||||
use wgpu::util::DeviceExt;
|
|
||||||
|
|
||||||
let entry = {
|
let entry = {
|
||||||
let current_size = self.layers.len();
|
let current_size = self.layers.len();
|
||||||
let entry = self.allocate(width, height)?;
|
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 {
|
match &entry {
|
||||||
Entry::Contiguous(allocation) => {
|
Entry::Contiguous(allocation) => {
|
||||||
self.upload_allocation(
|
self.upload_allocation(
|
||||||
&buffer, width, height, padding, 0, allocation, encoder,
|
&padded_data,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
padding,
|
||||||
|
0,
|
||||||
|
allocation,
|
||||||
|
queue,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Entry::Fragmented { fragments, .. } => {
|
Entry::Fragmented { fragments, .. } => {
|
||||||
|
|
@ -363,13 +362,13 @@ impl image::Storage for Atlas {
|
||||||
let offset = (y * padded_width as u32 + 4 * x) as usize;
|
let offset = (y * padded_width as u32 + 4 * x) as usize;
|
||||||
|
|
||||||
self.upload_allocation(
|
self.upload_allocation(
|
||||||
&buffer,
|
&padded_data,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
padding,
|
padding,
|
||||||
offset,
|
offset,
|
||||||
&fragment.allocation,
|
&fragment.allocation,
|
||||||
encoder,
|
queue,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,14 +47,17 @@ mod quad;
|
||||||
mod text;
|
mod text;
|
||||||
mod triangle;
|
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 iced_native::Theme;
|
||||||
pub use wgpu;
|
pub use wgpu;
|
||||||
|
|
||||||
pub use backend::Backend;
|
pub use backend::Backend;
|
||||||
pub use settings::Settings;
|
pub use settings::Settings;
|
||||||
|
|
||||||
pub(crate) use iced_graphics::Transformation;
|
use crate::buffer::Buffer;
|
||||||
|
use iced_graphics::Transformation;
|
||||||
|
|
||||||
#[cfg(any(feature = "image", feature = "svg"))]
|
#[cfg(any(feature = "image", feature = "svg"))]
|
||||||
mod image;
|
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_graphics::layer;
|
||||||
use iced_native::Rectangle;
|
use iced_native::Rectangle;
|
||||||
|
|
||||||
|
|
@ -12,11 +12,11 @@ use tracing::info_span;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Pipeline {
|
pub struct Pipeline {
|
||||||
pipeline: wgpu::RenderPipeline,
|
pipeline: wgpu::RenderPipeline,
|
||||||
constants: wgpu::BindGroup,
|
constant_layout: wgpu::BindGroupLayout,
|
||||||
constants_buffer: wgpu::Buffer,
|
|
||||||
vertices: wgpu::Buffer,
|
vertices: wgpu::Buffer,
|
||||||
indices: wgpu::Buffer,
|
indices: wgpu::Buffer,
|
||||||
instances: wgpu::Buffer,
|
layers: Vec<Layer>,
|
||||||
|
prepare_layer: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pipeline {
|
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 =
|
let layout =
|
||||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("iced_wgpu::quad pipeline layout"),
|
label: Some("iced_wgpu::quad pipeline layout"),
|
||||||
|
|
@ -148,117 +132,145 @@ impl Pipeline {
|
||||||
usage: wgpu::BufferUsages::INDEX,
|
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 {
|
||||||
pipeline,
|
pipeline,
|
||||||
constants,
|
constant_layout,
|
||||||
constants_buffer,
|
|
||||||
vertices,
|
vertices,
|
||||||
indices,
|
indices,
|
||||||
instances,
|
layers: Vec::new(),
|
||||||
|
prepare_layer: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(
|
pub fn prepare(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
staging_belt: &mut wgpu::util::StagingBelt,
|
queue: &wgpu::Queue,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
|
||||||
instances: &[layer::Quad],
|
instances: &[layer::Quad],
|
||||||
transformation: Transformation,
|
transformation: Transformation,
|
||||||
scale: f32,
|
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>,
|
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")]
|
#[cfg(feature = "tracing")]
|
||||||
let _ = info_span!("Wgpu::Quad", "DRAW").entered();
|
let _ = info_span!("Wgpu::Quad", "PREPARE").entered();
|
||||||
|
|
||||||
let uniforms = Uniforms::new(transformation, scale);
|
let uniforms = Uniforms::new(transformation, scale);
|
||||||
|
|
||||||
{
|
queue.write_buffer(
|
||||||
let mut constants_buffer = staging_belt.write_buffer(
|
&self.constants_buffer,
|
||||||
encoder,
|
0,
|
||||||
&self.constants_buffer,
|
bytemuck::bytes_of(&uniforms),
|
||||||
0,
|
);
|
||||||
wgpu::BufferSize::new(mem::size_of::<Uniforms>() as u64)
|
|
||||||
.unwrap(),
|
|
||||||
device,
|
|
||||||
);
|
|
||||||
|
|
||||||
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;
|
pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
|
||||||
let total = instances.len();
|
#[cfg(feature = "tracing")]
|
||||||
|
let _ = info_span!("Wgpu::Quad", "DRAW").entered();
|
||||||
|
|
||||||
while i < total {
|
render_pass.set_bind_group(0, &self.constants, &[]);
|
||||||
let end = (i + MAX_INSTANCES).min(total);
|
render_pass.set_vertex_buffer(1, self.instances.slice(..));
|
||||||
let amount = end - i;
|
|
||||||
|
|
||||||
let instance_bytes = bytemuck::cast_slice(&instances[i..end]);
|
render_pass.draw_indexed(
|
||||||
|
0..QUAD_INDICES.len() as u32,
|
||||||
let mut instance_buffer = staging_belt.write_buffer(
|
0,
|
||||||
encoder,
|
0..self.instance_count as u32,
|
||||||
&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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,7 +297,7 @@ const QUAD_VERTS: [Vertex; 4] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const MAX_INSTANCES: usize = 100_000;
|
const INITIAL_INSTANCES: usize = 10_000;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
//! Configure a renderer.
|
//! Configure a renderer.
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
pub use crate::Antialiasing;
|
pub use crate::Antialiasing;
|
||||||
|
|
||||||
|
use crate::Font;
|
||||||
|
|
||||||
/// The settings of a [`Backend`].
|
/// The settings of a [`Backend`].
|
||||||
///
|
///
|
||||||
/// [`Backend`]: crate::Backend
|
/// [`Backend`]: crate::Backend
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
/// The present mode of the [`Backend`].
|
/// The present mode of the [`Backend`].
|
||||||
///
|
///
|
||||||
|
|
@ -16,42 +16,20 @@ pub struct Settings {
|
||||||
/// The internal graphics backend to use.
|
/// The internal graphics backend to use.
|
||||||
pub internal_backend: wgpu::Backends,
|
pub internal_backend: wgpu::Backends,
|
||||||
|
|
||||||
/// The bytes of the font that will be used by default.
|
/// The default [`Font`] to use.
|
||||||
///
|
pub default_font: Font,
|
||||||
/// If `None` is provided, a default system font will be chosen.
|
|
||||||
pub default_font: Option<&'static [u8]>,
|
|
||||||
|
|
||||||
/// The default size of text.
|
/// The default size of text.
|
||||||
///
|
///
|
||||||
/// By default, it will be set to `16.0`.
|
/// By default, it will be set to `16.0`.
|
||||||
pub default_text_size: f32,
|
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.
|
/// The antialiasing strategy that will be used for triangle primitives.
|
||||||
///
|
///
|
||||||
/// By default, it is `None`.
|
/// By default, it is `None`.
|
||||||
pub antialiasing: Option<Antialiasing>,
|
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 {
|
impl Settings {
|
||||||
/// Creates new [`Settings`] using environment configuration.
|
/// Creates new [`Settings`] using environment configuration.
|
||||||
///
|
///
|
||||||
|
|
@ -81,9 +59,8 @@ impl Default for Settings {
|
||||||
Settings {
|
Settings {
|
||||||
present_mode: wgpu::PresentMode::AutoVsync,
|
present_mode: wgpu::PresentMode::AutoVsync,
|
||||||
internal_backend: wgpu::Backends::all(),
|
internal_backend: wgpu::Backends::all(),
|
||||||
default_font: None,
|
default_font: Font::SansSerif,
|
||||||
default_text_size: 20.0,
|
default_text_size: 16.0,
|
||||||
text_multithreading: false,
|
|
||||||
antialiasing: None,
|
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;
|
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 {
|
pub struct Pipeline {
|
||||||
draw_brush: RefCell<wgpu_glyph::GlyphBrush<()>>,
|
system: Option<System>,
|
||||||
draw_font_map: RefCell<HashMap<String, wgpu_glyph::FontId>>,
|
renderers: Vec<glyphon::TextRenderer>,
|
||||||
measure_brush: RefCell<glyph_brush::GlyphBrush<()>>,
|
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 {
|
impl Pipeline {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
format: wgpu::TextureFormat,
|
format: wgpu::TextureFormat,
|
||||||
default_font: Option<&[u8]>,
|
|
||||||
multithreading: bool,
|
|
||||||
) -> Self {
|
) -> 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 {
|
Pipeline {
|
||||||
draw_brush: RefCell::new(draw_brush),
|
system: Some(
|
||||||
draw_font_map: RefCell::new(HashMap::new()),
|
SystemBuilder {
|
||||||
measure_brush: RefCell::new(measure_brush),
|
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<'_>) {
|
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
||||||
self.draw_brush.borrow_mut().queue(section);
|
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,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
staging_belt: &mut wgpu::util::StagingBelt,
|
queue: &wgpu::Queue,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
sections: &[Text<'_>],
|
||||||
target: &wgpu::TextureView,
|
bounds: Rectangle,
|
||||||
transformation: Transformation,
|
scale_factor: f32,
|
||||||
region: wgpu_glyph::Region,
|
target_size: Size<u32>,
|
||||||
) {
|
) -> bool {
|
||||||
self.draw_brush
|
self.system.as_mut().unwrap().with_mut(|fields| {
|
||||||
.borrow_mut()
|
if self.renderers.len() <= self.prepare_layer {
|
||||||
.draw_queued_with_transform_and_scissoring(
|
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,
|
device,
|
||||||
staging_belt,
|
queue,
|
||||||
encoder,
|
&mut self.atlas,
|
||||||
target,
|
glyphon::Resolution {
|
||||||
transformation.into(),
|
width: target_size.width,
|
||||||
region,
|
height: target_size.height,
|
||||||
)
|
},
|
||||||
.expect("Draw text");
|
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(
|
pub fn measure(
|
||||||
&self,
|
&self,
|
||||||
content: &str,
|
content: &str,
|
||||||
size: f32,
|
size: f32,
|
||||||
font: iced_native::Font,
|
font: Font,
|
||||||
bounds: iced_native::Size,
|
bounds: Size,
|
||||||
) -> (f32, f32) {
|
) -> (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 {
|
let (total_lines, max_width) = paragraph
|
||||||
bounds: (bounds.width, bounds.height),
|
.layout_runs()
|
||||||
text: vec![wgpu_glyph::Text {
|
.enumerate()
|
||||||
text: content,
|
.fold((0, 0.0), |(_, max), (i, buffer)| {
|
||||||
scale: size.into(),
|
(i + 1, buffer.line_w.max(max))
|
||||||
font_id: wgpu_glyph::FontId(font_id),
|
});
|
||||||
extra: wgpu_glyph::Extra::default(),
|
|
||||||
}],
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(bounds) =
|
(max_width, size * 1.2 * total_lines as f32)
|
||||||
self.measure_brush.borrow_mut().glyph_bounds(section)
|
})
|
||||||
{
|
|
||||||
(bounds.width().ceil(), bounds.height().ceil())
|
|
||||||
} else {
|
|
||||||
(0.0, 0.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hit_test(
|
pub fn hit_test(
|
||||||
|
|
@ -132,134 +271,148 @@ impl Pipeline {
|
||||||
font: iced_native::Font,
|
font: iced_native::Font,
|
||||||
bounds: iced_native::Size,
|
bounds: iced_native::Size,
|
||||||
point: iced_native::Point,
|
point: iced_native::Point,
|
||||||
nearest_only: bool,
|
_nearest_only: bool,
|
||||||
) -> Option<Hit> {
|
) -> 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 {
|
let cursor = paragraph.hit(point.x, point.y)?;
|
||||||
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 mut mb = self.measure_brush.borrow_mut();
|
Some(Hit::CharOffset(cursor.index))
|
||||||
|
|
||||||
// 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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trim_measurement_cache(&mut self) {
|
pub fn trim_measurement_cache(&mut self) {
|
||||||
// TODO: We should probably use a `GlyphCalculator` for this. However,
|
self.system
|
||||||
// it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop.
|
.as_mut()
|
||||||
// This makes stuff quite inconvenient. A manual method for trimming the
|
.unwrap()
|
||||||
// cache would make our lives easier.
|
.with_measurement_cache_mut(|cache| cache.borrow_mut().trim());
|
||||||
loop {
|
}
|
||||||
let action = self
|
}
|
||||||
.measure_brush
|
|
||||||
.borrow_mut()
|
|
||||||
.process_queued(|_, _| {}, |_| {});
|
|
||||||
|
|
||||||
match action {
|
fn to_family(font: Font) -> glyphon::Family<'static> {
|
||||||
Ok(_) => break,
|
match font {
|
||||||
Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => {
|
Font::Name(name) => glyphon::Family::Name(name),
|
||||||
let (width, height) = suggested;
|
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
|
struct Cache<'a> {
|
||||||
.borrow_mut()
|
entries: FxHashMap<KeyHash, glyphon::Buffer<'a>>,
|
||||||
.resize_texture(width, height);
|
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 {
|
fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'a>> {
|
||||||
match font {
|
self.entries.get(key)
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
let font = ab_glyph::FontArc::try_from_slice(bytes)
|
fn allocate(
|
||||||
.expect("Load font");
|
&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
|
if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) {
|
||||||
.draw_font_map
|
let metrics = glyphon::Metrics::new(key.size, key.size * 1.2);
|
||||||
.borrow_mut()
|
let mut buffer = glyphon::Buffer::new(fonts, metrics);
|
||||||
.insert(String::from(name), font_id);
|
|
||||||
|
|
||||||
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)]
|
#[derive(Debug)]
|
||||||
pub struct Pipeline {
|
pub struct Pipeline {
|
||||||
blit: Option<msaa::Blit>,
|
blit: Option<msaa::Blit>,
|
||||||
index_buffer: Buffer<u32>,
|
|
||||||
index_strides: Vec<u32>,
|
|
||||||
solid: solid::Pipeline,
|
solid: solid::Pipeline,
|
||||||
|
|
||||||
/// Gradients are currently not supported on WASM targets due to their need of storage buffers.
|
/// Gradients are currently not supported on WASM targets due to their need of storage buffers.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
gradient: gradient::Pipeline,
|
gradient: gradient::Pipeline,
|
||||||
|
|
||||||
|
layers: Vec<Layer>,
|
||||||
|
prepare_layer: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pipeline {
|
#[derive(Debug)]
|
||||||
pub fn new(
|
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,
|
device: &wgpu::Device,
|
||||||
format: wgpu::TextureFormat,
|
solid: &solid::Pipeline,
|
||||||
antialiasing: Option<settings::Antialiasing>,
|
#[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline,
|
||||||
) -> Pipeline {
|
) -> Self {
|
||||||
Pipeline {
|
Self {
|
||||||
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
|
|
||||||
index_buffer: Buffer::new(
|
index_buffer: Buffer::new(
|
||||||
device,
|
device,
|
||||||
"iced_wgpu::triangle vertex buffer",
|
"iced_wgpu::triangle index buffer",
|
||||||
wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
|
wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
|
||||||
),
|
),
|
||||||
index_strides: Vec::new(),
|
index_strides: Vec::new(),
|
||||||
solid: solid::Pipeline::new(device, format, antialiasing),
|
solid: solid::Layer::new(device, &solid.constants_layout),
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[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,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
staging_belt: &mut wgpu::util::StagingBelt,
|
queue: &wgpu::Queue,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
solid: &solid::Pipeline,
|
||||||
target: &wgpu::TextureView,
|
#[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline,
|
||||||
target_size: Size<u32>,
|
|
||||||
transformation: Transformation,
|
|
||||||
scale_factor: f32,
|
|
||||||
meshes: &[Mesh<'_>],
|
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
|
// Count the total amount of vertices & indices we need to handle
|
||||||
let count = mesh::attribute_count_of(meshes);
|
let count = mesh::attribute_count_of(meshes);
|
||||||
|
|
||||||
|
|
@ -75,6 +80,7 @@ impl Pipeline {
|
||||||
.resize(device, count.gradient_vertices);
|
.resize(device, count.gradient_vertices);
|
||||||
|
|
||||||
// Prepare dynamic buffers & data store for writing
|
// Prepare dynamic buffers & data store for writing
|
||||||
|
self.index_buffer.clear();
|
||||||
self.index_strides.clear();
|
self.index_strides.clear();
|
||||||
self.solid.vertices.clear();
|
self.solid.vertices.clear();
|
||||||
self.solid.uniforms.clear();
|
self.solid.uniforms.clear();
|
||||||
|
|
@ -99,13 +105,8 @@ impl Pipeline {
|
||||||
let transform =
|
let transform =
|
||||||
transformation * Transformation::translate(origin.x, origin.y);
|
transformation * Transformation::translate(origin.x, origin.y);
|
||||||
|
|
||||||
let new_index_offset = self.index_buffer.write(
|
let new_index_offset =
|
||||||
device,
|
self.index_buffer.write(queue, index_offset, indices);
|
||||||
staging_belt,
|
|
||||||
encoder,
|
|
||||||
index_offset,
|
|
||||||
indices,
|
|
||||||
);
|
|
||||||
|
|
||||||
index_offset += new_index_offset;
|
index_offset += new_index_offset;
|
||||||
self.index_strides.push(indices.len() as u32);
|
self.index_strides.push(indices.len() as u32);
|
||||||
|
|
@ -116,9 +117,7 @@ impl Pipeline {
|
||||||
self.solid.uniforms.push(&solid::Uniforms::new(transform));
|
self.solid.uniforms.push(&solid::Uniforms::new(transform));
|
||||||
|
|
||||||
let written_bytes = self.solid.vertices.write(
|
let written_bytes = self.solid.vertices.write(
|
||||||
device,
|
queue,
|
||||||
staging_belt,
|
|
||||||
encoder,
|
|
||||||
solid_vertex_offset,
|
solid_vertex_offset,
|
||||||
&buffers.vertices,
|
&buffers.vertices,
|
||||||
);
|
);
|
||||||
|
|
@ -130,9 +129,7 @@ impl Pipeline {
|
||||||
buffers, gradient, ..
|
buffers, gradient, ..
|
||||||
} => {
|
} => {
|
||||||
let written_bytes = self.gradient.vertices.write(
|
let written_bytes = self.gradient.vertices.write(
|
||||||
device,
|
queue,
|
||||||
staging_belt,
|
|
||||||
encoder,
|
|
||||||
gradient_vertex_offset,
|
gradient_vertex_offset,
|
||||||
&buffers.vertices,
|
&buffers.vertices,
|
||||||
);
|
);
|
||||||
|
|
@ -196,14 +193,14 @@ impl Pipeline {
|
||||||
let uniforms_resized = self.solid.uniforms.resize(device);
|
let uniforms_resized = self.solid.uniforms.resize(device);
|
||||||
|
|
||||||
if uniforms_resized {
|
if uniforms_resized {
|
||||||
self.solid.bind_group = solid::Pipeline::bind_group(
|
self.solid.constants = solid::Layer::bind_group(
|
||||||
device,
|
device,
|
||||||
self.solid.uniforms.raw(),
|
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"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
@ -218,22 +215,169 @@ impl Pipeline {
|
||||||
let storage_resized = self.gradient.storage.resize(device);
|
let storage_resized = self.gradient.storage.resize(device);
|
||||||
|
|
||||||
if uniforms_resized || storage_resized {
|
if uniforms_resized || storage_resized {
|
||||||
self.gradient.bind_group = gradient::Pipeline::bind_group(
|
self.gradient.constants = gradient::Layer::bind_group(
|
||||||
device,
|
device,
|
||||||
self.gradient.uniforms.raw(),
|
self.gradient.uniforms.raw(),
|
||||||
self.gradient.storage.raw(),
|
self.gradient.storage.raw(),
|
||||||
&self.gradient.bind_group_layout,
|
&gradient.constants_layout,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write to GPU
|
// Write to GPU
|
||||||
self.gradient.uniforms.write(device, staging_belt, encoder);
|
self.gradient.uniforms.write(queue);
|
||||||
self.gradient.storage.write(device, staging_belt, encoder);
|
self.gradient.storage.write(queue);
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
self.gradient.color_stop_offset = 0;
|
self.gradient.color_stop_offset = 0;
|
||||||
self.gradient.color_stops_pending_write.color_stops.clear();
|
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
|
// Configure render pass
|
||||||
{
|
{
|
||||||
|
|
@ -268,87 +412,26 @@ impl Pipeline {
|
||||||
depth_stencil_attachment: None,
|
depth_stencil_attachment: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut num_solids = 0;
|
let layer = &mut self.layers[layer];
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
let mut num_gradients = 0;
|
|
||||||
let mut last_is_solid = None;
|
|
||||||
|
|
||||||
for (index, mesh) in meshes.iter().enumerate() {
|
layer.render(
|
||||||
let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
|
&self.solid,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
render_pass.set_scissor_rect(
|
&self.gradient,
|
||||||
clip_bounds.x,
|
meshes,
|
||||||
clip_bounds.y,
|
scale_factor,
|
||||||
clip_bounds.width,
|
&mut render_pass,
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.index_buffer.clear();
|
|
||||||
|
|
||||||
if let Some(blit) = &mut self.blit {
|
if let Some(blit) = &mut self.blit {
|
||||||
blit.draw(encoder, target);
|
blit.draw(encoder, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn end_frame(&mut self) {
|
||||||
|
self.prepare_layer = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fragment_target(
|
fn fragment_target(
|
||||||
|
|
@ -390,10 +473,62 @@ mod solid {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Pipeline {
|
pub struct Pipeline {
|
||||||
pub pipeline: wgpu::RenderPipeline,
|
pub pipeline: wgpu::RenderPipeline,
|
||||||
|
pub constants_layout: wgpu::BindGroupLayout,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Layer {
|
||||||
pub vertices: Buffer<triangle::ColoredVertex2D>,
|
pub vertices: Buffer<triangle::ColoredVertex2D>,
|
||||||
pub uniforms: dynamic::Buffer<Uniforms>,
|
pub uniforms: dynamic::Buffer<Uniforms>,
|
||||||
pub bind_group_layout: wgpu::BindGroupLayout,
|
pub constants: wgpu::BindGroup,
|
||||||
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::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)]
|
#[derive(Debug, Clone, Copy, ShaderType)]
|
||||||
|
|
@ -416,18 +551,7 @@ mod solid {
|
||||||
format: wgpu::TextureFormat,
|
format: wgpu::TextureFormat,
|
||||||
antialiasing: Option<settings::Antialiasing>,
|
antialiasing: Option<settings::Antialiasing>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let vertices = Buffer::new(
|
let constants_layout = device.create_bind_group_layout(
|
||||||
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(
|
|
||||||
&wgpu::BindGroupLayoutDescriptor {
|
&wgpu::BindGroupLayoutDescriptor {
|
||||||
label: Some("iced_wgpu::triangle::solid bind group layout"),
|
label: Some("iced_wgpu::triangle::solid bind group layout"),
|
||||||
entries: &[wgpu::BindGroupLayoutEntry {
|
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(
|
let layout = device.create_pipeline_layout(
|
||||||
&wgpu::PipelineLayoutDescriptor {
|
&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("iced_wgpu::triangle::solid pipeline layout"),
|
label: Some("iced_wgpu::triangle::solid pipeline layout"),
|
||||||
bind_group_layouts: &[&bind_group_layout],
|
bind_group_layouts: &[&constants_layout],
|
||||||
push_constant_ranges: &[],
|
push_constant_ranges: &[],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -501,33 +622,9 @@ mod solid {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
pipeline,
|
pipeline,
|
||||||
vertices,
|
constants_layout,
|
||||||
uniforms,
|
|
||||||
bind_group_layout,
|
|
||||||
bind_group,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)]
|
#[derive(Debug)]
|
||||||
pub struct Pipeline {
|
pub struct Pipeline {
|
||||||
pub pipeline: wgpu::RenderPipeline,
|
pub pipeline: wgpu::RenderPipeline,
|
||||||
|
pub constants_layout: wgpu::BindGroupLayout,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Layer {
|
||||||
pub vertices: Buffer<Vertex2D>,
|
pub vertices: Buffer<Vertex2D>,
|
||||||
pub uniforms: dynamic::Buffer<Uniforms>,
|
pub uniforms: dynamic::Buffer<Uniforms>,
|
||||||
pub storage: dynamic::Buffer<Storage>,
|
pub storage: dynamic::Buffer<Storage>,
|
||||||
|
pub constants: wgpu::BindGroup,
|
||||||
pub color_stop_offset: i32,
|
pub color_stop_offset: i32,
|
||||||
//Need to store these and then write them all at once
|
//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
|
//or else they will be padded to 256 and cause gaps in the storage buffer
|
||||||
pub color_stops_pending_write: Storage,
|
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)]
|
#[derive(Debug, ShaderType)]
|
||||||
|
|
@ -584,25 +756,7 @@ mod gradient {
|
||||||
format: wgpu::TextureFormat,
|
format: wgpu::TextureFormat,
|
||||||
antialiasing: Option<settings::Antialiasing>,
|
antialiasing: Option<settings::Antialiasing>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let vertices = Buffer::new(
|
let constants_layout = device.create_bind_group_layout(
|
||||||
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(
|
|
||||||
&wgpu::BindGroupLayoutDescriptor {
|
&wgpu::BindGroupLayoutDescriptor {
|
||||||
label: Some(
|
label: Some(
|
||||||
"iced_wgpu::triangle::gradient bind group layout",
|
"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(
|
let layout = device.create_pipeline_layout(
|
||||||
&wgpu::PipelineLayoutDescriptor {
|
&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some(
|
label: Some(
|
||||||
"iced_wgpu::triangle::gradient pipeline layout",
|
"iced_wgpu::triangle::gradient pipeline layout",
|
||||||
),
|
),
|
||||||
bind_group_layouts: &[&bind_group_layout],
|
bind_group_layouts: &[&constants_layout],
|
||||||
push_constant_ranges: &[],
|
push_constant_ranges: &[],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -694,44 +841,8 @@ mod gradient {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
pipeline,
|
pipeline,
|
||||||
vertices,
|
constants_layout,
|
||||||
uniforms,
|
|
||||||
storage,
|
|
||||||
color_stop_offset: 0,
|
|
||||||
color_stops_pending_write: Storage {
|
|
||||||
color_stops: vec![],
|
|
||||||
},
|
|
||||||
bind_group_layout,
|
|
||||||
bind_group,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
adapter: wgpu::Adapter,
|
||||||
device: wgpu::Device,
|
device: wgpu::Device,
|
||||||
queue: wgpu::Queue,
|
queue: wgpu::Queue,
|
||||||
staging_belt: wgpu::util::StagingBelt,
|
|
||||||
format: wgpu::TextureFormat,
|
format: wgpu::TextureFormat,
|
||||||
theme: PhantomData<Theme>,
|
theme: PhantomData<Theme>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Theme> Compositor<Theme> {
|
impl<Theme> Compositor<Theme> {
|
||||||
const CHUNK_SIZE: u64 = 10 * 1024;
|
|
||||||
|
|
||||||
/// Requests a new [`Compositor`] with the given [`Settings`].
|
/// Requests a new [`Compositor`] with the given [`Settings`].
|
||||||
///
|
///
|
||||||
/// Returns `None` if no compatible graphics adapter could be found.
|
/// Returns `None` if no compatible graphics adapter could be found.
|
||||||
|
|
@ -98,15 +95,12 @@ impl<Theme> Compositor<Theme> {
|
||||||
.next()
|
.next()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let staging_belt = wgpu::util::StagingBelt::new(Self::CHUNK_SIZE);
|
|
||||||
|
|
||||||
Some(Compositor {
|
Some(Compositor {
|
||||||
instance,
|
instance,
|
||||||
settings,
|
settings,
|
||||||
adapter,
|
adapter,
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
staging_belt,
|
|
||||||
format,
|
format,
|
||||||
theme: PhantomData,
|
theme: PhantomData,
|
||||||
})
|
})
|
||||||
|
|
@ -114,7 +108,7 @@ impl<Theme> Compositor<Theme> {
|
||||||
|
|
||||||
/// Creates a new rendering [`Backend`] for this [`Compositor`].
|
/// Creates a new rendering [`Backend`] for this [`Compositor`].
|
||||||
pub fn create_backend(&self) -> Backend {
|
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
|
.texture
|
||||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
.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| {
|
renderer.with_primitives(|backend, primitives| {
|
||||||
backend.present(
|
backend.present(
|
||||||
&self.device,
|
&self.device,
|
||||||
&mut self.staging_belt,
|
&self.queue,
|
||||||
&mut encoder,
|
&mut encoder,
|
||||||
|
Some(background_color),
|
||||||
view,
|
view,
|
||||||
primitives,
|
primitives,
|
||||||
viewport,
|
viewport,
|
||||||
|
|
@ -237,13 +204,9 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Submit work
|
// Submit work
|
||||||
self.staging_belt.finish();
|
|
||||||
let _submission = self.queue.submit(Some(encoder.finish()));
|
let _submission = self.queue.submit(Some(encoder.finish()));
|
||||||
frame.present();
|
frame.present();
|
||||||
|
|
||||||
// Recall staging buffers
|
|
||||||
self.staging_belt.recall();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(error) => match error {
|
Err(error) => match error {
|
||||||
|
|
|
||||||
|
|
@ -851,6 +851,16 @@ pub fn run_command<A, E>(
|
||||||
current_cache = user_interface.into_cache();
|
current_cache = user_interface.into_cache();
|
||||||
*cache = current_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
|
/// [`Application`]: crate::Application
|
||||||
pub exit_on_close_request: bool,
|
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.
|
/// The window settings of an application.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue