mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-30 09:47:22 +00:00
Desktop: Render overlays with vello (#2965)
* Render overlays with vello * Fix nix flake comments * Rendering refactor with better names and code location * Remove unnecessary overlay renders * Post rebase fix
This commit is contained in:
parent
037bcb6b26
commit
34a8b9b6f1
14 changed files with 728 additions and 401 deletions
|
@ -4,11 +4,12 @@
|
||||||
#
|
#
|
||||||
# Development Environment:
|
# Development Environment:
|
||||||
# - Provides all necessary tools for Rust/Wasm development
|
# - Provides all necessary tools for Rust/Wasm development
|
||||||
|
# - Includes dependencies for desktop app development
|
||||||
# - Sets up profiling and debugging tools
|
# - Sets up profiling and debugging tools
|
||||||
# - Configures mold as the default linker for faster builds
|
# - Configures mold as the default linker for faster builds
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# - Development shell: `nix develop`
|
# - Development shell: `nix develop .nix` from the project root
|
||||||
# - Run in dev shell with direnv: add `use flake` to .envrc
|
# - Run in dev shell with direnv: add `use flake` to .envrc
|
||||||
{
|
{
|
||||||
description = "Development environment and build configuration";
|
description = "Development environment and build configuration";
|
||||||
|
|
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -1836,16 +1836,19 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cef",
|
"cef",
|
||||||
|
"derivative",
|
||||||
"dirs",
|
"dirs",
|
||||||
"futures",
|
"futures",
|
||||||
"glam",
|
"glam",
|
||||||
"graph-craft",
|
"graph-craft",
|
||||||
|
"graphene-std",
|
||||||
"graphite-editor",
|
"graphite-editor",
|
||||||
"include_dir",
|
"include_dir",
|
||||||
"ron",
|
"ron",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"vello",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"wgpu-executor",
|
"wgpu-executor",
|
||||||
"winit",
|
"winit",
|
||||||
|
@ -1880,6 +1883,7 @@ dependencies = [
|
||||||
"spin",
|
"spin",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tracing",
|
||||||
"usvg",
|
"usvg",
|
||||||
"vello",
|
"vello",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|
|
@ -19,6 +19,7 @@ graphite-editor = { path = "../editor", features = [
|
||||||
"ron",
|
"ron",
|
||||||
"vello",
|
"vello",
|
||||||
] }
|
] }
|
||||||
|
graphene-std = { workspace = true }
|
||||||
graph-craft = { workspace = true }
|
graph-craft = { workspace = true }
|
||||||
wgpu-executor = { workspace = true }
|
wgpu-executor = { workspace = true }
|
||||||
|
|
||||||
|
@ -34,3 +35,5 @@ dirs = { workspace = true }
|
||||||
ron = { workspace = true}
|
ron = { workspace = true}
|
||||||
bytemuck = { workspace = true }
|
bytemuck = { workspace = true }
|
||||||
glam = { workspace = true }
|
glam = { workspace = true }
|
||||||
|
vello = { workspace = true }
|
||||||
|
derivative = { workspace = true }
|
||||||
|
|
|
@ -48,7 +48,15 @@ impl WinitApp {
|
||||||
self.send_messages_to_editor(responses);
|
self.send_messages_to_editor(responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_messages_to_editor(&mut self, responses: Vec<FrontendMessage>) {
|
fn send_messages_to_editor(&mut self, mut responses: Vec<FrontendMessage>) {
|
||||||
|
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::RenderOverlays(_))) {
|
||||||
|
let FrontendMessage::RenderOverlays(overlay_context) = message else { unreachable!() };
|
||||||
|
if let Some(graphics_state) = &mut self.graphics_state {
|
||||||
|
let scene = overlay_context.take_scene();
|
||||||
|
graphics_state.set_overlays_scene(scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if responses.is_empty() {
|
if responses.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -110,8 +118,8 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||||
match event {
|
match event {
|
||||||
CustomEvent::UiUpdate(texture) => {
|
CustomEvent::UiUpdate(texture) => {
|
||||||
if let Some(graphics_state) = self.graphics_state.as_mut() {
|
if let Some(graphics_state) = self.graphics_state.as_mut() {
|
||||||
graphics_state.bind_ui_texture(&texture);
|
|
||||||
graphics_state.resize(texture.width(), texture.height());
|
graphics_state.resize(texture.width(), texture.height());
|
||||||
|
graphics_state.bind_ui_texture(texture);
|
||||||
}
|
}
|
||||||
if let Some(window) = &self.window {
|
if let Some(window) = &self.window {
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
|
@ -125,7 +133,7 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CustomEvent::MessageReceived { message } => {
|
CustomEvent::MessageReceived { message } => {
|
||||||
if let Message::InputPreprocessor(ipp_message) = &message {
|
if let Message::InputPreprocessor(_) = &message {
|
||||||
if let Some(window) = &self.window {
|
if let Some(window) = &self.window {
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
|
@ -144,13 +152,14 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||||
panic!("graphics state not intialized, viewport offset might be lost");
|
panic!("graphics state not intialized, viewport offset might be lost");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.dispatch_message(message);
|
self.dispatch_message(message);
|
||||||
}
|
}
|
||||||
CustomEvent::NodeGraphRan { texture } => {
|
CustomEvent::NodeGraphRan { texture } => {
|
||||||
if let Some(texture) = texture
|
if let Some(texture) = texture
|
||||||
&& let Some(graphics_state) = &mut self.graphics_state
|
&& let Some(graphics_state) = &mut self.graphics_state
|
||||||
{
|
{
|
||||||
graphics_state.bind_viewport_texture(&texture);
|
graphics_state.bind_viewport_texture(texture);
|
||||||
}
|
}
|
||||||
let mut responses = VecDeque::new();
|
let mut responses = VecDeque::new();
|
||||||
let err = self.editor.poll_node_graph_evaluation(&mut responses);
|
let err = self.editor.poll_node_graph_evaluation(&mut responses);
|
||||||
|
|
|
@ -1,310 +1,5 @@
|
||||||
use std::sync::Arc;
|
mod frame_buffer_ref;
|
||||||
|
pub(crate) use frame_buffer_ref::FrameBufferRef;
|
||||||
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
mod graphics_state;
|
||||||
use thiserror::Error;
|
pub(crate) use graphics_state::{GraphicsState, WgpuContext};
|
||||||
use winit::window::Window;
|
|
||||||
|
|
||||||
pub(crate) struct FrameBufferRef<'a> {
|
|
||||||
buffer: &'a [u8],
|
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
}
|
|
||||||
impl<'a> FrameBufferRef<'a> {
|
|
||||||
pub(crate) fn new(buffer: &'a [u8], width: usize, height: usize) -> Result<Self, FrameBufferError> {
|
|
||||||
let fb = Self { buffer, width, height };
|
|
||||||
fb.validate_size()?;
|
|
||||||
Ok(fb)
|
|
||||||
}
|
|
||||||
pub(crate) fn buffer(&self) -> &[u8] {
|
|
||||||
self.buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn width(&self) -> usize {
|
|
||||||
self.width
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn height(&self) -> usize {
|
|
||||||
self.height
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_size(&self) -> Result<(), FrameBufferError> {
|
|
||||||
if self.buffer.len() != self.width * self.height * 4 {
|
|
||||||
Err(FrameBufferError::InvalidSize {
|
|
||||||
buffer_size: self.buffer.len(),
|
|
||||||
expected_size: self.width * self.height * 4,
|
|
||||||
width: self.width,
|
|
||||||
height: self.height,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> std::fmt::Debug for FrameBufferRef<'a> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("FrameBuffer")
|
|
||||||
.field("width", &self.width)
|
|
||||||
.field("height", &self.height)
|
|
||||||
.field("len", &self.buffer.len())
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub(crate) enum FrameBufferError {
|
|
||||||
#[error("Invalid buffer size {buffer_size}, expected {expected_size} for width {width} multiplied with height {height} multiplied by 4 channels")]
|
|
||||||
InvalidSize { buffer_size: usize, expected_size: usize, width: usize, height: usize },
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use wgpu_executor::Context as WgpuContext;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct GraphicsState {
|
|
||||||
surface: wgpu::Surface<'static>,
|
|
||||||
context: WgpuContext,
|
|
||||||
config: wgpu::SurfaceConfiguration,
|
|
||||||
render_pipeline: wgpu::RenderPipeline,
|
|
||||||
sampler: wgpu::Sampler,
|
|
||||||
viewport_scale: [f32; 2],
|
|
||||||
viewport_offset: [f32; 2],
|
|
||||||
viewport_texture: Option<wgpu::Texture>,
|
|
||||||
ui_texture: Option<wgpu::Texture>,
|
|
||||||
bind_group: Option<wgpu::BindGroup>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GraphicsState {
|
|
||||||
pub(crate) fn new(window: Arc<Window>, context: WgpuContext) -> Self {
|
|
||||||
let size = window.inner_size();
|
|
||||||
|
|
||||||
let surface = context.instance.create_surface(window).unwrap();
|
|
||||||
|
|
||||||
let surface_caps = surface.get_capabilities(&context.adapter);
|
|
||||||
let surface_format = surface_caps.formats.iter().find(|f| f.is_srgb()).copied().unwrap_or(surface_caps.formats[0]);
|
|
||||||
|
|
||||||
let config = wgpu::SurfaceConfiguration {
|
|
||||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
|
||||||
format: surface_format,
|
|
||||||
width: size.width,
|
|
||||||
height: size.height,
|
|
||||||
present_mode: surface_caps.present_modes[0],
|
|
||||||
alpha_mode: surface_caps.alpha_modes[0],
|
|
||||||
view_formats: vec![],
|
|
||||||
desired_maximum_frame_latency: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
surface.configure(&context.device, &config);
|
|
||||||
|
|
||||||
// Create shader module
|
|
||||||
let shader = context.device.create_shader_module(wgpu::include_wgsl!("render/fullscreen_texture.wgsl"));
|
|
||||||
|
|
||||||
// Create sampler
|
|
||||||
let sampler = context.device.create_sampler(&wgpu::SamplerDescriptor {
|
|
||||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
|
||||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
|
||||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
|
||||||
mag_filter: wgpu::FilterMode::Linear,
|
|
||||||
min_filter: wgpu::FilterMode::Nearest,
|
|
||||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
let texture_bind_group_layout = context.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
|
||||||
entries: &[
|
|
||||||
wgpu::BindGroupLayoutEntry {
|
|
||||||
binding: 0,
|
|
||||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
||||||
ty: wgpu::BindingType::Texture {
|
|
||||||
multisampled: false,
|
|
||||||
view_dimension: wgpu::TextureViewDimension::D2,
|
|
||||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
|
||||||
},
|
|
||||||
count: None,
|
|
||||||
},
|
|
||||||
wgpu::BindGroupLayoutEntry {
|
|
||||||
binding: 1,
|
|
||||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
||||||
ty: wgpu::BindingType::Texture {
|
|
||||||
multisampled: false,
|
|
||||||
view_dimension: wgpu::TextureViewDimension::D2,
|
|
||||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
|
||||||
},
|
|
||||||
count: None,
|
|
||||||
},
|
|
||||||
wgpu::BindGroupLayoutEntry {
|
|
||||||
binding: 2,
|
|
||||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
||||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
|
||||||
count: None,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
label: Some("texture_bind_group_layout"),
|
|
||||||
});
|
|
||||||
|
|
||||||
let render_pipeline_layout = context.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
|
||||||
label: Some("Render Pipeline Layout"),
|
|
||||||
bind_group_layouts: &[&texture_bind_group_layout],
|
|
||||||
push_constant_ranges: &[wgpu::PushConstantRange {
|
|
||||||
stages: wgpu::ShaderStages::FRAGMENT,
|
|
||||||
range: 0..size_of::<Constants>() as u32,
|
|
||||||
}],
|
|
||||||
});
|
|
||||||
|
|
||||||
let render_pipeline = context.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
|
||||||
label: Some("Render Pipeline"),
|
|
||||||
layout: Some(&render_pipeline_layout),
|
|
||||||
vertex: wgpu::VertexState {
|
|
||||||
module: &shader,
|
|
||||||
entry_point: Some("vs_main"),
|
|
||||||
buffers: &[],
|
|
||||||
compilation_options: Default::default(),
|
|
||||||
},
|
|
||||||
fragment: Some(wgpu::FragmentState {
|
|
||||||
module: &shader,
|
|
||||||
entry_point: Some("fs_main"),
|
|
||||||
targets: &[Some(wgpu::ColorTargetState {
|
|
||||||
format: config.format,
|
|
||||||
blend: Some(wgpu::BlendState::REPLACE),
|
|
||||||
write_mask: wgpu::ColorWrites::ALL,
|
|
||||||
})],
|
|
||||||
compilation_options: Default::default(),
|
|
||||||
}),
|
|
||||||
primitive: wgpu::PrimitiveState {
|
|
||||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
|
||||||
strip_index_format: None,
|
|
||||||
front_face: wgpu::FrontFace::Ccw,
|
|
||||||
cull_mode: Some(wgpu::Face::Back),
|
|
||||||
polygon_mode: wgpu::PolygonMode::Fill,
|
|
||||||
unclipped_depth: false,
|
|
||||||
conservative: false,
|
|
||||||
},
|
|
||||||
depth_stencil: None,
|
|
||||||
multisample: wgpu::MultisampleState {
|
|
||||||
count: 1,
|
|
||||||
mask: !0,
|
|
||||||
alpha_to_coverage_enabled: false,
|
|
||||||
},
|
|
||||||
multiview: None,
|
|
||||||
cache: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
surface,
|
|
||||||
context,
|
|
||||||
config,
|
|
||||||
render_pipeline,
|
|
||||||
sampler,
|
|
||||||
viewport_scale: [1.0, 1.0],
|
|
||||||
viewport_offset: [0.0, 0.0],
|
|
||||||
viewport_texture: None,
|
|
||||||
ui_texture: None,
|
|
||||||
bind_group: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn resize(&mut self, width: u32, height: u32) {
|
|
||||||
if width > 0 && height > 0 && (self.config.width != width || self.config.height != height) {
|
|
||||||
self.config.width = width;
|
|
||||||
self.config.height = height;
|
|
||||||
self.surface.configure(&self.context.device, &self.config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn bind_ui_texture(&mut self, texture: &wgpu::Texture) {
|
|
||||||
let bind_group = self.create_bindgroup(texture, &self.viewport_texture.clone().unwrap_or(texture.clone()));
|
|
||||||
|
|
||||||
self.ui_texture = Some(texture.clone());
|
|
||||||
|
|
||||||
self.bind_group = Some(bind_group);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn bind_viewport_texture(&mut self, texture: &wgpu::Texture) {
|
|
||||||
let bind_group = self.create_bindgroup(&self.ui_texture.clone().unwrap_or(texture.clone()), texture);
|
|
||||||
|
|
||||||
self.viewport_texture = Some(texture.clone());
|
|
||||||
|
|
||||||
self.bind_group = Some(bind_group);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn set_viewport_scale(&mut self, scale: [f32; 2]) {
|
|
||||||
self.viewport_scale = scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn set_viewport_offset(&mut self, offset: [f32; 2]) {
|
|
||||||
self.viewport_offset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_bindgroup(&self, ui_texture: &wgpu::Texture, viewport_texture: &wgpu::Texture) -> wgpu::BindGroup {
|
|
||||||
let ui_texture_view = ui_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
|
||||||
let viewport_texture_view = viewport_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
|
||||||
|
|
||||||
self.context.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
||||||
layout: &self.render_pipeline.get_bind_group_layout(0),
|
|
||||||
entries: &[
|
|
||||||
wgpu::BindGroupEntry {
|
|
||||||
binding: 0,
|
|
||||||
resource: wgpu::BindingResource::TextureView(&ui_texture_view),
|
|
||||||
},
|
|
||||||
wgpu::BindGroupEntry {
|
|
||||||
binding: 1,
|
|
||||||
resource: wgpu::BindingResource::TextureView(&viewport_texture_view),
|
|
||||||
},
|
|
||||||
wgpu::BindGroupEntry {
|
|
||||||
binding: 2,
|
|
||||||
resource: wgpu::BindingResource::Sampler(&self.sampler),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
label: Some("texture_bind_group"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
|
|
||||||
let output = self.surface.get_current_texture()?;
|
|
||||||
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
|
|
||||||
|
|
||||||
let mut encoder = self.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder") });
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
||||||
label: Some("Render Pass"),
|
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
||||||
view: &view,
|
|
||||||
resolve_target: None,
|
|
||||||
ops: wgpu::Operations {
|
|
||||||
load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.01, g: 0.01, b: 0.01, a: 1.0 }),
|
|
||||||
store: wgpu::StoreOp::Store,
|
|
||||||
},
|
|
||||||
})],
|
|
||||||
depth_stencil_attachment: None,
|
|
||||||
occlusion_query_set: None,
|
|
||||||
timestamp_writes: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
render_pass.set_pipeline(&self.render_pipeline);
|
|
||||||
render_pass.set_push_constants(
|
|
||||||
wgpu::ShaderStages::FRAGMENT,
|
|
||||||
0,
|
|
||||||
bytemuck::bytes_of(&Constants {
|
|
||||||
viewport_scale: self.viewport_scale,
|
|
||||||
viewport_offset: self.viewport_offset,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if let Some(bind_group) = &self.bind_group {
|
|
||||||
render_pass.set_bind_group(0, bind_group, &[]);
|
|
||||||
render_pass.draw(0..6, 0..1); // Draw 3 vertices for fullscreen triangle
|
|
||||||
} else {
|
|
||||||
tracing::warn!("No bind group available - showing clear color only");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.context.queue.submit(std::iter::once(encoder.finish()));
|
|
||||||
output.present();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Copy, Clone, Pod, Zeroable)]
|
|
||||||
struct Constants {
|
|
||||||
viewport_scale: [f32; 2],
|
|
||||||
viewport_offset: [f32; 2],
|
|
||||||
}
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct Constants {
|
struct Constants {
|
||||||
viewport_scale: vec2<f32>,
|
viewport_scale: vec2<f32>,
|
||||||
viewport_offset: vec2<f32>,
|
viewport_offset: vec2<f32>,
|
||||||
|
@ -33,19 +34,36 @@ struct Constants {
|
||||||
var<push_constant> constants: Constants;
|
var<push_constant> constants: Constants;
|
||||||
|
|
||||||
@group(0) @binding(0)
|
@group(0) @binding(0)
|
||||||
var t_ui: texture_2d<f32>;
|
|
||||||
@group(0) @binding(1)
|
|
||||||
var t_viewport: texture_2d<f32>;
|
var t_viewport: texture_2d<f32>;
|
||||||
|
@group(0) @binding(1)
|
||||||
|
var t_overlays: texture_2d<f32>;
|
||||||
@group(0) @binding(2)
|
@group(0) @binding(2)
|
||||||
|
var t_ui: texture_2d<f32>;
|
||||||
|
@group(0) @binding(3)
|
||||||
var s_diffuse: sampler;
|
var s_diffuse: sampler;
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
let ui_color: vec4<f32> = textureSample(t_ui, s_diffuse, in.tex_coords);
|
let ui = textureSample(t_ui, s_diffuse, in.tex_coords);
|
||||||
if (ui_color.a == 1.0) {
|
if (ui.a >= 0.999) {
|
||||||
return ui_color;
|
return ui;
|
||||||
}
|
}
|
||||||
let viewport_tex_coords = (in.tex_coords - constants.viewport_offset) * constants.viewport_scale;
|
|
||||||
let viewport_color: vec4<f32> = textureSample(t_viewport, s_diffuse, viewport_tex_coords);
|
let vp_cord = (in.tex_coords - constants.viewport_offset) * constants.viewport_scale;
|
||||||
return ui_color * ui_color.a + viewport_color * (1.0 - ui_color.a);
|
|
||||||
|
let ov = textureSample(t_overlays, s_diffuse, vp_cord);
|
||||||
|
let vp = textureSample(t_viewport, s_diffuse, vp_cord);
|
||||||
|
|
||||||
|
if (ov.a < 0.001) {
|
||||||
|
return blend(ui, vp);
|
||||||
|
}
|
||||||
|
|
||||||
|
let comp = blend(ov, vp);
|
||||||
|
return blend(ui, comp);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blend(fg: vec4<f32>, bg: vec4<f32>) -> vec4<f32> {
|
||||||
|
let a = fg.a + bg.a * (1.0 - fg.a);
|
||||||
|
let rgb = fg.rgb * fg.a + bg.rgb * bg.a * (1.0 - fg.a);
|
||||||
|
return vec4<f32>(rgb, a);
|
||||||
}
|
}
|
53
desktop/src/render/frame_buffer_ref.rs
Normal file
53
desktop/src/render/frame_buffer_ref.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub(crate) struct FrameBufferRef<'a> {
|
||||||
|
buffer: &'a [u8],
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
}
|
||||||
|
impl<'a> FrameBufferRef<'a> {
|
||||||
|
pub(crate) fn new(buffer: &'a [u8], width: usize, height: usize) -> Result<Self, FrameBufferError> {
|
||||||
|
let fb = Self { buffer, width, height };
|
||||||
|
fb.validate_size()?;
|
||||||
|
Ok(fb)
|
||||||
|
}
|
||||||
|
pub(crate) fn buffer(&self) -> &[u8] {
|
||||||
|
self.buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn width(&self) -> usize {
|
||||||
|
self.width
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn height(&self) -> usize {
|
||||||
|
self.height
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_size(&self) -> Result<(), FrameBufferError> {
|
||||||
|
if self.buffer.len() != self.width * self.height * 4 {
|
||||||
|
Err(FrameBufferError::InvalidSize {
|
||||||
|
buffer_size: self.buffer.len(),
|
||||||
|
expected_size: self.width * self.height * 4,
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> std::fmt::Debug for FrameBufferRef<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("FrameBuffer")
|
||||||
|
.field("width", &self.width)
|
||||||
|
.field("height", &self.height)
|
||||||
|
.field("len", &self.buffer.len())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub(crate) enum FrameBufferError {
|
||||||
|
#[error("Invalid buffer size {buffer_size}, expected {expected_size} for width {width} multiplied with height {height} multiplied by 4 channels")]
|
||||||
|
InvalidSize { buffer_size: usize, expected_size: usize, width: usize, height: usize },
|
||||||
|
}
|
322
desktop/src/render/graphics_state.rs
Normal file
322
desktop/src/render/graphics_state.rs
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
use graphene_std::Color;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use wgpu_executor::WgpuExecutor;
|
||||||
|
use winit::window::Window;
|
||||||
|
|
||||||
|
pub(crate) use wgpu_executor::Context as WgpuContext;
|
||||||
|
|
||||||
|
#[derive(derivative::Derivative)]
|
||||||
|
#[derivative(Debug)]
|
||||||
|
pub(crate) struct GraphicsState {
|
||||||
|
surface: wgpu::Surface<'static>,
|
||||||
|
context: WgpuContext,
|
||||||
|
executor: WgpuExecutor,
|
||||||
|
config: wgpu::SurfaceConfiguration,
|
||||||
|
render_pipeline: wgpu::RenderPipeline,
|
||||||
|
transparent_texture: wgpu::Texture,
|
||||||
|
sampler: wgpu::Sampler,
|
||||||
|
viewport_scale: [f32; 2],
|
||||||
|
viewport_offset: [f32; 2],
|
||||||
|
viewport_texture: Option<wgpu::Texture>,
|
||||||
|
overlays_texture: Option<wgpu::Texture>,
|
||||||
|
ui_texture: Option<wgpu::Texture>,
|
||||||
|
bind_group: Option<wgpu::BindGroup>,
|
||||||
|
#[derivative(Debug = "ignore")]
|
||||||
|
overlays_scene: Option<vello::Scene>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphicsState {
|
||||||
|
pub(crate) fn new(window: Arc<Window>, context: WgpuContext) -> Self {
|
||||||
|
let size = window.inner_size();
|
||||||
|
|
||||||
|
let surface = context.instance.create_surface(window).unwrap();
|
||||||
|
|
||||||
|
let surface_caps = surface.get_capabilities(&context.adapter);
|
||||||
|
let surface_format = surface_caps.formats.iter().find(|f| f.is_srgb()).copied().unwrap_or(surface_caps.formats[0]);
|
||||||
|
|
||||||
|
let config = wgpu::SurfaceConfiguration {
|
||||||
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
format: surface_format,
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
present_mode: surface_caps.present_modes[0],
|
||||||
|
alpha_mode: surface_caps.alpha_modes[0],
|
||||||
|
view_formats: vec![],
|
||||||
|
desired_maximum_frame_latency: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
surface.configure(&context.device, &config);
|
||||||
|
|
||||||
|
let transparent_texture = context.device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("Transparent Texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||||
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
view_formats: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create shader module
|
||||||
|
let shader = context.device.create_shader_module(wgpu::include_wgsl!("composite_shader.wgsl"));
|
||||||
|
|
||||||
|
// Create sampler
|
||||||
|
let sampler = context.device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
|
mag_filter: wgpu::FilterMode::Linear,
|
||||||
|
min_filter: wgpu::FilterMode::Nearest,
|
||||||
|
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let texture_bind_group_layout = context.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
multisampled: false,
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
multisampled: false,
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
multisampled: false,
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 3,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: Some("texture_bind_group_layout"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let render_pipeline_layout = context.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("Render Pipeline Layout"),
|
||||||
|
bind_group_layouts: &[&texture_bind_group_layout],
|
||||||
|
push_constant_ranges: &[wgpu::PushConstantRange {
|
||||||
|
stages: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
range: 0..size_of::<Constants>() as u32,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
let render_pipeline = context.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("Render Pipeline"),
|
||||||
|
layout: Some(&render_pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("vs_main"),
|
||||||
|
buffers: &[],
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("fs_main"),
|
||||||
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
|
format: config.format,
|
||||||
|
blend: Some(wgpu::BlendState::REPLACE),
|
||||||
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||||
|
strip_index_format: None,
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
cull_mode: Some(wgpu::Face::Back),
|
||||||
|
polygon_mode: wgpu::PolygonMode::Fill,
|
||||||
|
unclipped_depth: false,
|
||||||
|
conservative: false,
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState {
|
||||||
|
count: 1,
|
||||||
|
mask: !0,
|
||||||
|
alpha_to_coverage_enabled: false,
|
||||||
|
},
|
||||||
|
multiview: None,
|
||||||
|
cache: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let wgpu_executor = WgpuExecutor::with_context(context.clone()).expect("Failed to create WgpuExecutor");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
surface,
|
||||||
|
context,
|
||||||
|
executor: wgpu_executor,
|
||||||
|
config,
|
||||||
|
render_pipeline,
|
||||||
|
transparent_texture,
|
||||||
|
sampler,
|
||||||
|
viewport_scale: [1.0, 1.0],
|
||||||
|
viewport_offset: [0.0, 0.0],
|
||||||
|
viewport_texture: None,
|
||||||
|
overlays_texture: None,
|
||||||
|
ui_texture: None,
|
||||||
|
bind_group: None,
|
||||||
|
overlays_scene: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn resize(&mut self, width: u32, height: u32) {
|
||||||
|
if width > 0 && height > 0 && (self.config.width != width || self.config.height != height) {
|
||||||
|
self.config.width = width;
|
||||||
|
self.config.height = height;
|
||||||
|
self.surface.configure(&self.context.device, &self.config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn bind_viewport_texture(&mut self, viewport_texture: wgpu::Texture) {
|
||||||
|
self.viewport_texture = Some(viewport_texture);
|
||||||
|
self.update_bindgroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn bind_overlays_texture(&mut self, overlays_texture: wgpu::Texture) {
|
||||||
|
self.overlays_texture = Some(overlays_texture);
|
||||||
|
self.update_bindgroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn bind_ui_texture(&mut self, bind_ui_texture: wgpu::Texture) {
|
||||||
|
self.ui_texture = Some(bind_ui_texture);
|
||||||
|
self.update_bindgroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_viewport_scale(&mut self, scale: [f32; 2]) {
|
||||||
|
self.viewport_scale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_viewport_offset(&mut self, offset: [f32; 2]) {
|
||||||
|
self.viewport_offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_overlays_scene(&mut self, scene: vello::Scene) {
|
||||||
|
self.overlays_scene = Some(scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_overlays(&mut self, scene: vello::Scene) {
|
||||||
|
let Some(viewport_texture) = self.viewport_texture.as_ref() else {
|
||||||
|
tracing::warn!("No viewport texture bound, cannot render overlays");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let size = glam::UVec2::new(viewport_texture.width(), viewport_texture.height());
|
||||||
|
let texture = futures::executor::block_on(self.executor.render_vello_scene_to_texture(&scene, size, &Default::default(), Color::TRANSPARENT));
|
||||||
|
let Ok(texture) = texture else {
|
||||||
|
tracing::error!("Error rendering overlays");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.bind_overlays_texture(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
|
||||||
|
if let Some(scene) = self.overlays_scene.take() {
|
||||||
|
self.render_overlays(scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = self.surface.get_current_texture()?;
|
||||||
|
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
let mut encoder = self.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder") });
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("Render Pass"),
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: &view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.01, g: 0.01, b: 0.01, a: 1.0 }),
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
render_pass.set_pipeline(&self.render_pipeline);
|
||||||
|
render_pass.set_push_constants(
|
||||||
|
wgpu::ShaderStages::FRAGMENT,
|
||||||
|
0,
|
||||||
|
bytemuck::bytes_of(&Constants {
|
||||||
|
viewport_scale: self.viewport_scale,
|
||||||
|
viewport_offset: self.viewport_offset,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if let Some(bind_group) = &self.bind_group {
|
||||||
|
render_pass.set_bind_group(0, bind_group, &[]);
|
||||||
|
render_pass.draw(0..6, 0..1); // Draw 3 vertices for fullscreen triangle
|
||||||
|
} else {
|
||||||
|
tracing::warn!("No bind group available - showing clear color only");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.context.queue.submit(std::iter::once(encoder.finish()));
|
||||||
|
output.present();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_bindgroup(&mut self) {
|
||||||
|
let viewport_texture_view = self.viewport_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
let overlays_texture_view = self.overlays_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
let ui_texture_view = self.ui_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
let bind_group = self.context.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &self.render_pipeline.get_bind_group_layout(0),
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&viewport_texture_view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&overlays_texture_view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&ui_texture_view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 3,
|
||||||
|
resource: wgpu::BindingResource::Sampler(&self.sampler),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: Some("texture_bind_group"),
|
||||||
|
});
|
||||||
|
|
||||||
|
self.bind_group = Some(bind_group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
struct Constants {
|
||||||
|
viewport_scale: [f32; 2],
|
||||||
|
viewport_offset: [f32; 2],
|
||||||
|
}
|
|
@ -46,6 +46,7 @@ once_cell = { workspace = true }
|
||||||
web-sys = { workspace = true }
|
web-sys = { workspace = true }
|
||||||
bytemuck = { workspace = true }
|
bytemuck = { workspace = true }
|
||||||
vello = { workspace = true }
|
vello = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
|
||||||
# Required dependencies
|
# Required dependencies
|
||||||
spin = "0.9.8"
|
spin = "0.9.8"
|
||||||
|
|
|
@ -13,8 +13,12 @@ use graphene_std::raster::Image;
|
||||||
use graphene_std::raster::color::Color;
|
use graphene_std::raster::color::Color;
|
||||||
use graphene_std::text::{Font, TextAlign};
|
use graphene_std::text::{Font, TextAlign};
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
|
|
||||||
#[impl_message(Message, Frontend)]
|
#[impl_message(Message, Frontend)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(derivative::Derivative, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
#[derivative(Debug, PartialEq)]
|
||||||
pub enum FrontendMessage {
|
pub enum FrontendMessage {
|
||||||
// Display prefix: make the frontend show something, like a dialog
|
// Display prefix: make the frontend show something, like a dialog
|
||||||
DisplayDialog {
|
DisplayDialog {
|
||||||
|
@ -318,4 +322,10 @@ pub enum FrontendMessage {
|
||||||
UpdateViewportHolePunch {
|
UpdateViewportHolePunch {
|
||||||
active: bool,
|
active: bool,
|
||||||
},
|
},
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
RenderOverlays(
|
||||||
|
#[serde(skip, default = "OverlayContext::default")]
|
||||||
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
|
OverlayContext,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,8 @@ pub mod grid_overlays;
|
||||||
mod overlays_message;
|
mod overlays_message;
|
||||||
mod overlays_message_handler;
|
mod overlays_message_handler;
|
||||||
pub mod utility_functions;
|
pub mod utility_functions;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg_attr(not(target_arch = "wasm32"), path = "utility_types_vello.rs")]
|
||||||
pub mod utility_types;
|
pub mod utility_types;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
pub mod utility_types_vello;
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
pub use utility_types_vello as utility_types;
|
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant};
|
pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant};
|
||||||
|
|
|
@ -73,34 +73,18 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
|
||||||
#[cfg(all(not(target_arch = "wasm32"), not(test)))]
|
#[cfg(all(not(target_arch = "wasm32"), not(test)))]
|
||||||
OverlaysMessage::Draw => {
|
OverlaysMessage::Draw => {
|
||||||
use super::utility_types::OverlayContext;
|
use super::utility_types::OverlayContext;
|
||||||
use vello::Scene;
|
let size = ipp.viewport_bounds.size();
|
||||||
|
|
||||||
let size = ipp.viewport_bounds.size().as_uvec2();
|
let overlay_context = OverlayContext::new(size, device_pixel_ratio, visibility_settings);
|
||||||
|
|
||||||
let scene = Scene::new();
|
|
||||||
|
|
||||||
if visibility_settings.all() {
|
if visibility_settings.all() {
|
||||||
let overlay_context = OverlayContext {
|
|
||||||
scene,
|
|
||||||
size: size.as_dvec2(),
|
|
||||||
device_pixel_ratio,
|
|
||||||
visibility_settings,
|
|
||||||
};
|
|
||||||
|
|
||||||
responses.add(DocumentMessage::GridOverlays(overlay_context.clone()));
|
responses.add(DocumentMessage::GridOverlays(overlay_context.clone()));
|
||||||
|
|
||||||
for provider in &self.overlay_providers {
|
for provider in &self.overlay_providers {
|
||||||
let overlay_context = OverlayContext {
|
responses.add(provider(overlay_context.clone()));
|
||||||
scene: Scene::new(),
|
|
||||||
size: size.as_dvec2(),
|
|
||||||
device_pixel_ratio,
|
|
||||||
visibility_settings,
|
|
||||||
};
|
|
||||||
responses.add(provider(overlay_context));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
responses.add(FrontendMessage::RenderOverlays(overlay_context));
|
||||||
// TODO: Render the Vello scene to a texture and display it
|
|
||||||
}
|
}
|
||||||
OverlaysMessage::AddProvider(message) => {
|
OverlaysMessage::AddProvider(message) => {
|
||||||
self.overlay_providers.insert(message);
|
self.overlay_providers.insert(message);
|
||||||
|
|
|
@ -13,6 +13,7 @@ use graphene_std::math::quad::Quad;
|
||||||
use graphene_std::vector::click_target::ClickTargetType;
|
use graphene_std::vector::click_target::ClickTargetType;
|
||||||
use graphene_std::vector::{PointId, SegmentId, VectorData};
|
use graphene_std::vector::{PointId, SegmentId, VectorData};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
use vello::Scene;
|
use vello::Scene;
|
||||||
use vello::kurbo::{self, BezPath};
|
use vello::kurbo::{self, BezPath};
|
||||||
use vello::peniko;
|
use vello::peniko;
|
||||||
|
@ -132,12 +133,12 @@ impl OverlaysVisibilitySettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
pub struct OverlayContext {
|
pub struct OverlayContext {
|
||||||
// Serde functionality isn't used but is required by the message system macros
|
// Serde functionality isn't used but is required by the message system macros
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
#[specta(skip)]
|
#[specta(skip)]
|
||||||
pub scene: Scene,
|
internal: Arc<Mutex<OverlayContextInternal>>,
|
||||||
pub size: DVec2,
|
pub size: DVec2,
|
||||||
// The device pixel ratio is a property provided by the browser window and is the CSS pixel size divided by the physical monitor's pixel size.
|
// The device pixel ratio is a property provided by the browser window and is the CSS pixel size divided by the physical monitor's pixel size.
|
||||||
// It allows better pixel density of visualizations on high-DPI displays where the OS display scaling is not 100%, or where the browser is zoomed.
|
// It allows better pixel density of visualizations on high-DPI displays where the OS display scaling is not 100%, or where the browser is zoomed.
|
||||||
|
@ -145,6 +146,22 @@ pub struct OverlayContext {
|
||||||
pub visibility_settings: OverlaysVisibilitySettings,
|
pub visibility_settings: OverlaysVisibilitySettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Clone for OverlayContext {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
let internal = self.internal.lock().expect("Failed to lock internal overlay context");
|
||||||
|
let size = internal.size;
|
||||||
|
let device_pixel_ratio = internal.device_pixel_ratio;
|
||||||
|
let visibility_settings = internal.visibility_settings;
|
||||||
|
drop(internal); // Explicitly release the lock before cloning the Arc<Mutex<_>>
|
||||||
|
Self {
|
||||||
|
internal: self.internal.clone(),
|
||||||
|
size,
|
||||||
|
device_pixel_ratio,
|
||||||
|
visibility_settings,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Manual implementations since Scene doesn't implement PartialEq or Debug
|
// Manual implementations since Scene doesn't implement PartialEq or Debug
|
||||||
impl PartialEq for OverlayContext {
|
impl PartialEq for OverlayContext {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
@ -167,7 +184,7 @@ impl std::fmt::Debug for OverlayContext {
|
||||||
impl Default for OverlayContext {
|
impl Default for OverlayContext {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
scene: Scene::new(),
|
internal: Mutex::new(OverlayContextInternal::default()).into(),
|
||||||
size: DVec2::ZERO,
|
size: DVec2::ZERO,
|
||||||
device_pixel_ratio: 1.0,
|
device_pixel_ratio: 1.0,
|
||||||
visibility_settings: OverlaysVisibilitySettings::default(),
|
visibility_settings: OverlaysVisibilitySettings::default(),
|
||||||
|
@ -181,6 +198,235 @@ impl core::hash::Hash for OverlayContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OverlayContext {
|
impl OverlayContext {
|
||||||
|
pub(super) fn new(size: DVec2, device_pixel_ratio: f64, visibility_settings: OverlaysVisibilitySettings) -> Self {
|
||||||
|
Self {
|
||||||
|
internal: Arc::new(Mutex::new(OverlayContextInternal::new(size, device_pixel_ratio, visibility_settings))),
|
||||||
|
size,
|
||||||
|
device_pixel_ratio,
|
||||||
|
visibility_settings,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_scene(self) -> Scene {
|
||||||
|
let mut internal = self.internal.lock().expect("Failed to lock internal overlay context");
|
||||||
|
std::mem::take(&mut *internal).scene
|
||||||
|
}
|
||||||
|
|
||||||
|
fn internal(&'_ self) -> MutexGuard<'_, OverlayContextInternal> {
|
||||||
|
self.internal.lock().expect("Failed to lock internal overlay context")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>) {
|
||||||
|
self.internal().quad(quad, stroke_color, color_fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_triangle(&mut self, base: DVec2, direction: DVec2, size: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||||
|
self.internal().draw_triangle(base, direction, size, color_fill, color_stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dashed_quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||||
|
self.internal().dashed_quad(quad, stroke_color, color_fill, dash_width, dash_gap_width, dash_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>) {
|
||||||
|
self.internal().polygon(polygon, stroke_color, color_fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dashed_polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||||
|
self.internal().dashed_polygon(polygon, stroke_color, color_fill, dash_width, dash_gap_width, dash_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>) {
|
||||||
|
self.internal().line(start, end, color, thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||||
|
self.internal().dashed_line(start, end, color, thickness, dash_width, dash_gap_width, dash_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
|
||||||
|
self.internal().hover_manipulator_handle(position, selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hover_manipulator_anchor(&mut self, position: DVec2, selected: bool) {
|
||||||
|
self.internal().hover_manipulator_anchor(position, selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||||
|
self.internal().manipulator_handle(position, selected, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn manipulator_anchor(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||||
|
self.internal().manipulator_anchor(position, selected, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn square(&mut self, position: DVec2, size: Option<f64>, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||||
|
self.internal().square(position, size, color_fill, color_stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pixel(&mut self, position: DVec2, color: Option<&str>) {
|
||||||
|
self.internal().pixel(position, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||||
|
self.internal().circle(position, radius, color_fill, color_stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dashed_ellipse(
|
||||||
|
&mut self,
|
||||||
|
center: DVec2,
|
||||||
|
radius_x: f64,
|
||||||
|
radius_y: f64,
|
||||||
|
rotation: Option<f64>,
|
||||||
|
start_angle: Option<f64>,
|
||||||
|
end_angle: Option<f64>,
|
||||||
|
counterclockwise: Option<bool>,
|
||||||
|
color_fill: Option<&str>,
|
||||||
|
color_stroke: Option<&str>,
|
||||||
|
dash_width: Option<f64>,
|
||||||
|
dash_gap_width: Option<f64>,
|
||||||
|
dash_offset: Option<f64>,
|
||||||
|
) {
|
||||||
|
self.internal().dashed_ellipse(
|
||||||
|
center,
|
||||||
|
radius_x,
|
||||||
|
radius_y,
|
||||||
|
rotation,
|
||||||
|
start_angle,
|
||||||
|
end_angle,
|
||||||
|
counterclockwise,
|
||||||
|
color_fill,
|
||||||
|
color_stroke,
|
||||||
|
dash_width,
|
||||||
|
dash_gap_width,
|
||||||
|
dash_offset,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) {
|
||||||
|
self.internal().draw_arc(center, radius, start_from, end_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||||
|
self.internal().draw_arc_gizmo_angle(pivot, bold_radius, arc_radius, offset_angle, angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||||
|
self.internal().draw_angle(pivot, radius, arc_radius, offset_angle, angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
|
||||||
|
self.internal().draw_scale(start, scale, radius, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compass_rose(&mut self, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option<bool>) {
|
||||||
|
self.internal().compass_rose(compass_center, angle, show_compass_with_hover_ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pivot(&mut self, position: DVec2, angle: f64) {
|
||||||
|
self.internal().pivot(position, angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {
|
||||||
|
self.internal().dowel_pin(position, angle, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
|
||||||
|
self.internal().arc_sweep_angle(offset_angle, angle, end_point_position, bold_radius, pivot, text, transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used by the Pen and Path tools to outline the path of the shape.
|
||||||
|
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
|
||||||
|
self.internal().outline_vector(vector_data, transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||||
|
pub fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||||
|
self.internal().outline_bezier(bezier, transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used by the path tool segment mode in order to show the selected segments.
|
||||||
|
pub fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||||
|
self.internal().outline_select_bezier(bezier, transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||||
|
self.internal().outline_overlay_bezier(bezier, transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used by the Select tool to outline a path or a free point when selected or hovered.
|
||||||
|
pub fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
||||||
|
self.internal().outline(target_types, transform, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fills the area inside the path. Assumes `color` is in gamma space.
|
||||||
|
/// Used by the Pen tool to show the path being closed.
|
||||||
|
pub fn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {
|
||||||
|
self.internal().fill_path(subpaths, transform, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fills the area inside the path with a pattern. Assumes `color` is in gamma space.
|
||||||
|
/// Used by the fill tool to show the area to be filled.
|
||||||
|
pub fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &Color) {
|
||||||
|
self.internal().fill_path_pattern(subpaths, transform, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_width(&self, text: &str) -> f64 {
|
||||||
|
self.internal().get_width(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text(&self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {
|
||||||
|
self.internal().text(text, font_color, background_color, transform, padding, pivot);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option<String>) {
|
||||||
|
self.internal().translation_box(translation, quad, typed_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Pivot {
|
||||||
|
Start,
|
||||||
|
Middle,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DrawHandles {
|
||||||
|
All,
|
||||||
|
SelectedAnchors(Vec<SegmentId>),
|
||||||
|
FrontierHandles(HashMap<SegmentId, Vec<PointId>>),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct OverlayContextInternal {
|
||||||
|
scene: Scene,
|
||||||
|
size: DVec2,
|
||||||
|
device_pixel_ratio: f64,
|
||||||
|
visibility_settings: OverlaysVisibilitySettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for OverlayContextInternal {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
scene: Scene::new(),
|
||||||
|
size: DVec2::ZERO,
|
||||||
|
device_pixel_ratio: 1.0,
|
||||||
|
visibility_settings: OverlaysVisibilitySettings::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OverlayContextInternal {
|
||||||
|
pub(super) fn new(size: DVec2, device_pixel_ratio: f64, visibility_settings: OverlaysVisibilitySettings) -> Self {
|
||||||
|
Self {
|
||||||
|
scene: Scene::new(),
|
||||||
|
size,
|
||||||
|
device_pixel_ratio,
|
||||||
|
visibility_settings,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_color(color: &str) -> peniko::Color {
|
fn parse_color(color: &str) -> peniko::Color {
|
||||||
let hex = color.trim_start_matches('#');
|
let hex = color.trim_start_matches('#');
|
||||||
let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
|
let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
|
||||||
|
@ -190,11 +436,11 @@ impl OverlayContext {
|
||||||
peniko::Color::from_rgba8(r, g, b, a)
|
peniko::Color::from_rgba8(r, g, b, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>) {
|
fn quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>) {
|
||||||
self.dashed_polygon(&quad.0, stroke_color, color_fill, None, None, None);
|
self.dashed_polygon(&quad.0, stroke_color, color_fill, None, None, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_triangle(&mut self, base: DVec2, direction: DVec2, size: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
fn draw_triangle(&mut self, base: DVec2, direction: DVec2, size: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||||
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||||
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||||
let normal = direction.perp();
|
let normal = direction.perp();
|
||||||
|
@ -215,15 +461,15 @@ impl OverlayContext {
|
||||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &path);
|
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &path);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dashed_quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
fn dashed_quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||||
self.dashed_polygon(&quad.0, stroke_color, color_fill, dash_width, dash_gap_width, dash_offset);
|
self.dashed_polygon(&quad.0, stroke_color, color_fill, dash_width, dash_gap_width, dash_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>) {
|
fn polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>) {
|
||||||
self.dashed_polygon(polygon, stroke_color, color_fill, None, None, None);
|
self.dashed_polygon(polygon, stroke_color, color_fill, None, None, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dashed_polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
fn dashed_polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||||
if polygon.len() < 2 {
|
if polygon.len() < 2 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -255,12 +501,12 @@ impl OverlayContext {
|
||||||
self.scene.stroke(&stroke, transform, Self::parse_color(stroke_color), None, &path);
|
self.scene.stroke(&stroke, transform, Self::parse_color(stroke_color), None, &path);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>) {
|
fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>) {
|
||||||
self.dashed_line(start, end, color, thickness, None, None, None)
|
self.dashed_line(start, end, color, thickness, None, None, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
fn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||||
let transform = self.get_transform();
|
let transform = self.get_transform();
|
||||||
|
|
||||||
let start = start.round() - DVec2::splat(0.5);
|
let start = start.round() - DVec2::splat(0.5);
|
||||||
|
@ -280,7 +526,7 @@ impl OverlayContext {
|
||||||
self.scene.stroke(&stroke, transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &path);
|
self.scene.stroke(&stroke, transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &path);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
fn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||||
let transform = self.get_transform();
|
let transform = self.get_transform();
|
||||||
let position = position.round() - DVec2::splat(0.5);
|
let position = position.round() - DVec2::splat(0.5);
|
||||||
|
|
||||||
|
@ -293,7 +539,7 @@ impl OverlayContext {
|
||||||
.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &circle);
|
.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &circle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
|
fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
|
||||||
let transform = self.get_transform();
|
let transform = self.get_transform();
|
||||||
|
|
||||||
let position = position.round() - DVec2::splat(0.5);
|
let position = position.round() - DVec2::splat(0.5);
|
||||||
|
@ -311,13 +557,13 @@ impl OverlayContext {
|
||||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &inner_circle);
|
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &inner_circle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn manipulator_anchor(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
fn manipulator_anchor(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||||
let color_stroke = color.unwrap_or(COLOR_OVERLAY_BLUE);
|
let color_stroke = color.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||||
let color_fill = if selected { color_stroke } else { COLOR_OVERLAY_WHITE };
|
let color_fill = if selected { color_stroke } else { COLOR_OVERLAY_WHITE };
|
||||||
self.square(position, None, Some(color_fill), Some(color_stroke));
|
self.square(position, None, Some(color_fill), Some(color_stroke));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hover_manipulator_anchor(&mut self, position: DVec2, selected: bool) {
|
fn hover_manipulator_anchor(&mut self, position: DVec2, selected: bool) {
|
||||||
self.square(position, Some(MANIPULATOR_GROUP_MARKER_SIZE + 2.), Some(COLOR_OVERLAY_BLUE_50), Some(COLOR_OVERLAY_BLUE_50));
|
self.square(position, Some(MANIPULATOR_GROUP_MARKER_SIZE + 2.), Some(COLOR_OVERLAY_BLUE_50), Some(COLOR_OVERLAY_BLUE_50));
|
||||||
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
|
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
|
||||||
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
|
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
|
||||||
|
@ -327,7 +573,7 @@ impl OverlayContext {
|
||||||
kurbo::Affine::scale(self.device_pixel_ratio)
|
kurbo::Affine::scale(self.device_pixel_ratio)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn square(&mut self, position: DVec2, size: Option<f64>, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
fn square(&mut self, position: DVec2, size: Option<f64>, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||||
let size = size.unwrap_or(MANIPULATOR_GROUP_MARKER_SIZE);
|
let size = size.unwrap_or(MANIPULATOR_GROUP_MARKER_SIZE);
|
||||||
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||||
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||||
|
@ -343,7 +589,7 @@ impl OverlayContext {
|
||||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &rect);
|
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pixel(&mut self, position: DVec2, color: Option<&str>) {
|
fn pixel(&mut self, position: DVec2, color: Option<&str>) {
|
||||||
let size = 1.;
|
let size = 1.;
|
||||||
let color_fill = color.unwrap_or(COLOR_OVERLAY_WHITE);
|
let color_fill = color.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||||
|
|
||||||
|
@ -356,7 +602,7 @@ impl OverlayContext {
|
||||||
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &rect);
|
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||||
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||||
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||||
let position = position.round();
|
let position = position.round();
|
||||||
|
@ -369,7 +615,7 @@ impl OverlayContext {
|
||||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &circle);
|
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &circle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dashed_ellipse(
|
fn dashed_ellipse(
|
||||||
&mut self,
|
&mut self,
|
||||||
_center: DVec2,
|
_center: DVec2,
|
||||||
_radius_x: f64,
|
_radius_x: f64,
|
||||||
|
@ -386,7 +632,7 @@ impl OverlayContext {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) {
|
fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) {
|
||||||
let segments = ((end_at - start_from).abs() / (std::f64::consts::PI / 4.)).ceil() as usize;
|
let segments = ((end_at - start_from).abs() / (std::f64::consts::PI / 4.)).ceil() as usize;
|
||||||
let step = (end_at - start_from) / segments as f64;
|
let step = (end_at - start_from) / segments as f64;
|
||||||
let half_step = step / 2.;
|
let half_step = step / 2.;
|
||||||
|
@ -420,13 +666,13 @@ impl OverlayContext {
|
||||||
self.scene.stroke(&kurbo::Stroke::new(1.0), self.get_transform(), Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
self.scene.stroke(&kurbo::Stroke::new(1.0), self.get_transform(), Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||||
let end_point1 = pivot + bold_radius * DVec2::from_angle(angle + offset_angle);
|
let end_point1 = pivot + bold_radius * DVec2::from_angle(angle + offset_angle);
|
||||||
self.line(pivot, end_point1, None, None);
|
self.line(pivot, end_point1, None, None);
|
||||||
self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);
|
self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||||
let end_point1 = pivot + radius * DVec2::from_angle(angle + offset_angle);
|
let end_point1 = pivot + radius * DVec2::from_angle(angle + offset_angle);
|
||||||
let end_point2 = pivot + radius * DVec2::from_angle(offset_angle);
|
let end_point2 = pivot + radius * DVec2::from_angle(offset_angle);
|
||||||
self.line(pivot, end_point1, None, None);
|
self.line(pivot, end_point1, None, None);
|
||||||
|
@ -434,7 +680,7 @@ impl OverlayContext {
|
||||||
self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);
|
self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
|
fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
|
||||||
let sign = scale.signum();
|
let sign = scale.signum();
|
||||||
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
|
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
|
||||||
fill_color.insert(0, '#');
|
fill_color.insert(0, '#');
|
||||||
|
@ -452,7 +698,7 @@ impl OverlayContext {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compass_rose(&mut self, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option<bool>) {
|
fn compass_rose(&mut self, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option<bool>) {
|
||||||
const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.;
|
const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.;
|
||||||
const MAIN_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.;
|
const MAIN_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.;
|
||||||
const MAIN_RING_INNER_RADIUS: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.;
|
const MAIN_RING_INNER_RADIUS: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.;
|
||||||
|
@ -508,7 +754,7 @@ impl OverlayContext {
|
||||||
.stroke(&kurbo::Stroke::new(MAIN_RING_STROKE_WIDTH), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &circle);
|
.stroke(&kurbo::Stroke::new(MAIN_RING_STROKE_WIDTH), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &circle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pivot(&mut self, position: DVec2, angle: f64) {
|
fn pivot(&mut self, position: DVec2, angle: f64) {
|
||||||
let uv = DVec2::from_angle(angle);
|
let uv = DVec2::from_angle(angle);
|
||||||
let (x, y) = (position.round() - DVec2::splat(0.5)).into();
|
let (x, y) = (position.round() - DVec2::splat(0.5)).into();
|
||||||
|
|
||||||
|
@ -539,7 +785,7 @@ impl OverlayContext {
|
||||||
self.scene.stroke(&stroke, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &path);
|
self.scene.stroke(&stroke, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &path);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {
|
fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {
|
||||||
let (x, y) = (position.round() - DVec2::splat(0.5)).into();
|
let (x, y) = (position.round() - DVec2::splat(0.5)).into();
|
||||||
let color = color.unwrap_or(COLOR_OVERLAY_YELLOW_DULL);
|
let color = color.unwrap_or(COLOR_OVERLAY_YELLOW_DULL);
|
||||||
|
|
||||||
|
@ -581,14 +827,14 @@ impl OverlayContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
|
fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
|
||||||
self.manipulator_handle(end_point_position, true, None);
|
self.manipulator_handle(end_point_position, true, None);
|
||||||
self.draw_arc_gizmo_angle(pivot, bold_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
|
self.draw_arc_gizmo_angle(pivot, bold_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
|
||||||
self.text(text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
self.text(text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used by the Pen and Path tools to outline the path of the shape.
|
/// Used by the Pen and Path tools to outline the path of the shape.
|
||||||
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
|
fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
|
||||||
let vello_transform = self.get_transform();
|
let vello_transform = self.get_transform();
|
||||||
let mut path = BezPath::new();
|
let mut path = BezPath::new();
|
||||||
|
|
||||||
|
@ -604,7 +850,7 @@ impl OverlayContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||||
pub fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||||
let vello_transform = self.get_transform();
|
let vello_transform = self.get_transform();
|
||||||
let mut path = BezPath::new();
|
let mut path = BezPath::new();
|
||||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||||
|
@ -613,7 +859,7 @@ impl OverlayContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used by the path tool segment mode in order to show the selected segments.
|
/// Used by the path tool segment mode in order to show the selected segments.
|
||||||
pub fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||||
let vello_transform = self.get_transform();
|
let vello_transform = self.get_transform();
|
||||||
let mut path = BezPath::new();
|
let mut path = BezPath::new();
|
||||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||||
|
@ -621,7 +867,7 @@ impl OverlayContext {
|
||||||
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||||
let vello_transform = self.get_transform();
|
let vello_transform = self.get_transform();
|
||||||
let mut path = BezPath::new();
|
let mut path = BezPath::new();
|
||||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||||
|
@ -695,7 +941,7 @@ impl OverlayContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used by the Select tool to outline a path or a free point when selected or hovered.
|
/// Used by the Select tool to outline a path or a free point when selected or hovered.
|
||||||
pub fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
||||||
let mut subpaths: Vec<bezier_rs::Subpath<PointId>> = vec![];
|
let mut subpaths: Vec<bezier_rs::Subpath<PointId>> = vec![];
|
||||||
|
|
||||||
for target_type in target_types {
|
for target_type in target_types {
|
||||||
|
@ -717,7 +963,7 @@ impl OverlayContext {
|
||||||
|
|
||||||
/// Fills the area inside the path. Assumes `color` is in gamma space.
|
/// Fills the area inside the path. Assumes `color` is in gamma space.
|
||||||
/// Used by the Pen tool to show the path being closed.
|
/// Used by the Pen tool to show the path being closed.
|
||||||
pub fn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {
|
fn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {
|
||||||
let path = self.push_path(subpaths, transform);
|
let path = self.push_path(subpaths, transform);
|
||||||
|
|
||||||
self.scene.fill(peniko::Fill::NonZero, self.get_transform(), Self::parse_color(color), None, &path);
|
self.scene.fill(peniko::Fill::NonZero, self.get_transform(), Self::parse_color(color), None, &path);
|
||||||
|
@ -725,7 +971,7 @@ impl OverlayContext {
|
||||||
|
|
||||||
/// Fills the area inside the path with a pattern. Assumes `color` is in gamma space.
|
/// Fills the area inside the path with a pattern. Assumes `color` is in gamma space.
|
||||||
/// Used by the fill tool to show the area to be filled.
|
/// Used by the fill tool to show the area to be filled.
|
||||||
pub fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &Color) {
|
fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &Color) {
|
||||||
// TODO: Implement pattern fill in Vello
|
// TODO: Implement pattern fill in Vello
|
||||||
// For now, just fill with a semi-transparent version of the color
|
// For now, just fill with a semi-transparent version of the color
|
||||||
let path = self.push_path(subpaths, transform);
|
let path = self.push_path(subpaths, transform);
|
||||||
|
@ -745,16 +991,16 @@ impl OverlayContext {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_width(&self, _text: &str) -> f64 {
|
fn get_width(&self, _text: &str) -> f64 {
|
||||||
// TODO: Implement proper text measurement in Vello
|
// TODO: Implement proper text measurement in Vello
|
||||||
0.
|
0.
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text(&self, _text: &str, _font_color: &str, _background_color: Option<&str>, _transform: DAffine2, _padding: f64, _pivot: [Pivot; 2]) {
|
fn text(&self, _text: &str, _font_color: &str, _background_color: Option<&str>, _transform: DAffine2, _padding: f64, _pivot: [Pivot; 2]) {
|
||||||
// TODO: Implement text rendering in Vello
|
// TODO: Implement text rendering in Vello
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option<String>) {
|
fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option<String>) {
|
||||||
if translation.x.abs() > 1e-3 {
|
if translation.x.abs() > 1e-3 {
|
||||||
self.dashed_line(quad.top_left(), quad.top_right(), None, None, Some(2.), Some(2.), Some(0.5));
|
self.dashed_line(quad.top_left(), quad.top_right(), None, None, Some(2.), Some(2.), Some(0.5));
|
||||||
|
|
||||||
|
@ -784,16 +1030,3 @@ impl OverlayContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Pivot {
|
|
||||||
Start,
|
|
||||||
Middle,
|
|
||||||
End,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum DrawHandles {
|
|
||||||
All,
|
|
||||||
SelectedAnchors(Vec<SegmentId>),
|
|
||||||
FrontierHandles(HashMap<SegmentId, Vec<PointId>>),
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
|
@ -158,11 +158,9 @@ impl WgpuExecutor {
|
||||||
});
|
});
|
||||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
let [r, g, b, _] = background.to_rgba8_srgb();
|
let [r, g, b, a] = background.to_rgba8_srgb();
|
||||||
let render_params = RenderParams {
|
let render_params = RenderParams {
|
||||||
// We are using an explicit opaque color here to eliminate the alpha premultiplication step
|
base_color: vello::peniko::Color::from_rgba8(r, g, b, a),
|
||||||
// which would be required to support a transparent webgpu canvas
|
|
||||||
base_color: vello::peniko::Color::from_rgba8(r, g, b, 0xff),
|
|
||||||
width: size.x,
|
width: size.x,
|
||||||
height: size.y,
|
height: size.y,
|
||||||
antialiasing_method: AaConfig::Msaa16,
|
antialiasing_method: AaConfig::Msaa16,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue