diff --git a/Cargo.toml b/Cargo.toml index b38bb90c5..8d78740e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,6 @@ [workspace] resolver = "2" members = [ - "renderer", - "vello", - "vger", - "tiny_skia", "reactive", "editor-core", "examples/*", @@ -101,10 +97,20 @@ ui-events.workspace = true ui-events-winit.workspace = true dpi.workspace = true wgpu.workspace = true + +anyrender = "0.6.1" +anyrender_vello = {version = "0.6.1", optional = true } +anyrender_vello_cpu = {version = "0.8.1", optional = true, features = ["pixels_window_renderer"] } +anyrender_skia = {version = "0.2.0", optional = true } + + fluent-bundle = { version = "0.16", optional = true } unic-langid = { version = "0.9", optional = true } sys-locale = {version = "0.3.2", optional = true } +cosmic-text = { version = "0.14.2", features = ["shape-run-cache"] } + + [target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies] muda = { workspace = true } @@ -129,9 +135,7 @@ objc2-app-kit = { version = "0.3", features = [ ] } [features] -default = ["editor", "default-image-formats", "vger"] -vello = ["dep:floem_vello_renderer"] -vger = ["dep:floem_vger_renderer"] +default = ["editor", "default-image-formats", "vello-classic", "vello-cpu"] serde = [ "dep:serde", "winit/serde", @@ -168,10 +172,24 @@ tokio = ["dep:tokio"] # rfd (file dialog) async runtime rfd-async-std = ["dep:rfd", "rfd/async-std"] rfd-tokio = ["dep:rfd", "rfd/tokio"] -crossbeam = ["dep:crossbeam", "floem_renderer/crossbeam"] +crossbeam = ["dep:crossbeam"] localization = ["dep:fluent-bundle", "dep:unic-langid", "dep:sys-locale"] -[profile.dev] -opt-level = 1 +# Renderers +vello-classic = ["dep:anyrender_vello"] +skia = ["dep:anyrender_skia"] +vello-cpu = ["dep:anyrender_vello_cpu"] + + +[patch.crates-io] +anyrender = { path = "../anyrender/crates/anyrender" } +# anyrender_skia = { path = "../anyrender/crates/anyrender_skia" } +anyrender_vello = { path = "../anyrender/crates/anyrender_vello" } +anyrender_vello_cpu = { path = "../anyrender/crates/anyrender_vello_cpu" } +vello = {path = "../vello/vello/"} +# anyrender_svg = { path = "../anyrender/crates/anyrender_svg" } +# wgpu_context = { path = "../anyrender/crates/wgpu_context" } + + diff --git a/examples/localization/Cargo.toml b/examples/localization/Cargo.toml index 6a669d847..4298c922a 100644 --- a/examples/localization/Cargo.toml +++ b/examples/localization/Cargo.toml @@ -5,4 +5,4 @@ license.workspace = true version.workspace = true [dependencies] -floem = { path = "../..", features = ["vello", "localization"] } +floem = { path = "../..", features = ["localization"] } diff --git a/examples/todo-complex/Cargo.toml b/examples/todo-complex/Cargo.toml index 78ae78b75..21fd66bc6 100644 --- a/examples/todo-complex/Cargo.toml +++ b/examples/todo-complex/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" confy = "1.0.0" floem = { path = "../..", features = [ "serde", - "vello", ], default-features = false } rusqlite = { version = "0.37.0", features = ["bundled"] } serde = { workspace = true } diff --git a/examples/view-transition/Cargo.toml b/examples/view-transition/Cargo.toml index 90229c4f1..487ac960a 100644 --- a/examples/view-transition/Cargo.toml +++ b/examples/view-transition/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] -floem = { path = "../..", features = ["vello"], default-features = false } +floem = { path = "../..", default-features = false } diff --git a/examples/widget-gallery/Cargo.toml b/examples/widget-gallery/Cargo.toml index 3013b5693..86879abf5 100644 --- a/examples/widget-gallery/Cargo.toml +++ b/examples/widget-gallery/Cargo.toml @@ -4,12 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -floem = { path = "../..", features = ["rfd-async-std", "vello"] } +floem = { path = "../..", features = ["rfd-async-std"] } strum = { workspace = true } files = { path = "../files/", optional = true } stacks = { path = "../stacks/", optional = true } -[features] -default = ["full"] -vello = ["floem/vello"] -full = ["dep:files", "dep:stacks"] diff --git a/examples/widget-gallery/src/main.rs b/examples/widget-gallery/src/main.rs index 75c5bf3a2..15f2d06a6 100644 --- a/examples/widget-gallery/src/main.rs +++ b/examples/widget-gallery/src/main.rs @@ -130,7 +130,7 @@ fn app_view(window_id: WindowId) -> impl IntoView { .scroll() .style(|s| s.size_full()) }, - Some( + Some( WindowConfig::default() .size(Size::new(700.0, 400.0)) .title(name.unwrap_or_default()), diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml deleted file mode 100644 index 36bc7571f..000000000 --- a/renderer/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "floem_renderer" -version.workspace = true -edition = "2021" -repository = "https://github.com/lapce/floem" -description = "A native Rust UI library with fine-grained reactivity" -license.workspace = true - -[dependencies] -parking_lot = { workspace = true } -peniko = { workspace = true } -resvg = { workspace = true } -swash = { workspace = true } -cosmic-text = { version = "0.14.2", features = ["shape-run-cache"] } -unicode-segmentation = { workspace = true } -winit = { workspace = true } -wgpu = { workspace = true } -crossbeam = { version = "0.8", optional = true } -futures = "0.3.31" - -[target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen-futures = { version = "0.4" } - -[features] -crossbeam = ["dep:crossbeam"] diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs deleted file mode 100644 index 51ec87b54..000000000 --- a/renderer/src/lib.rs +++ /dev/null @@ -1,85 +0,0 @@ -pub mod swash; -pub mod text; - -use crate::text::LayoutRun; -use peniko::{ - kurbo::{Affine, Point, Rect, Shape, Stroke}, - BlendMode, BrushRef, -}; -pub use resvg::tiny_skia; -pub use resvg::usvg; -use text::TextLayout; - -pub mod gpu_resources; - -pub struct Svg<'a> { - pub tree: &'a usvg::Tree, - pub hash: &'a [u8], -} - -pub struct Img<'a> { - pub img: peniko::ImageBrush, - pub hash: &'a [u8], -} - -pub trait Renderer { - fn begin(&mut self, capture: bool); - - fn set_transform(&mut self, transform: Affine); - - fn set_z_index(&mut self, z_index: i32); - - /// Clip to a [`Shape`]. - fn clip(&mut self, shape: &impl Shape); - - fn clear_clip(&mut self); - - /// Stroke a [`Shape`]. - fn stroke<'b, 's>( - &mut self, - shape: &impl Shape, - brush: impl Into>, - stroke: &'s Stroke, - ); - - /// Fill a [`Shape`], using the [non-zero fill rule]. - /// - /// [non-zero fill rule]: https://en.wikipedia.org/wiki/Nonzero-rule - fn fill<'b>(&mut self, path: &impl Shape, brush: impl Into>, blur_radius: f64); - - /// Push a layer (This is not supported with Vger) - fn push_layer( - &mut self, - blend: impl Into, - alpha: f32, - transform: Affine, - clip: &impl Shape, - ); - - /// Pop a layer (This is not supported with Vger) - fn pop_layer(&mut self); - - /// Draw a [`TextLayout`]. - /// - /// The `pos` parameter specifies the upper-left corner of the layout object - /// (even for right-to-left text). - fn draw_text(&mut self, layout: &TextLayout, pos: impl Into) { - self.draw_text_with_layout(layout.layout_runs(), pos); - } - - fn draw_text_with_layout<'b>( - &mut self, - layout: impl Iterator>, - pos: impl Into, - ); - - fn draw_svg<'b>(&mut self, svg: Svg<'b>, rect: Rect, brush: Option>>); - - fn draw_img(&mut self, img: Img<'_>, rect: Rect); - - fn finish(&mut self) -> Option; - - fn debug_info(&self) -> String { - "Unknown".into() - } -} diff --git a/renderer/src/swash.rs b/renderer/src/swash.rs deleted file mode 100644 index d12c4775c..000000000 --- a/renderer/src/swash.rs +++ /dev/null @@ -1,69 +0,0 @@ -use cosmic_text::{CacheKey, CacheKeyFlags, SwashImage}; -use swash::{ - scale::{Render, ScaleContext, Source, StrikeWith}, - zeno::{Angle, Format, Transform, Vector}, -}; - -use crate::text::FONT_SYSTEM; - -const IS_MACOS: bool = cfg!(target_os = "macos"); - -pub struct SwashScaler { - context: ScaleContext, - pub font_embolden: f32, -} - -impl Default for SwashScaler { - fn default() -> Self { - Self { - context: ScaleContext::new(), - font_embolden: 0., - } - } -} - -impl SwashScaler { - pub fn new(font_embolden: f32) -> Self { - Self { - context: ScaleContext::new(), - font_embolden, - } - } - - pub fn get_image(&mut self, cache_key: CacheKey) -> Option { - let font = match FONT_SYSTEM.lock().get_font(cache_key.font_id) { - Some(some) => some, - None => { - return None; - } - }; - - // Build the scaler - let mut scaler = self - .context - .builder(font.as_swash()) - .size(f32::from_bits(cache_key.font_size_bits)) - .hint(!IS_MACOS) - .build(); - - let offset = Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float()); - - Render::new(&[ - Source::ColorOutline(0), - Source::ColorBitmap(StrikeWith::BestFit), - Source::Outline, - ]) - .format(Format::Alpha) - .offset(offset) - .embolden(self.font_embolden) - .transform(if cache_key.flags.contains(CacheKeyFlags::FAKE_ITALIC) { - Some(Transform::skew( - Angle::from_degrees(14.0), - Angle::from_degrees(0.0), - )) - } else { - None - }) - .render(&mut scaler, cache_key.glyph_id) - } -} diff --git a/renderer/src/text/mod.rs b/renderer/src/text/mod.rs deleted file mode 100644 index b541236ae..000000000 --- a/renderer/src/text/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod attrs; -mod layout; - -pub use attrs::{Attrs, AttrsList, AttrsOwned, FamilyOwned, LineHeightValue}; -pub use cosmic_text::{ - fontdb, Affinity, Align, CacheKey, Cursor, Family, LayoutGlyph, LayoutLine, LineEnding, - Stretch, Style, SubpixelBin, SwashCache, SwashContent, Weight, Wrap, -}; -pub use layout::{HitPoint, HitPosition, LayoutRun, TextLayout, FONT_SYSTEM}; diff --git a/src/app_handle.rs b/src/app_handle.rs index 0bb313261..569643c1f 100644 --- a/src/app_handle.rs +++ b/src/app_handle.rs @@ -1,5 +1,4 @@ use dpi::PhysicalPosition; -use floem_renderer::gpu_resources::GpuResources; #[cfg(not(target_arch = "wasm32"))] use std::time::Instant; use ui_events_winit::WindowEventTranslation; @@ -12,6 +11,7 @@ use wgpu::web_sys; use floem_reactive::SignalUpdate; use peniko::kurbo::{Point, Size}; use std::{collections::HashMap, rc::Rc}; +use wgpu::{Device, Instance, Queue}; use winit::{ dpi::{LogicalPosition, LogicalSize}, event::WindowEvent, @@ -19,21 +19,10 @@ use winit::{ window::{Theme, WindowId}, }; -use crate::app::AppConfig; use crate::{ - AppEvent, - action::{Timer, TimerToken}, - app::{APP_UPDATE_EVENTS, AppEventCallback, AppUpdateEvent, UserEvent}, - context::PaintState, - dropped_file::FileDragEvent::{self, DragDropped}, - ext_event::EXT_EVENT_HANDLER, - inspector::Capture, - profiler::{Profile, ProfileEvent}, - view::View, - window::WindowConfig, - window_handle::WindowHandle, - window_id::process_window_updates, + action::{Timer, TimerToken}, app::{AppEventCallback, AppUpdateEvent, UserEvent, APP_UPDATE_EVENTS}, context::PaintState, dropped_file::FileDragEvent::{self, DragDropped}, ext_event::EXT_EVENT_HANDLER, inspector::Capture, profiler::{Profile, ProfileEvent}, renderer::AnyWindowRenderer, view::View, window::WindowConfig, window_handle::WindowHandle, window_id::process_window_updates, AppEvent }; +use crate::{app::AppConfig, gpu_resources::GpuResources}; pub(crate) struct ApplicationHandle { window_handles: HashMap, @@ -84,7 +73,7 @@ impl ApplicationHandle { } = &handle.paint_state { let (gpu_resources, surface) = rx.recv().unwrap().unwrap(); - let renderer = crate::renderer::Renderer::new( + let renderer = AnyWindowRenderer::VelloWindowRenderer::new( window.clone(), gpu_resources.clone(), surface, diff --git a/src/context.rs b/src/context.rs index 9617037c2..d20cfbbd9 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,6 +1,6 @@ +use crate::gpu_resources::{GpuResourceError, GpuResources}; +use anyrender::WindowRenderer; use floem_reactive::Scope; -use floem_renderer::Renderer as FloemRenderer; -use floem_renderer::gpu_resources::{GpuResourceError, GpuResources}; use peniko::kurbo::{Affine, Point, Rect, RoundedRect, Shape, Size, Vec2}; use std::{ ops::{Deref, DerefMut}, @@ -27,7 +27,6 @@ use crate::animate::{AnimStateKind, RepeatMode}; use crate::dropped_file::FileDragEvent; use crate::easing::{Easing, Linear}; use crate::menu::Menu; -use crate::renderer::Renderer; use crate::style::{Disabled, DisplayProp, Focusable, Hidden, PointerEvents, PointerEventsProp}; use crate::view_state::IsHiddenState; use crate::{ @@ -1058,61 +1057,11 @@ std::thread_local! { pub struct PaintCx<'a> { pub window_state: &'a mut WindowState, pub(crate) paint_state: &'a mut PaintState, - pub(crate) transform: Affine, - pub(crate) clip: Option, - pub(crate) z_index: Option, - pub(crate) saved_transforms: Vec, - pub(crate) saved_clips: Vec>, - pub(crate) saved_z_indexes: Vec>, pub gpu_resources: Option, pub window: Arc, - #[cfg(feature = "vello")] - pub layer_count: usize, - #[cfg(feature = "vello")] - pub saved_layer_counts: Vec, } impl PaintCx<'_> { - pub fn save(&mut self) { - self.saved_transforms.push(self.transform); - self.saved_clips.push(self.clip); - self.saved_z_indexes.push(self.z_index); - #[cfg(feature = "vello")] - self.saved_layer_counts.push(self.layer_count); - } - - pub fn restore(&mut self) { - #[cfg(feature = "vello")] - { - let saved_count = self.saved_layer_counts.pop().unwrap_or_default(); - while self.layer_count > saved_count { - self.pop_layer(); - self.layer_count -= 1; - } - } - - self.transform = self.saved_transforms.pop().unwrap_or_default(); - self.clip = self.saved_clips.pop().unwrap_or_default(); - self.z_index = self.saved_z_indexes.pop().unwrap_or_default(); - self.paint_state - .renderer_mut() - .set_transform(self.transform); - if let Some(z_index) = self.z_index { - self.paint_state.renderer_mut().set_z_index(z_index); - } else { - self.paint_state.renderer_mut().set_z_index(0); - } - - #[cfg(not(feature = "vello"))] - { - if let Some(rect) = self.clip { - self.paint_state.renderer_mut().clip(&rect); - } else { - self.paint_state.renderer_mut().clear_clip(); - } - } - } - /// Allows a `View` to determine if it is being called in order to /// paint a *draggable* image of itself during a drag (likely /// `draggable()` was called on the `View` or `ViewId`) as opposed @@ -1254,47 +1203,6 @@ impl PaintCx<'_> { self.restore(); } - /// Clip the drawing area to the given shape. - pub fn clip(&mut self, shape: &impl Shape) { - #[cfg(feature = "vello")] - { - use peniko::Mix; - - self.push_layer(Mix::Normal, 1.0, Affine::IDENTITY, shape); - self.layer_count += 1; - self.clip = Some(shape.bounding_box().to_rounded_rect(0.0)); - } - - #[cfg(not(feature = "vello"))] - { - let rect = if let Some(rect) = shape.as_rect() { - rect.to_rounded_rect(0.0) - } else if let Some(rect) = shape.as_rounded_rect() { - rect - } else { - let rect = shape.bounding_box(); - rect.to_rounded_rect(0.0) - }; - - let rect = if let Some(existing) = self.clip { - let rect = existing.rect().intersect(rect.rect()); - self.paint_state.renderer_mut().clip(&rect); - rect.to_rounded_rect(0.0) - } else { - self.paint_state.renderer_mut().clip(&shape); - rect - }; - - self.clip = Some(rect); - } - } - - /// Remove clipping so the entire window can be rendered to. - pub fn clear_clip(&mut self) { - self.clip = None; - self.paint_state.renderer_mut().clear_clip(); - } - pub fn offset(&mut self, offset: (f64, f64)) { let mut new = self.transform.as_coeffs(); new[4] += offset.0; @@ -1338,34 +1246,10 @@ impl PaintCx<'_> { Size::ZERO } } - - pub(crate) fn set_z_index(&mut self, z_index: i32) { - self.z_index = Some(z_index); - self.paint_state.renderer_mut().set_z_index(z_index); - } - - pub fn is_focused(&self, id: ViewId) -> bool { - self.window_state.is_focused(&id) - } } -// TODO: should this be private? -pub enum PaintState { - /// The renderer is not yet initialized. This state is used to wait for the GPU resources to be acquired. - PendingGpuResources { - window: Arc, - rx: Receiver), GpuResourceError>>, - font_embolden: f32, - /// This field holds an instance of `Renderer::Uninitialized` until the GPU resources are acquired, - /// which will be returned in `PaintState::renderer` and `PaintState::renderer_mut`. - /// All calls to renderer methods will be no-ops until the renderer is initialized. - /// - /// Previously, `PaintState::renderer` and `PaintState::renderer_mut` would panic if called when the renderer was uninitialized. - /// However, this turned out to be hard to handle properly and led to panics, especially since the rest of the application code can't control when the renderer is initialized. - renderer: crate::renderer::Renderer, - }, - /// The renderer is initialized and ready to paint. - Initialized { renderer: crate::renderer::Renderer }, +pub struct PaintState { + // renderer: Box, } impl PaintState { diff --git a/renderer/src/gpu_resources.rs b/src/gpu_resources.rs similarity index 91% rename from renderer/src/gpu_resources.rs rename to src/gpu_resources.rs index dc43f82df..6ddc89cdd 100644 --- a/renderer/src/gpu_resources.rs +++ b/src/gpu_resources.rs @@ -11,9 +11,9 @@ use std::{future::Future, sync::Arc}; #[cfg(feature = "crossbeam")] -use crossbeam::channel::{bounded as sync_channel, Receiver}; +use crossbeam::channel::{Receiver, bounded as sync_channel}; #[cfg(not(feature = "crossbeam"))] -use std::sync::mpsc::{sync_channel, Receiver}; +use std::sync::mpsc::{Receiver, sync_channel}; use wgpu::Backends; use winit::window::{Window, WindowId}; @@ -70,7 +70,7 @@ impl GpuResources { } }; - let Ok(adapter) = instance + let Some(adapter) = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::default(), compatible_surface: Some(&surface), @@ -86,11 +86,14 @@ impl GpuResources { tx.send( adapter - .request_device(&wgpu::DeviceDescriptor { - label: None, - required_features, - ..Default::default() - }) + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features, + ..Default::default() + }, + None, + ) .await .map_err(GpuResourceError::DeviceRequestError) .map(|(device, queue)| Self { diff --git a/src/lib.rs b/src/lib.rs index 9ec4e249a..ff8b45939 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -182,7 +182,6 @@ mod app; #[cfg(target_os = "macos")] mod app_delegate; mod app_handle; -#[cfg(feature = "vello")] mod border_path_iter; mod clipboard; pub mod context; @@ -193,15 +192,22 @@ pub mod ext_event; pub mod file; #[cfg(any(feature = "rfd-async-std", feature = "rfd-tokio"))] pub mod file_action; +pub mod gpu_resources; pub(crate) mod id; mod inspector; pub mod menu; mod nav; mod profiler; -mod renderer; +pub mod renderer; pub mod responsive; mod screen_layout; pub mod style; +pub mod text { + pub mod attrs; + pub mod layout; + pub use attrs::*; + pub use layout::*; +} pub mod theme; pub mod unit; mod update; @@ -232,10 +238,6 @@ pub mod receiver_signal { pub use app::{AppConfig, AppEvent, Application, launch, quit_app, reopen}; pub use clipboard::{Clipboard, ClipboardError}; pub use floem_reactive as reactive; -pub use floem_renderer::Renderer; -pub use floem_renderer::Svg as RendererSvg; -pub use floem_renderer::gpu_resources::GpuResources; -pub use floem_renderer::text; pub use id::ViewId; pub use imbl; pub use muda; @@ -251,7 +253,6 @@ pub use window_id::{Urgency, WindowIdExt}; pub use window_state::WindowState; pub mod prelude { - pub use crate::Renderer; pub use crate::unit::{DurationUnitExt, UnitExt}; pub use crate::view_tuple::ViewTuple; pub use crate::views::*; diff --git a/src/renderer.rs b/src/renderer.rs index 304dd0594..c0ae7f853 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,450 +1,366 @@ -//! # Renderer -//! -//! This section is to help understand how Floem is implemented for developers of Floem. -//! -//! ## Render loop and update lifecycle -//! -//! event -> update -> layout -> paint. -//! -//! #### Event -//! After an event comes in (e.g. the user clicked the mouse, pressed a key etc), the event will be propagated from the root view to the children. -//! If the parent does not handle the event, it will automatically be sent to the child view. If the parent does handle the event the parent can decide whether the event should continue propagating so that the child can also process the event or if the propagation should stop. -//! The event propagation is stopped whenever an event listener returns `true` on the event handling. -//! -//! -//! #### Event handling -> reactive system updates -//! Event handling is a common place for reactive state changes to occur. E.g., on the counter example, when you click increment, -//! it updates the counter and because the label has an effect that is subscribed to those changes (see [`floem_reactive::create_effect`]), the label will update the text it presents. -//! -//! #### Update -//! The update of states on the Views could cause some of them to need a new layout recalculation, because the size might have changed etc. -//! The reactive system can't directly manipulate the view state of the label because the `WindowState` owns all the views. And instead, it will send the update to a message queue via [`ViewId::update_state`](crate::ViewId::update_state) -//! After the event propagation is done, Floem will process all the update messages in the queue, and it can manipulate the state of a particular view through the update method. -//! -//! -//! #### Layout -//! The layout method is called from the root view to re-layout the views that have requested a layout call. -//! The layout call is to change the layout properties at Taffy, and after the layout call is done, `compute_layout` is called to calculate the sizes and positions of each view. -//! -//! #### Paint -//! And in the end, `paint` is called to render all the views to the screen. -//! -//! -//! ## Terminology -//! -//! Useful definitions for developers of Floem -//! -//! #### Active view -//! -//! Affects pointer events. Pointer events will only be sent to the active view. The view will continue to receive pointer events even if the mouse is outside its bounds. -//! It is useful when you drag things, e.g. the scroll bar, you set the scroll bar active after pointer down, then when you drag, the `PointerMove` will always be sent to the view, even if your mouse is outside of the view. -//! -//! #### Focused view -//! Affects keyboard events. Keyboard events will only be sent to the focused view. The view will continue to receive keyboard events even if it's not the active view. -//! -//! ## Notable invariants and tolerances -//! - There can be only one root `View` -//! - Only one view can be active at a time. -//! - Only one view can be focused at a time. -//! use std::sync::Arc; -use crate::kurbo::Point; -use floem_renderer::Img; -use floem_renderer::gpu_resources::GpuResources; -use floem_renderer::text::LayoutRun; -use floem_tiny_skia_renderer::TinySkiaRenderer; -#[cfg(feature = "vello")] -use floem_vello_renderer::VelloRenderer; -#[cfg(not(feature = "vello"))] -use floem_vger_renderer::VgerRenderer; -use peniko::BrushRef; -use peniko::kurbo::{Affine, Rect, Shape, Size, Stroke}; -use winit::window::Window; +use anyrender::*; +use peniko::{ + kurbo::{Affine, Rect, Shape, Stroke}, + *, +}; -#[allow(clippy::large_enum_variant)] -pub enum Renderer { - #[cfg(feature = "vello")] - Vello(VelloRenderer), - #[cfg(not(feature = "vello"))] - Vger(VgerRenderer), - TinySkia(TinySkiaRenderer>), - /// Uninitialized renderer, used to allow the renderer to be created lazily - /// All operations on this renderer are no-ops - Uninitialized { - scale: f64, - size: Size, - }, -} +macro_rules! define_renderer_enum { + ( + $(#[$meta:meta])* + $vis:vis enum $enum_name:ident { + $( + $(#[$variant_meta:meta])* + $variant:ident($renderer_type:ty) with feature $feature:literal, + )* + } + ) => { + $(#[$meta])* + $vis enum $enum_name { + $( + $(#[$variant_meta])* + #[cfg(feature = $feature)] + $variant($renderer_type), + )* + } -impl Renderer { - pub fn new( - window: Arc, - gpu_resources: GpuResources, - surface: wgpu::Surface<'static>, - scale: f64, - size: Size, - font_embolden: f32, - ) -> Self { - let size = Size::new(size.width.max(1.0), size.height.max(1.0)); + $vis enum AnyScenePainter<'a> { + $( + #[cfg(feature = $feature)] + $variant(&'a mut <$renderer_type as WindowRenderer>::ScenePainter<'a>), + )* + } - let force_tiny_skia = std::env::var("FLOEM_FORCE_TINY_SKIA") - .ok() - .map(|val| val.as_str() == "1") - .unwrap_or(false); + impl WindowRenderer for $enum_name { + type ScenePainter<'a> = AnyScenePainter<'a> + where + Self: 'a; - #[cfg(feature = "vello")] - let vger_err = if !force_tiny_skia { - match VelloRenderer::new( - gpu_resources, - surface, - size.width as u32, - size.height as u32, - scale, - font_embolden, - ) { - Ok(vger) => return Self::Vello(vger), - Err(err) => Some(err), + fn resume(&mut self, window: Arc, width: u32, height: u32) { + match self { + $( + #[cfg(feature = $feature)] + Self::$variant(r) => r.resume(window, width, height), + )* + } } - } else { - None - }; - #[cfg(not(feature = "vello"))] - let vger_err = if !force_tiny_skia { - match VgerRenderer::new( - gpu_resources, - surface, - size.width as u32, - size.height as u32, - scale, - font_embolden, - ) { - Ok(vger) => return Self::Vger(vger), - Err(err) => Some(err), + fn suspend(&mut self) { + match self { + $( + #[cfg(feature = $feature)] + Self::$variant(r) => r.suspend(), + )* + } } - } else { - None - }; - let tiny_skia_err = match TinySkiaRenderer::new( - window, - size.width as u32, - size.height as u32, - scale, - font_embolden, - ) { - Ok(tiny_skia) => return Self::TinySkia(tiny_skia), - Err(err) => err, - }; + fn is_active(&self) -> bool { + match self { + $( + #[cfg(feature = $feature)] + Self::$variant(r) => r.is_active(), + )* + } + } - if !force_tiny_skia { - panic!( - "Failed to create VgerRenderer: {}\nFailed to create TinySkiaRenderer: {tiny_skia_err}", - vger_err.unwrap() - ); - } else { - panic!("Failed to create TinySkiaRenderer: {tiny_skia_err}"); - } - } + fn set_size(&mut self, width: u32, height: u32) { + match self { + $( + #[cfg(feature = $feature)] + Self::$variant(r) => r.set_size(width, height), + )* + } + } - pub fn resize(&mut self, scale: f64, size: Size) { - let size = Size::new(size.width.max(1.0), size.height.max(1.0)); - match self { - #[cfg(feature = "vello")] - Renderer::Vello(r) => r.resize(size.width as u32, size.height as u32, scale), - #[cfg(not(feature = "vello"))] - Renderer::Vger(r) => r.resize(size.width as u32, size.height as u32, scale), - Renderer::TinySkia(r) => r.resize(size.width as u32, size.height as u32, scale), - Renderer::Uninitialized { .. } => {} + fn render)>(&mut self, draw_fn: F) { + match self { + $( + #[cfg(feature = $feature)] + Self::$variant(r) => r.render(|p| { + let mut wrapper = AnyScenePainter::$variant(p); + draw_fn(&mut wrapper); + }), + )* + } + } } - } - pub fn set_scale(&mut self, scale: f64) { - match self { - #[cfg(feature = "vello")] - Renderer::Vello(r) => r.set_scale(scale), - #[cfg(not(feature = "vello"))] - Renderer::Vger(r) => r.set_scale(scale), - Renderer::TinySkia(r) => r.set_scale(scale), - Renderer::Uninitialized { - scale: old_scale, .. - } => { - *old_scale = scale; + impl<'a> PaintScene for AnyScenePainter<'a> { + fn reset(&mut self) { + match self { + $( + #[cfg(feature = $feature)] + Self::$variant(p) => p.reset(), + )* + } } - } - } - pub fn scale(&self) -> f64 { - match self { - #[cfg(feature = "vello")] - Renderer::Vello(r) => r.scale(), - #[cfg(not(feature = "vello"))] - Renderer::Vger(r) => r.scale(), - Renderer::TinySkia(r) => r.scale(), - Renderer::Uninitialized { scale, .. } => *scale, - } - } + fn push_layer( + &mut self, + blend: impl Into, + alpha: f32, + transform: Affine, + clip: &impl Shape, + ) { + match self { + $( + #[cfg(feature = $feature)] + Self::$variant(p) => p.push_layer(blend, alpha, transform, clip), + )* + } + } - pub fn size(&self) -> Size { - match self { - #[cfg(feature = "vello")] - Renderer::Vello(r) => r.size(), - #[cfg(not(feature = "vello"))] - Renderer::Vger(r) => r.size(), - Renderer::TinySkia(r) => r.size(), - Renderer::Uninitialized { size, .. } => *size, - } - } + fn push_clip_layer(&mut self, transform: Affine, clip: &impl Shape) { + match self { + $( + #[cfg(feature = $feature)] + Self::$variant(p) => p.push_clip_layer(transform, clip), + )* + } + } - pub(crate) fn debug_info(&self) -> String { - use crate::Renderer; + fn pop_layer(&mut self) { + match self { + $( + #[cfg(feature = $feature)] + Self::$variant(p) => p.pop_layer(), + )* + } + } - match self { - #[cfg(feature = "vello")] - Self::Vello(r) => r.debug_info(), - #[cfg(not(feature = "vello"))] - Self::Vger(r) => r.debug_info(), - Self::TinySkia(r) => r.debug_info(), - Self::Uninitialized { .. } => "Uninitialized".to_string(), - } - } -} + fn stroke<'b>( + &mut self, + style: &Stroke, + transform: Affine, + brush: impl Into>, + brush_transform: Option, + shape: &impl Shape, + ) { + match self { + $( + #[cfg(feature = $feature)] + Self::$variant(p) => p.stroke(style, transform, brush, brush_transform, shape), + )* + } + } -impl floem_renderer::Renderer for Renderer { - fn begin(&mut self, capture: bool) { - match self { - #[cfg(feature = "vello")] - Renderer::Vello(r) => { - r.begin(capture); + fn fill<'b>( + &mut self, + style: Fill, + transform: Affine, + brush: impl Into>, + brush_transform: Option, + shape: &impl Shape, + ) { + match self { + $( + #[cfg(feature = $feature)] + Self::$variant(p) => p.fill(style, transform, brush, brush_transform, shape), + )* + } } - #[cfg(not(feature = "vello"))] - Renderer::Vger(r) => { - r.begin(capture); + + fn draw_glyphs<'b, 's: 'b>( + &'s mut self, + font: &'b FontData, + font_size: f32, + hint: bool, + normalized_coords: &'b [NormalizedCoord], + embolden: kurbo::Vec2, + style: impl Into>, + brush: impl Into>, + brush_alpha: f32, + transform: Affine, + glyph_transform: Option, + glyphs: impl Iterator, + ) { + match self { + $( + #[cfg(feature = $feature)] + Self::$variant(p) => p.draw_glyphs( + font, + font_size, + hint, + normalized_coords, + embolden, + style, + brush, + brush_alpha, + transform, + glyph_transform, + glyphs, + ), + )* + } } - Renderer::TinySkia(r) => { - r.begin(capture); + + fn draw_box_shadow( + &mut self, + transform: Affine, + rect: Rect, + brush: Color, + radius: f64, + std_dev: f64, + ) { + match self { + $( + #[cfg(feature = $feature)] + Self::$variant(p) => p.draw_box_shadow(transform, rect, brush, radius, std_dev), + )* + } + } + + fn draw_image(&mut self, image: ImageBrushRef, transform: Affine) { + match self { + $( + #[cfg(feature = $feature)] + Self::$variant(p) => p.draw_image(image, transform), + )* + } } - Renderer::Uninitialized { .. } => {} } - } + }; +} - fn clip(&mut self, shape: &impl Shape) { +pub enum AnyWindowRenderer { + VelloClassic(anyrender_vello::VelloWindowRenderer), +} +pub enum AnyScenePainter<'a> { + VelloClassic( + &'a mut ::ScenePainter<'a>, + ), +} +impl WindowRenderer for AnyWindowRenderer { + type ScenePainter<'a> + = AnyScenePainter<'a> + where + Self: 'a; + fn resume(&mut self, window: Arc, width: u32, height: u32) { match self { - #[cfg(feature = "vello")] - Renderer::Vello(v) => { - v.clip(shape); - } - #[cfg(not(feature = "vello"))] - Renderer::Vger(v) => { - v.clip(shape); - } - Renderer::TinySkia(v) => { - v.clip(shape); - } - Renderer::Uninitialized { .. } => {} + Self::VelloClassic(r) => r.resume(window, width, height), } } - - fn clear_clip(&mut self) { + fn suspend(&mut self) { match self { - #[cfg(feature = "vello")] - Renderer::Vello(v) => { - v.clear_clip(); - } - #[cfg(not(feature = "vello"))] - Renderer::Vger(v) => { - v.clear_clip(); - } - Renderer::TinySkia(v) => { - v.clear_clip(); - } - Renderer::Uninitialized { .. } => {} + Self::VelloClassic(r) => r.suspend(), } } - - fn stroke<'b, 's>( - &mut self, - shape: &impl Shape, - brush: impl Into>, - stroke: &'s Stroke, - ) { + fn is_active(&self) -> bool { match self { - #[cfg(feature = "vello")] - Renderer::Vello(v) => { - v.stroke(shape, brush, stroke); - } - #[cfg(not(feature = "vello"))] - Renderer::Vger(v) => { - v.stroke(shape, brush, stroke); - } - Renderer::TinySkia(v) => { - v.stroke(shape, brush, stroke); - } - Renderer::Uninitialized { .. } => {} + Self::VelloClassic(r) => r.is_active(), } } - - fn fill<'b>( - &mut self, - path: &impl peniko::kurbo::Shape, - brush: impl Into>, - blur_radius: f64, - ) { + fn set_size(&mut self, width: u32, height: u32) { match self { - #[cfg(feature = "vello")] - Renderer::Vello(v) => { - v.fill(path, brush, blur_radius); - } - #[cfg(not(feature = "vello"))] - Renderer::Vger(v) => { - v.fill(path, brush, blur_radius); - } - Renderer::TinySkia(v) => { - v.fill(path, brush, blur_radius); - } - Renderer::Uninitialized { .. } => {} + Self::VelloClassic(r) => r.set_size(width, height), + } + } + fn render)>(&mut self, draw_fn: F) { + match self { + Self::VelloClassic(r) => r.render(|p| { + let mut wrapper = AnyScenePainter::VelloClassic(p); + draw_fn(&mut wrapper); + }), + } + } +} +impl<'a> PaintScene for AnyScenePainter<'a> { + fn reset(&mut self) { + match self { + Self::VelloClassic(p) => p.reset(), } } - fn push_layer( &mut self, - blend: impl Into, + blend: impl Into, alpha: f32, transform: Affine, clip: &impl Shape, ) { match self { - #[cfg(feature = "vello")] - Renderer::Vello(v) => { - v.push_layer(blend, alpha, transform, clip); - } - #[cfg(not(feature = "vello"))] - Renderer::Vger(v) => { - v.push_layer(blend, alpha, transform, clip); - } - Renderer::TinySkia(v) => v.push_layer(blend, alpha, transform, clip), - Renderer::Uninitialized { .. } => {} + Self::VelloClassic(p) => p.push_layer(blend, alpha, transform, clip), } } - - fn pop_layer(&mut self) { + fn push_clip_layer(&mut self, transform: Affine, clip: &impl Shape) { match self { - #[cfg(feature = "vello")] - Renderer::Vello(v) => { - v.pop_layer(); - } - #[cfg(not(feature = "vello"))] - Renderer::Vger(v) => { - v.pop_layer(); - } - Renderer::TinySkia(v) => v.pop_layer(), - Renderer::Uninitialized { .. } => {} + Self::VelloClassic(p) => p.push_clip_layer(transform, clip), } } - - fn draw_text_with_layout<'b>( - &mut self, - layout: impl Iterator>, - pos: impl Into, - ) { + fn pop_layer(&mut self) { match self { - #[cfg(feature = "vello")] - Renderer::Vello(v) => { - v.draw_text_with_layout(layout, pos); - } - #[cfg(not(feature = "vello"))] - Renderer::Vger(v) => { - v.draw_text_with_layout(layout, pos); - } - Renderer::TinySkia(v) => { - v.draw_text_with_layout(layout, pos); - } - Renderer::Uninitialized { .. } => {} + Self::VelloClassic(p) => p.pop_layer(), } } - - fn draw_img(&mut self, img: Img<'_>, rect: Rect) { + fn stroke<'b>( + &mut self, + style: &Stroke, + transform: Affine, + brush: impl Into>, + brush_transform: Option, + shape: &impl Shape, + ) { match self { - #[cfg(feature = "vello")] - Renderer::Vello(v) => { - v.draw_img(img, rect); - } - #[cfg(not(feature = "vello"))] - Renderer::Vger(v) => { - v.draw_img(img, rect); - } - Renderer::TinySkia(v) => { - v.draw_img(img, rect); - } - Renderer::Uninitialized { .. } => {} + Self::VelloClassic(p) => p.stroke(style, transform, brush, brush_transform, shape), } } - - fn draw_svg<'b>( + fn fill<'b>( &mut self, - svg: floem_renderer::Svg<'b>, - rect: Rect, - brush: Option>>, + style: Fill, + transform: Affine, + brush: impl Into>, + brush_transform: Option, + shape: &impl Shape, ) { match self { - #[cfg(feature = "vello")] - Renderer::Vello(v) => { - v.draw_svg(svg, rect, brush); - } - #[cfg(not(feature = "vello"))] - Renderer::Vger(v) => { - v.draw_svg(svg, rect, brush); - } - Renderer::TinySkia(v) => { - v.draw_svg(svg, rect, brush); - } - Renderer::Uninitialized { .. } => {} + Self::VelloClassic(p) => p.fill(style, transform, brush, brush_transform, shape), } } - - fn set_transform(&mut self, transform: Affine) { + fn draw_glyphs<'b, 's: 'b>( + &'s mut self, + font: &'b FontData, + font_size: f32, + hint: bool, + normalized_coords: &'b [NormalizedCoord], + embolden: kurbo::Vec2, + style: impl Into>, + brush: impl Into>, + brush_alpha: f32, + transform: Affine, + glyph_transform: Option, + glyphs: impl Iterator, + ) { match self { - #[cfg(feature = "vello")] - Renderer::Vello(v) => { - v.set_transform(transform); - } - #[cfg(not(feature = "vello"))] - Renderer::Vger(v) => { - v.set_transform(transform); - } - Renderer::TinySkia(v) => { - v.set_transform(transform); - } - Renderer::Uninitialized { .. } => {} + Self::VelloClassic(p) => p.draw_glyphs( + font, + font_size, + hint, + normalized_coords, + embolden, + style, + brush, + brush_alpha, + transform, + glyph_transform, + glyphs, + ), } } - - fn set_z_index(&mut self, z_index: i32) { + fn draw_box_shadow( + &mut self, + transform: Affine, + rect: Rect, + brush: Color, + radius: f64, + std_dev: f64, + ) { match self { - #[cfg(feature = "vello")] - Renderer::Vello(v) => { - v.set_z_index(z_index); - } - #[cfg(not(feature = "vello"))] - Renderer::Vger(v) => { - v.set_z_index(z_index); - } - Renderer::TinySkia(v) => { - v.set_z_index(z_index); - } - Renderer::Uninitialized { .. } => {} + Self::VelloClassic(p) => p.draw_box_shadow(transform, rect, brush, radius, std_dev), } } - - fn finish(&mut self) -> Option { + fn draw_image(&mut self, image: ImageBrushRef, transform: Affine) { match self { - #[cfg(feature = "vello")] - Renderer::Vello(r) => r.finish(), - #[cfg(not(feature = "vello"))] - Renderer::Vger(r) => r.finish(), - Renderer::TinySkia(r) => r.finish(), - Renderer::Uninitialized { .. } => None, + Self::VelloClassic(p) => p.draw_image(image, transform), } } } diff --git a/renderer/src/text/attrs.rs b/src/text/attrs.rs similarity index 99% rename from renderer/src/text/attrs.rs rename to src/text/attrs.rs index eb5511fcc..8ef23f4ab 100644 --- a/renderer/src/text/attrs.rs +++ b/src/text/attrs.rs @@ -1,6 +1,6 @@ use std::ops::Range; -use crate::text::{fontdb, Family, Stretch, Style, Weight}; +pub use cosmic_text::{Family, Stretch, Style, Weight, fontdb}; use peniko::Color; /// An owned version of [`Family`] diff --git a/renderer/src/text/layout.rs b/src/text/layout.rs similarity index 99% rename from renderer/src/text/layout.rs rename to src/text/layout.rs index 9532d0783..82bf3f19f 100644 --- a/renderer/src/text/layout.rs +++ b/src/text/layout.rs @@ -1,7 +1,6 @@ use std::{ops::Range, sync::LazyLock}; -use crate::text::AttrsList; -use cosmic_text::{ +pub use cosmic_text::{ Affinity, Align, Buffer, BufferLine, Cursor, FontSystem, LayoutCursor, LayoutGlyph, LineEnding, LineIter, Metrics, Scroll, Shaping, Wrap, }; @@ -9,6 +8,8 @@ use parking_lot::Mutex; use peniko::kurbo::{Point, Size}; use unicode_segmentation::UnicodeSegmentation; +use crate::text::attrs::AttrsList; + pub static FONT_SYSTEM: LazyLock> = LazyLock::new(|| { let mut font_system = FontSystem::new(); #[cfg(target_os = "macos")] diff --git a/src/theme.rs b/src/theme.rs index b11420106..f66c420e6 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -7,6 +7,7 @@ use crate::{ StylePropValue, Transition, }, style_class, + text::Weight, unit::{DurationUnitExt, UnitExt}, views::{ ButtonClass, CheckboxClass, LabelClass, LabelCustomStyle, LabeledCheckboxClass, @@ -18,7 +19,6 @@ use crate::{ slider::{SliderClass, SliderCustomStyle}, }, }; -use floem_renderer::text::Weight; use peniko::{Brush, Color, color::palette::css}; use smallvec::smallvec; diff --git a/src/view.rs b/src/view.rs index 14a04350f..3c818c7fe 100644 --- a/src/view.rs +++ b/src/view.rs @@ -49,7 +49,6 @@ use std::any::Any; use taffy::tree::NodeId; use crate::{ - Renderer, context::{ComputeLayoutCx, EventCx, LayoutCx, PaintCx, StyleCx, UpdateCx}, event::{Event, EventPropagation}, id::ViewId, @@ -550,7 +549,7 @@ fn paint_box_shadow( } } } -#[cfg(feature = "vello")] + pub(crate) fn paint_outline(cx: &mut PaintCx, style: &ViewStyleProps, size: Size) { use crate::{ border_path_iter::{BorderPath, BorderPathEvent}, @@ -602,113 +601,6 @@ pub(crate) fn paint_outline(cx: &mut PaintCx, style: &ViewStyleProps, size: Size assert!(current_path.is_empty()); } -#[cfg(not(feature = "vello"))] -pub(crate) fn paint_outline(cx: &mut PaintCx, style: &ViewStyleProps, size: Size) { - let outline = &style.outline().0; - if outline.width == 0. { - // TODO: we should warn! when outline is < 0 - return; - } - let half = outline.width / 2.0; - let rect = size.to_rect().inflate(half, half); - let border_radii = border_to_radii_view(style, size); - cx.stroke( - &rect.to_rounded_rect(radii_add(border_radii, half)), - &style.outline_color(), - outline, - ); -} - -#[cfg(not(feature = "vello"))] -pub(crate) fn paint_border( - cx: &mut PaintCx, - layout_style: &LayoutProps, - style: &ViewStyleProps, - size: Size, -) { - let border = layout_style.border(); - - let left = border.left.map(|v| v.0).unwrap_or(Stroke::new(0.)); - let top = border.top.map(|v| v.0).unwrap_or(Stroke::new(0.)); - let right = border.right.map(|v| v.0).unwrap_or(Stroke::new(0.)); - let bottom = border.bottom.map(|v| v.0).unwrap_or(Stroke::new(0.)); - - if left.width == top.width - && top.width == right.width - && right.width == bottom.width - && bottom.width == left.width - && left.width > 0.0 - && style.border_color().left.is_some() - && style.border_color().top.is_some() - && style.border_color().right.is_some() - && style.border_color().bottom.is_some() - && style.border_color().left == style.border_color().top - && style.border_color().top == style.border_color().right - && style.border_color().right == style.border_color().bottom - { - let half = left.width / 2.0; - let rect = size.to_rect().inflate(-half, -half); - let radii = border_to_radii_view(style, size); - if let Some(color) = style.border_color().left { - if radii_max(radii) > 0.0 { - let radii = radii_map(radii, |r| (r - half).max(0.0)); - cx.stroke(&rect.to_rounded_rect(radii), &color, &left); - } else { - cx.stroke(&rect, &color, &left); - } - } - } else { - // TODO: now with vello should we do this left.width > 0. check? - if left.width > 0.0 - && let Some(color) = style.border_color().left - { - let half = left.width / 2.0; - cx.stroke( - &Line::new(Point::new(half, 0.0), Point::new(half, size.height)), - &color, - &left, - ); - } - if right.width > 0.0 - && let Some(color) = style.border_color().right - { - let half = right.width / 2.0; - cx.stroke( - &Line::new( - Point::new(size.width - half, 0.0), - Point::new(size.width - half, size.height), - ), - &color, - &right, - ); - } - if top.width > 0.0 - && let Some(color) = style.border_color().top - { - let half = top.width / 2.0; - cx.stroke( - &Line::new(Point::new(0.0, half), Point::new(size.width, half)), - &color, - &top, - ); - } - if bottom.width > 0.0 - && let Some(color) = style.border_color().bottom - { - let half = bottom.width / 2.0; - cx.stroke( - &Line::new( - Point::new(0.0, size.height - half), - Point::new(size.width, size.height - half), - ), - &color, - &bottom, - ); - } - } -} - -#[cfg(feature = "vello")] pub(crate) fn paint_border( cx: &mut PaintCx, layout_style: &LayoutProps, diff --git a/src/view_state.rs b/src/view_state.rs index 24ddb81cd..30f563cc6 100644 --- a/src/view_state.rs +++ b/src/view_state.rs @@ -70,7 +70,6 @@ impl Stack { } } -#[cfg(feature = "vello")] prop_extractor! { pub(crate) ViewStyleProps { pub border_radius: BorderRadiusProp, @@ -84,19 +83,6 @@ prop_extractor! { pub shadow: BoxShadowProp, } } -// removing outlines to make clippy happy about progress fields not being read -#[cfg(not(feature = "vello"))] -prop_extractor! { - pub(crate) ViewStyleProps { - pub border_radius: BorderRadiusProp, - - pub outline: Outline, - pub outline_color: OutlineColor, - pub border_color: BorderColorProp, - pub background: Background, - pub shadow: BoxShadowProp, - } -} bitflags! { #[derive(Default, Copy, Clone, Debug)] diff --git a/src/views/editor/gutter.rs b/src/views/editor/gutter.rs index 340b9a10b..2dea8f10d 100644 --- a/src/views/editor/gutter.rs +++ b/src/views/editor/gutter.rs @@ -1,5 +1,4 @@ use crate::{ - Renderer, context::PaintCx, id::ViewId, peniko::kurbo::Point, diff --git a/src/views/editor/layout.rs b/src/views/editor/layout.rs index e9a0f55f3..48228bc9a 100644 --- a/src/views/editor/layout.rs +++ b/src/views/editor/layout.rs @@ -1,7 +1,5 @@ -use crate::{ - peniko::Color, - text::{LayoutLine, TextLayout}, -}; +use crate::{peniko::Color, text::TextLayout}; +use cosmic_text::LayoutLine; use floem_editor_core::buffer::rope_text::RopeText; use super::{phantom_text::PhantomTextLine, visual_line::TextLayoutProvider}; diff --git a/src/views/editor/mod.rs b/src/views/editor/mod.rs index cdcb77377..1386e0b6a 100644 --- a/src/views/editor/mod.rs +++ b/src/views/editor/mod.rs @@ -16,7 +16,7 @@ use crate::{ prop, prop_extractor, reactive::{ReadSignal, RwSignal, Scope, batch, untrack}, style::{CursorColor, StylePropValue, TextColor}, - text::{Attrs, AttrsList, LineHeightValue, TextLayout, Wrap}, + text::{Affinity, Attrs, AttrsList, LineHeightValue, TextLayout, Wrap}, view::{IntoView, View}, views::text, }; @@ -32,7 +32,6 @@ use floem_editor_core::{ word::WordCursor, }; use floem_reactive::{SignalGet, SignalTrack, SignalUpdate, SignalWith, Trigger}; -use floem_renderer::text::Affinity; use lapce_xi_rope::Rope; pub mod actions; diff --git a/src/views/editor/view.rs b/src/views/editor/view.rs index d56e85e9e..df925acaf 100644 --- a/src/views/editor/view.rs +++ b/src/views/editor/view.rs @@ -1,7 +1,6 @@ use std::{collections::HashMap, ops::RangeInclusive, rc::Rc}; use crate::{ - Renderer, action::{set_ime_allowed, set_ime_cursor_area}, context::{LayoutCx, PaintCx, UpdateCx}, event::{Event, EventListener, EventPropagation}, diff --git a/src/views/img.rs b/src/views/img.rs index e3105d23c..f880b09db 100644 --- a/src/views/img.rs +++ b/src/views/img.rs @@ -7,7 +7,7 @@ use peniko::{Blob, ImageAlphaType, ImageData}; use sha2::{Digest, Sha256}; use taffy::NodeId; -use crate::{Renderer, id::ViewId, style::Style, unit::UnitExt, view::View}; +use crate::{id::ViewId, style::Style, unit::UnitExt, view::View}; /// Holds information about image position and size inside container. pub struct ImageStyle { diff --git a/src/views/label.rs b/src/views/label.rs index 16f05e3aa..91500ccec 100644 --- a/src/views/label.rs +++ b/src/views/label.rs @@ -12,12 +12,11 @@ use crate::{ TextOverflowProp, }, style_class, - text::{Attrs, AttrsList, FamilyOwned, TextLayout}, + text::{Attrs, AttrsList, Cursor, FamilyOwned, TextLayout}, unit::PxPct, view::View, }; use floem_reactive::create_updater; -use floem_renderer::{Renderer, text::Cursor}; use peniko::{ Brush, color::palette, diff --git a/src/views/localization.rs b/src/views/localization.rs index 3bca70395..8af00b564 100644 --- a/src/views/localization.rs +++ b/src/views/localization.rs @@ -4,12 +4,15 @@ use std::borrow::Cow; use std::pin::Pin; use std::rc::Rc; -use crate::style::{CustomStylable, CustomStyle, Style, StylePropValue}; -use crate::view_state::{Stack, StackOffset}; -use crate::views::{Decorators, static_label}; -use crate::{AnyView, IntoView, View, ViewId, prop, prop_extractor, style_class}; +use crate::{ + AnyView, IntoView, View, ViewId, prop, prop_extractor, + style::{CustomStylable, CustomStyle, Style, StylePropValue}, + style_class, + text::{Align, Weight}, + view_state::{Stack, StackOffset}, + views::{Decorators, static_label}, +}; use floem_reactive::create_updater; -use floem_renderer::text::Align; use fluent_bundle::{FluentBundle, FluentResource}; pub use fluent_bundle::FluentArgs; @@ -65,10 +68,8 @@ impl StylePropValue for LocaleMap { let count = languages.len(); let view = stack(( - format!("Languages ({count})").style(|s| { - s.font_size(12.0) - .font_weight(floem_renderer::text::Weight::SEMIBOLD) - }), + format!("Languages ({count})") + .style(|s| s.font_size(12.0).font_weight(Weight::SEMIBOLD)), v_stack_from_iter(languages.into_iter().map(|lang| { lang.style(|s| { s.font_size(11.0) diff --git a/src/views/rich_text.rs b/src/views/rich_text.rs index ed91a8005..0855e4e54 100644 --- a/src/views/rich_text.rs +++ b/src/views/rich_text.rs @@ -1,10 +1,6 @@ use std::any::Any; use floem_reactive::create_effect; -use floem_renderer::{ - Renderer, - text::{Attrs, AttrsList, AttrsOwned, TextLayout}, -}; use peniko::{ Color, color::palette, @@ -18,6 +14,7 @@ use crate::{ context::UpdateCx, id::ViewId, style::{Style, TextOverflow}, + text::{Attrs, AttrsList, AttrsOwned, TextLayout}, unit::PxPct, view::View, }; diff --git a/src/views/scroll.rs b/src/views/scroll.rs index 5dcc224aa..c0272a7f3 100644 --- a/src/views/scroll.rs +++ b/src/views/scroll.rs @@ -11,7 +11,6 @@ use crate::style::{ }; use crate::unit::PxPct; use crate::{ - Renderer, context::{ComputeLayoutCx, PaintCx}, event::{Event, EventPropagation}, id::ViewId, diff --git a/src/views/slider.rs b/src/views/slider.rs index 83dd3e2af..46b8d40b7 100644 --- a/src/views/slider.rs +++ b/src/views/slider.rs @@ -13,7 +13,6 @@ use winit::keyboard::NamedKey; use crate::style::{BorderRadiusProp, CustomStyle}; use crate::unit::Pct; use crate::{ - Renderer, event::EventPropagation, id::ViewId, prop, prop_extractor, diff --git a/src/views/svg.rs b/src/views/svg.rs index 3581916e6..830cdc3e2 100644 --- a/src/views/svg.rs +++ b/src/views/svg.rs @@ -1,13 +1,10 @@ use floem_reactive::create_effect; -use floem_renderer::{ - Renderer, - usvg::{self, Tree}, -}; use peniko::{ Brush, GradientKind, LinearGradientPosition, kurbo::{Point, Size}, }; use sha2::{Digest, Sha256}; +use usvg::{self, Tree}; use crate::{ id::ViewId, @@ -194,7 +191,8 @@ impl View for Svg { } else { self.svg_style.text_color().map(Brush::Solid) }; - cx.draw_svg(crate::RendererSvg { tree, hash }, rect, color.as_ref()); + // TODO need to move svg caching to this module + // cx.draw_svg(crate::RendererSvg { tree, hash }, rect, color.as_ref()); } } } diff --git a/src/views/text_input.rs b/src/views/text_input.rs index 614a6aacd..a461d3a49 100644 --- a/src/views/text_input.rs +++ b/src/views/text_input.rs @@ -1,37 +1,43 @@ #![deny(missing_docs)] use crate::action::{exec_after, set_ime_allowed, set_ime_cursor_area}; -use crate::event::{EventListener, EventPropagation}; -use crate::id::ViewId; -use crate::reactive::{RwSignal, create_effect}; -use crate::style::{FontFamily, FontProps, PaddingProp, SelectionStyle, TextAlignProp}; -use crate::style::{FontStyle, FontWeight, TextColor}; -use crate::unit::{PxPct, PxPctAuto}; -use crate::views::editor::text::Preedit; -use crate::{Clipboard, prop_extractor, style_class}; +use crate::{ + Clipboard, + context::{EventCx, UpdateCx}, + event::{Event, EventListener, EventPropagation}, + id::ViewId, + peniko::color::palette, + prop_extractor, + reactive::{RwSignal, create_effect}, + style::{ + FontFamily, FontProps, FontStyle, FontWeight, PaddingProp, SelectionStyle, Style, + TextAlignProp, TextColor, + }, + style_class, + text::{Attrs, AttrsList, FamilyOwned, TextLayout}, + unit::{PxPct, PxPctAuto}, + view::View, + views::editor::text::Preedit, +}; + use floem_reactive::{SignalGet, SignalUpdate, SignalWith, create_rw_signal}; use taffy::prelude::{Layout, NodeId}; -use floem_renderer::Renderer; -use ui_events::keyboard::{Key, KeyState, KeyboardEvent, Modifiers, NamedKey}; -use ui_events::pointer::{PointerButton, PointerButtonEvent, PointerEvent}; +use ui_events::{ + keyboard::{Key, KeyState, KeyboardEvent, Modifiers, NamedKey}, + pointer::{PointerButton, PointerButtonEvent, PointerEvent}, +}; use unicode_segmentation::UnicodeSegmentation; -use crate::{peniko::color::palette, style::Style, view::View}; - use std::{any::Any, ops::Range}; -use crate::text::{Attrs, AttrsList, FamilyOwned, TextLayout}; #[cfg(not(target_arch = "wasm32"))] use std::time::{Duration, Instant}; #[cfg(target_arch = "wasm32")] use web_time::{Duration, Instant}; -use peniko::Brush; -use peniko::kurbo::{Point, Rect, Size}; - -use crate::{ - context::{EventCx, UpdateCx}, - event::Event, +use peniko::{ + Brush, + kurbo::{Point, Rect, Size}, }; use super::Decorators; diff --git a/src/views/toggle_button.rs b/src/views/toggle_button.rs index 93dd7831e..1d53de97e 100644 --- a/src/views/toggle_button.rs +++ b/src/views/toggle_button.rs @@ -10,7 +10,6 @@ use ui_events::pointer::PointerEvent; use winit::keyboard::NamedKey; use crate::{ - Renderer, event::EventPropagation, id::ViewId, prop, prop_extractor, diff --git a/src/window_handle.rs b/src/window_handle.rs index 1c85d4d7f..d9d914154 100644 --- a/src/window_handle.rs +++ b/src/window_handle.rs @@ -13,8 +13,6 @@ use winit::window::{ }; use floem_reactive::{RwSignal, Scope, SignalGet, SignalUpdate, with_scope}; -use floem_renderer::Renderer; -use floem_renderer::gpu_resources::GpuResources; use peniko::color::palette; use peniko::kurbo::{Affine, Point, Size}; use winit::{ @@ -24,6 +22,7 @@ use winit::{ window::{Window, WindowId}, }; +use crate::app_handle::GpuResources; use crate::dropped_file::FileDragEvent; #[cfg(any(target_os = "linux", target_os = "freebsd"))] use crate::menu::MudaMenu; @@ -663,18 +662,8 @@ impl WindowHandle { let mut cx = PaintCx { window_state: &mut self.window_state, paint_state: &mut self.paint_state, - transform: Affine::IDENTITY, - clip: None, - z_index: None, - saved_transforms: Vec::new(), - saved_clips: Vec::new(), - saved_z_indexes: Vec::new(), gpu_resources, window: self.window.clone(), - #[cfg(feature = "vello")] - saved_layer_counts: Vec::new(), - #[cfg(feature = "vello")] - layer_count: 0, }; cx.paint_state .renderer_mut() diff --git a/tiny_skia/Cargo.toml b/tiny_skia/Cargo.toml deleted file mode 100644 index 963011936..000000000 --- a/tiny_skia/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "floem_tiny_skia_renderer" -version.workspace = true -edition = "2021" -repository = "https://github.com/lapce/floem" -description = "A native Rust UI library with fine-grained reactivity" -license.workspace = true - -[dependencies] -peniko = { workspace = true } -resvg = { workspace = true } -raw-window-handle = { workspace = true } - -anyhow = "1.0.69" -floem_renderer = { path = "../renderer", version = "0.2.0" } -softbuffer = "0.4.1" diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs deleted file mode 100644 index 8945f2f4d..000000000 --- a/tiny_skia/src/lib.rs +++ /dev/null @@ -1,1008 +0,0 @@ -use anyhow::{anyhow, Result}; -use floem_renderer::swash::SwashScaler; -use floem_renderer::text::{CacheKey, LayoutRun, SwashContent}; -use floem_renderer::tiny_skia::{ - self, FillRule, FilterQuality, GradientStop, LinearGradient, Mask, MaskType, Paint, Path, - PathBuilder, Pattern, Pixmap, RadialGradient, Shader, SpreadMode, Stroke, Transform, -}; -use floem_renderer::Img; -use floem_renderer::Renderer; -use peniko::kurbo::{PathEl, Size}; -use peniko::{ - color::palette, - kurbo::{Affine, Point, Rect, Shape}, - BrushRef, Color, GradientKind, -}; -use peniko::{BlendMode, Compose, Mix, RadialGradientPosition}; -use resvg::tiny_skia::StrokeDash; -use softbuffer::{Context, Surface}; -use std::cell::RefCell; -use std::collections::HashMap; -use std::num::NonZeroU32; -use std::rc::Rc; -use tiny_skia::{LineCap, LineJoin}; - -thread_local! { - #[allow(clippy::type_complexity)] - static IMAGE_CACHE: RefCell, (CacheColor, Rc)>> = RefCell::new(HashMap::new()); - #[allow(clippy::type_complexity)] - // The `u32` is a color encoded as a u32 so that it is hashable and eq. - static GLYPH_CACHE: RefCell>)>> = RefCell::new(HashMap::new()); - static SWASH_SCALER: RefCell = RefCell::new(SwashScaler::default()); -} - -fn cache_glyph(cache_color: CacheColor, cache_key: CacheKey, color: Color) -> Option> { - let c = color.to_rgba8(); - - if let Some(opt_glyph) = GLYPH_CACHE.with_borrow_mut(|gc| { - if let Some((color, glyph)) = gc.get_mut(&(cache_key, c.to_u32())) { - *color = cache_color; - Some(glyph.clone()) - } else { - None - } - }) { - return opt_glyph; - }; - - let image = SWASH_SCALER.with_borrow_mut(|s| s.get_image(cache_key))?; - - let result = if image.placement.width == 0 || image.placement.height == 0 { - // We can't create an empty `Pixmap` - None - } else { - let mut pixmap = Pixmap::new(image.placement.width, image.placement.height)?; - - if image.content == SwashContent::Mask { - for (a, &alpha) in pixmap.pixels_mut().iter_mut().zip(image.data.iter()) { - *a = tiny_skia::Color::from_rgba8(c.r, c.g, c.b, alpha) - .premultiply() - .to_color_u8(); - } - } else if image.content == SwashContent::Color { - for (a, b) in pixmap.pixels_mut().iter_mut().zip(image.data.chunks(4)) { - *a = tiny_skia::Color::from_rgba8(b[0], b[1], b[2], b[3]) - .premultiply() - .to_color_u8(); - } - } else { - return None; - } - - Some(Rc::new(Glyph { - pixmap, - left: image.placement.left as f32, - top: image.placement.top as f32, - })) - }; - - GLYPH_CACHE - .with_borrow_mut(|gc| gc.insert((cache_key, c.to_u32()), (cache_color, result.clone()))); - - result -} - -macro_rules! try_ret { - ($e:expr) => { - if let Some(e) = $e { - e - } else { - return; - } - }; -} - -struct Glyph { - pixmap: Pixmap, - left: f32, - top: f32, -} - -#[derive(PartialEq, Clone, Copy)] -struct CacheColor(bool); - -struct Layer { - pixmap: Pixmap, - /// clip is stored with the transform at the time clip is called - clip: Option, - mask: Mask, - /// this transform should generally only be used when making a draw call to skia - transform: Affine, - // the transform that the layer was pushed with that will be used when applying the layer - combine_transform: Affine, - blend_mode: BlendMode, - alpha: f32, - window_scale: f64, - cache_color: CacheColor, -} -impl Layer { - /// the img_rect should already be in the correct transformed space along with the window_scale applied - fn clip_rect(&self, img_rect: Rect) -> Option { - if let Some(clip) = self.clip { - let clip = clip.intersect(img_rect); - to_skia_rect(clip) - } else { - to_skia_rect(img_rect) - } - } - - /// Renders the pixmap at the position and transforms it with the given transform. - /// x and y should have already been scaled by the window scale - fn render_pixmap_direct(&mut self, img_pixmap: &Pixmap, x: f32, y: f32, transform: Affine) { - let img_rect = Rect::from_origin_size( - (x, y), - (img_pixmap.width() as f64, img_pixmap.height() as f64), - ); - let paint = Paint { - shader: Pattern::new( - img_pixmap.as_ref(), - SpreadMode::Pad, - FilterQuality::Nearest, - 1.0, - Transform::from_translate(x, y), - ), - ..Default::default() - }; - - let transform = transform.as_coeffs(); - let transform = Transform::from_row( - transform[0] as f32, - transform[1] as f32, - transform[2] as f32, - transform[3] as f32, - transform[4] as f32, - transform[5] as f32, - ); - if let Some(rect) = self.clip_rect(img_rect) { - self.pixmap.fill_rect(rect, &paint, transform, None); - } - } - - fn render_pixmap_rect(&mut self, pixmap: &Pixmap, rect: tiny_skia::Rect) { - let paint = Paint { - shader: Pattern::new( - pixmap.as_ref(), - SpreadMode::Pad, - FilterQuality::Bilinear, - 1.0, - Transform::from_scale( - rect.width() / pixmap.width() as f32, - rect.height() / pixmap.height() as f32, - ), - ), - ..Default::default() - }; - - self.pixmap.fill_rect( - rect, - &paint, - self.skia_transform(), - self.clip.is_some().then_some(&self.mask), - ); - } - - fn render_pixmap_with_paint( - &mut self, - pixmap: &Pixmap, - rect: tiny_skia::Rect, - paint: Option>, - ) { - let paint = if let Some(paint) = paint { - paint - } else { - return self.render_pixmap_rect(pixmap, rect); - }; - - let mut colored_bg = try_ret!(Pixmap::new(pixmap.width(), pixmap.height())); - colored_bg.fill_rect( - try_ret!(tiny_skia::Rect::from_xywh( - 0.0, - 0.0, - pixmap.width() as f32, - pixmap.height() as f32 - )), - &paint, - Transform::identity(), - None, - ); - - let mask = Mask::from_pixmap(pixmap.as_ref(), MaskType::Alpha); - colored_bg.apply_mask(&mask); - - self.render_pixmap_rect(&colored_bg, rect); - } - - fn skia_transform(&self) -> Transform { - skia_transform(self.transform, self.window_scale as f32) - } -} -impl Layer { - /// The combine transform should be the transform that the layer is pushed with without combining with the previous transform. It will be used when combining layers to offset/transform this layer into the parent with the parent transform - fn new( - blend: impl Into, - alpha: f32, - combine_transform: Affine, - clip: &impl Shape, - window_scale: f64, - cache_color: CacheColor, - ) -> Result { - let transform = Affine::IDENTITY; - let bbox = clip.bounding_box(); - let scaled_box = Affine::scale(window_scale).transform_rect_bbox(bbox); - let width = scaled_box.width() as u32; - let height = scaled_box.height() as u32; - let mut mask = Mask::new(width, height).ok_or_else(|| anyhow!("unable to create mask"))?; - mask.fill_path( - &shape_to_path(clip).ok_or_else(|| anyhow!("unable to create clip shape"))?, - FillRule::Winding, - false, - Transform::from_scale(window_scale as f32, window_scale as f32), - ); - Ok(Self { - pixmap: Pixmap::new(width, height).ok_or_else(|| anyhow!("unable to create pixmap"))?, - mask, - clip: Some(bbox), - transform, - combine_transform, - blend_mode: blend.into(), - alpha, - window_scale, - cache_color, - }) - } - - fn transform(&mut self, transform: Affine) { - self.transform *= transform; - } - - fn clip(&mut self, shape: &impl Shape) { - self.clip = Some( - self.transform - .then_scale(self.window_scale) - .transform_rect_bbox(shape.bounding_box()), - ); - let path = try_ret!(shape_to_path(shape)); - self.mask.clear(); - self.mask - .fill_path(&path, FillRule::Winding, false, self.skia_transform()); - } - - fn clear_clip(&mut self) { - self.clip = None; - } - - fn stroke<'b, 's>( - &mut self, - shape: &impl Shape, - brush: impl Into>, - stroke: &'s peniko::kurbo::Stroke, - ) { - let paint = try_ret!(brush_to_paint(brush)); - let path = try_ret!(shape_to_path(shape)); - let line_cap = match stroke.end_cap { - peniko::kurbo::Cap::Butt => LineCap::Butt, - peniko::kurbo::Cap::Square => LineCap::Square, - peniko::kurbo::Cap::Round => LineCap::Round, - }; - let line_join = match stroke.join { - peniko::kurbo::Join::Bevel => LineJoin::Bevel, - peniko::kurbo::Join::Miter => LineJoin::Miter, - peniko::kurbo::Join::Round => LineJoin::Round, - }; - let stroke = Stroke { - width: stroke.width as f32, - miter_limit: stroke.miter_limit as f32, - line_cap, - line_join, - dash: (!stroke.dash_pattern.is_empty()) - .then_some(StrokeDash::new( - stroke.dash_pattern.iter().map(|v| *v as f32).collect(), - stroke.dash_offset as f32, - )) - .flatten(), - }; - self.pixmap.stroke_path( - &path, - &paint, - &stroke, - self.skia_transform(), - self.clip.is_some().then_some(&self.mask), - ); - } - - fn fill<'b>(&mut self, shape: &impl Shape, brush: impl Into>, _blur_radius: f64) { - // FIXME: Handle _blur_radius - - let paint = try_ret!(brush_to_paint(brush)); - if let Some(rect) = shape.as_rect() { - let rect = try_ret!(to_skia_rect(rect)); - self.pixmap.fill_rect( - rect, - &paint, - self.skia_transform(), - self.clip.is_some().then_some(&self.mask), - ); - } else { - let path = try_ret!(shape_to_path(shape)); - self.pixmap.fill_path( - &path, - &paint, - FillRule::Winding, - self.skia_transform(), - self.clip.is_some().then_some(&self.mask), - ); - } - } - - fn draw_text_with_layout<'b>( - &mut self, - layout: impl Iterator>, - pos: impl Into, - ) { - // this pos is relative to the current transform, but not the current window scale. - // That is why we remove the window scale from the clip below - let pos: Point = pos.into(); - let clip = self.clip; - let undo_transform = |r| { - Affine::scale(self.window_scale) - .inverse() - .transform_rect_bbox(r) - }; - let scaled_clip = clip.map(undo_transform); - - // we manually handle the offset so that the glyph_x and y can be scaled by the window_scale - let offset = self.transform.translation(); - let transform = self.transform * Affine::translate((-offset.x, -offset.y)); - - for line in layout { - if let Some(rect) = scaled_clip { - let y = pos.y + offset.y + line.line_y as f64; - if y + (line.line_height as f64) < rect.y0 { - continue; - } - if y - (line.line_height as f64) > rect.y1 { - break; - } - } - 'line_loop: for glyph_run in line.glyphs { - let x = glyph_run.x + pos.x as f32 + offset.x as f32; - let y = line.line_y + pos.y as f32 + offset.y as f32; - if let Some(rect) = scaled_clip { - if ((x + glyph_run.w) as f64) < rect.x0 { - continue; - } else if x as f64 > rect.x1 { - break 'line_loop; - } - } - - let glyph_x = x * self.window_scale as f32; - let glyph_y = y * self.window_scale as f32; - let font_size = glyph_run.font_size * self.window_scale as f32; - - let (cache_key, new_x, new_y) = CacheKey::new( - glyph_run.font_id, - glyph_run.glyph_id, - font_size, - (glyph_x, glyph_y), - glyph_run.cache_key_flags, - ); - - let color = glyph_run.color_opt.map_or(palette::css::BLACK, |c| { - Color::from_rgba8(c.r(), c.g(), c.b(), c.a()) - }); - - let glyph = cache_glyph(self.cache_color, cache_key, color); - if let Some(glyph) = glyph { - self.render_pixmap_direct( - &glyph.pixmap, - new_x as f32 + glyph.left, - new_y as f32 - glyph.top, - transform, - ); - } - } - } - } - - fn draw_svg<'b>( - &mut self, - svg: floem_renderer::Svg<'b>, - rect: Rect, - brush: Option>>, - ) { - let width = (rect.width() * self.window_scale).round() as u32; - let height = (rect.height() * self.window_scale).round() as u32; - - let rect = try_ret!(to_skia_rect(rect)); - - let paint = brush.and_then(|brush| brush_to_paint(brush)); - - if IMAGE_CACHE.with_borrow_mut(|ic| { - if let Some((color, non_colored_svg_pixmap)) = ic.get_mut(svg.hash) { - *color = self.cache_color; - let pixmap = non_colored_svg_pixmap.clone(); - self.render_pixmap_with_paint(&pixmap, rect, paint.clone()); - // return - true - } else { - // continue - false - } - }) { - return; - }; - - let mut non_colored_svg = try_ret!(tiny_skia::Pixmap::new(width, height)); - let svg_transform = tiny_skia::Transform::from_scale( - width as f32 / svg.tree.size().width(), - height as f32 / svg.tree.size().height(), - ); - resvg::render(svg.tree, svg_transform, &mut non_colored_svg.as_mut()); - - self.render_pixmap_with_paint(&non_colored_svg, rect, paint); - - IMAGE_CACHE.with_borrow_mut(|ic| { - ic.insert( - svg.hash.to_owned(), - (self.cache_color, Rc::new(non_colored_svg)), - ) - }); - } - - fn draw_img(&mut self, img: Img<'_>, rect: Rect) { - let rect = try_ret!(to_skia_rect(rect)); - if IMAGE_CACHE.with_borrow_mut(|ic| { - if let Some((color, pixmap)) = ic.get_mut(img.hash) { - *color = self.cache_color; - let pixmap = pixmap.clone(); - self.render_pixmap_rect(&pixmap, rect); - // return - true - } else { - // continue - false - } - }) { - return; - }; - - let image_data = img.img.image.data.data(); - let mut pixmap = try_ret!(Pixmap::new(img.img.image.width, img.img.image.height)); - for (a, b) in pixmap - .pixels_mut() - .iter_mut() - .zip(image_data.chunks_exact(4)) - { - *a = tiny_skia::Color::from_rgba8(b[0], b[1], b[2], b[3]) - .premultiply() - .to_color_u8(); - } - - self.render_pixmap_rect(&pixmap, rect); - - IMAGE_CACHE.with_borrow_mut(|ic| { - ic.insert(img.hash.to_owned(), (self.cache_color, Rc::new(pixmap))) - }); - } -} - -pub struct TinySkiaRenderer { - #[allow(unused)] - context: Context, - surface: Surface, - cache_color: CacheColor, - transform: Affine, - window_scale: f64, - layers: Vec, -} - -impl - TinySkiaRenderer -{ - pub fn new(window: W, width: u32, height: u32, scale: f64, font_embolden: f32) -> Result - where - W: Clone, - { - let context = Context::new(window.clone()) - .map_err(|err| anyhow!("unable to create context: {}", err))?; - let mut surface = Surface::new(&context, window) - .map_err(|err| anyhow!("unable to create surface: {}", err))?; - surface - .resize( - NonZeroU32::new(width).unwrap_or(NonZeroU32::new(1).unwrap()), - NonZeroU32::new(height).unwrap_or(NonZeroU32::new(1).unwrap()), - ) - .map_err(|_| anyhow!("failed to resize surface"))?; - - let pixmap = - Pixmap::new(width, height).ok_or_else(|| anyhow!("unable to create pixmap"))?; - - let mask = Mask::new(width, height).ok_or_else(|| anyhow!("unable to create mask"))?; - - // this is fine to modify the embolden here but it shouldn't be modified any other time - SWASH_SCALER.with_borrow_mut(|s| s.font_embolden = font_embolden); - - let main_layer = Layer { - pixmap, - mask, - clip: None, - alpha: 1., - transform: Affine::IDENTITY, - combine_transform: Affine::IDENTITY, - blend_mode: Mix::Normal.into(), - window_scale: scale, - cache_color: CacheColor(false), - }; - Ok(Self { - context, - surface, - transform: Affine::IDENTITY, - window_scale: scale, - cache_color: CacheColor(false), - layers: vec![main_layer], - }) - } - - pub fn resize(&mut self, width: u32, height: u32, scale: f64) { - if width != self.layers[0].pixmap.width() || height != self.layers[0].pixmap.width() { - self.surface - .resize( - NonZeroU32::new(width).unwrap_or(NonZeroU32::new(1).unwrap()), - NonZeroU32::new(height).unwrap_or(NonZeroU32::new(1).unwrap()), - ) - .expect("failed to resize surface"); - self.layers[0].pixmap = Pixmap::new(width, height).expect("unable to create pixmap"); - self.layers[0].mask = Mask::new(width, height).expect("unable to create mask"); - } - self.layers[0].window_scale = scale; - self.window_scale = scale; - } - - pub fn set_scale(&mut self, scale: f64) { - self.layers[0].window_scale = scale; - self.window_scale = scale; - } - - pub fn scale(&self) -> f64 { - self.window_scale - } - - pub fn size(&self) -> Size { - Size::new( - self.layers[0].pixmap.width() as f64, - self.layers[0].pixmap.height() as f64, - ) - } -} - -fn to_color(color: Color) -> tiny_skia::Color { - let c = color.to_rgba8(); - tiny_skia::Color::from_rgba8(c.r, c.g, c.b, c.a) -} - -fn to_point(point: Point) -> tiny_skia::Point { - tiny_skia::Point::from_xy(point.x as f32, point.y as f32) -} - -impl Renderer - for TinySkiaRenderer -{ - fn begin(&mut self, _capture: bool) { - assert!(self.layers.len() == 1); - let first_layer = self.layers.last_mut().unwrap(); - first_layer.pixmap.fill(tiny_skia::Color::WHITE); - first_layer.clip = None; - first_layer.transform = Affine::IDENTITY; - } - - fn stroke<'b, 's>( - &mut self, - shape: &impl Shape, - brush: impl Into>, - stroke: &'s peniko::kurbo::Stroke, - ) { - self.layers.last_mut().unwrap().stroke(shape, brush, stroke); - } - - fn fill<'b>(&mut self, shape: &impl Shape, brush: impl Into>, blur_radius: f64) { - self.layers - .last_mut() - .unwrap() - .fill(shape, brush, blur_radius); - } - - fn draw_text_with_layout<'b>( - &mut self, - layout: impl Iterator>, - pos: impl Into, - ) { - self.layers - .last_mut() - .unwrap() - .draw_text_with_layout(layout, pos); - } - - fn draw_img(&mut self, img: Img<'_>, rect: Rect) { - self.layers.last_mut().unwrap().draw_img(img, rect); - } - - fn draw_svg<'b>( - &mut self, - svg: floem_renderer::Svg<'b>, - rect: Rect, - brush: Option>>, - ) { - self.layers.last_mut().unwrap().draw_svg(svg, rect, brush); - } - - fn set_transform(&mut self, cumulative_transform: Affine) { - let uncombined = self.transform.inverse() * cumulative_transform; - self.transform = cumulative_transform; - self.layers.last_mut().unwrap().transform(uncombined); - } - - fn set_z_index(&mut self, _z_index: i32) { - // FIXME: Remove this method? - } - - fn clip(&mut self, shape: &impl Shape) { - self.layers.iter_mut().for_each(|l| l.clip(shape)); - } - - fn clear_clip(&mut self) { - self.layers.iter_mut().for_each(|l| l.clear_clip()); - } - - fn finish(&mut self) -> Option { - // Remove cache entries which were not accessed. - IMAGE_CACHE.with_borrow_mut(|ic| ic.retain(|_, (c, _)| *c == self.cache_color)); - GLYPH_CACHE.with_borrow_mut(|gc| gc.retain(|_, (c, _)| *c == self.cache_color)); - - // Swap the cache color. - self.cache_color = CacheColor(!self.cache_color.0); - - let mut buffer = self - .surface - .buffer_mut() - .expect("failed to get the surface buffer"); - - // Copy from `tiny_skia::Pixmap` to the format specified by `softbuffer::Buffer`. - for (out_pixel, pixel) in - (buffer.iter_mut()).zip(self.layers.last().unwrap().pixmap.pixels().iter()) - { - *out_pixel = ((pixel.red() as u32) << 16) - | ((pixel.green() as u32) << 8) - | (pixel.blue() as u32); - } - - buffer - .present() - .expect("failed to present the surface buffer"); - - None - } - - fn push_layer( - &mut self, - blend: impl Into, - alpha: f32, - transform: Affine, - clip: &impl Shape, - ) { - if let Ok(res) = Layer::new( - blend, - alpha, - transform, - clip, - self.window_scale, - self.cache_color, - ) { - self.layers.push(res); - } - } - - fn pop_layer(&mut self) { - if self.layers.len() <= 1 { - // Don't pop the main layer - return; - } - - let layer = self.layers.pop().unwrap(); - let parent = self.layers.last_mut().unwrap(); - - apply_layer(&layer, parent); - } - - fn debug_info(&self) -> String { - "name: tiny_skia".into() - } -} - -fn shape_to_path(shape: &impl Shape) -> Option { - let mut builder = PathBuilder::new(); - for element in shape.path_elements(0.1) { - match element { - PathEl::ClosePath => builder.close(), - PathEl::MoveTo(p) => builder.move_to(p.x as f32, p.y as f32), - PathEl::LineTo(p) => builder.line_to(p.x as f32, p.y as f32), - PathEl::QuadTo(p1, p2) => { - builder.quad_to(p1.x as f32, p1.y as f32, p2.x as f32, p2.y as f32) - } - PathEl::CurveTo(p1, p2, p3) => builder.cubic_to( - p1.x as f32, - p1.y as f32, - p2.x as f32, - p2.y as f32, - p3.x as f32, - p3.y as f32, - ), - } - } - builder.finish() -} - -fn brush_to_paint<'b>(brush: impl Into>) -> Option> { - let shader = match brush.into() { - BrushRef::Solid(c) => Shader::SolidColor(to_color(c)), - BrushRef::Gradient(g) => { - let stops = g - .stops - .iter() - .map(|s| GradientStop::new(s.offset, to_color(s.color.to_alpha_color()))) - .collect(); - match g.kind { - GradientKind::Linear(linear) => LinearGradient::new( - to_point(linear.start), - to_point(linear.end), - stops, - SpreadMode::Pad, - Transform::identity(), - )?, - GradientKind::Radial(RadialGradientPosition { - start_center, - start_radius: _, - end_center, - end_radius, - }) => { - // FIXME: Doesn't use `start_radius` - RadialGradient::new( - to_point(start_center), - to_point(end_center), - end_radius, - stops, - SpreadMode::Pad, - Transform::identity(), - )? - } - GradientKind::Sweep { .. } => return None, - } - } - BrushRef::Image(_) => return None, - }; - Some(Paint { - shader, - ..Default::default() - }) -} - -fn to_skia_rect(rect: Rect) -> Option { - tiny_skia::Rect::from_ltrb( - rect.x0 as f32, - rect.y0 as f32, - rect.x1 as f32, - rect.y1 as f32, - ) -} - -type TinyBlendMode = tiny_skia::BlendMode; - -enum BlendStrategy { - /// Can be directly mapped to a tiny-skia blend mode - SinglePass(TinyBlendMode), - /// Requires multiple operations - MultiPass { - first_pass: TinyBlendMode, - second_pass: TinyBlendMode, - }, -} - -fn determine_blend_strategy(peniko_mode: &BlendMode) -> BlendStrategy { - match (peniko_mode.mix, peniko_mode.compose) { - (Mix::Normal, compose) => BlendStrategy::SinglePass(compose_to_tiny_blend_mode(compose)), - #[allow(deprecated, reason = "n/a")] - (Mix::Clip, compose) => BlendStrategy::MultiPass { - first_pass: compose_to_tiny_blend_mode(compose), - second_pass: TinyBlendMode::Source, - }, - - (mix, Compose::SrcOver) => BlendStrategy::SinglePass(mix_to_tiny_blend_mode(mix)), - - (mix, compose) => BlendStrategy::MultiPass { - first_pass: compose_to_tiny_blend_mode(compose), - second_pass: mix_to_tiny_blend_mode(mix), - }, - } -} - -fn compose_to_tiny_blend_mode(compose: Compose) -> TinyBlendMode { - match compose { - Compose::Clear => TinyBlendMode::Clear, - Compose::Copy => TinyBlendMode::Source, - Compose::Dest => TinyBlendMode::Destination, - Compose::SrcOver => TinyBlendMode::SourceOver, - Compose::DestOver => TinyBlendMode::DestinationOver, - Compose::SrcIn => TinyBlendMode::SourceIn, - Compose::DestIn => TinyBlendMode::DestinationIn, - Compose::SrcOut => TinyBlendMode::SourceOut, - Compose::DestOut => TinyBlendMode::DestinationOut, - Compose::SrcAtop => TinyBlendMode::SourceAtop, - Compose::DestAtop => TinyBlendMode::DestinationAtop, - Compose::Xor => TinyBlendMode::Xor, - Compose::Plus => TinyBlendMode::Plus, - Compose::PlusLighter => TinyBlendMode::Plus, // ?? - } -} - -fn mix_to_tiny_blend_mode(mix: Mix) -> TinyBlendMode { - match mix { - Mix::Normal => TinyBlendMode::SourceOver, - Mix::Multiply => TinyBlendMode::Multiply, - Mix::Screen => TinyBlendMode::Screen, - Mix::Overlay => TinyBlendMode::Overlay, - Mix::Darken => TinyBlendMode::Darken, - Mix::Lighten => TinyBlendMode::Lighten, - Mix::ColorDodge => TinyBlendMode::ColorDodge, - Mix::ColorBurn => TinyBlendMode::ColorBurn, - Mix::HardLight => TinyBlendMode::HardLight, - Mix::SoftLight => TinyBlendMode::SoftLight, - Mix::Difference => TinyBlendMode::Difference, - Mix::Exclusion => TinyBlendMode::Exclusion, - Mix::Hue => TinyBlendMode::Hue, - Mix::Saturation => TinyBlendMode::Saturation, - Mix::Color => TinyBlendMode::Color, - Mix::Luminosity => TinyBlendMode::Luminosity, - #[allow(deprecated, reason = "n/a")] - Mix::Clip => TinyBlendMode::SourceOver, - } -} - -fn apply_layer(layer: &Layer, parent: &mut Layer) { - match determine_blend_strategy(&layer.blend_mode) { - BlendStrategy::SinglePass(blend_mode) => { - let mut paint = Paint { - blend_mode, - anti_alias: true, - ..Default::default() - }; - - let transform = skia_transform_with_scaled_translation( - parent.transform * layer.combine_transform, - layer.window_scale as f32, - 1., - ); - - let layer_pattern = Pattern::new( - layer.pixmap.as_ref(), - SpreadMode::Pad, - FilterQuality::Bilinear, - layer.alpha, - Transform::identity(), - ); - - paint.shader = layer_pattern; - - let layer_rect = try_ret!(tiny_skia::Rect::from_xywh( - 0.0, - 0.0, - layer.pixmap.width() as f32, - layer.pixmap.height() as f32, - )); - - parent.pixmap.fill_rect( - layer_rect, - &paint, - transform, - parent.clip.is_some().then_some(&parent.mask), - ); - } - BlendStrategy::MultiPass { - first_pass, - second_pass, - } => { - let original_parent = parent.pixmap.clone(); - - let mut paint = Paint { - blend_mode: first_pass, - anti_alias: true, - ..Default::default() - }; - - let transform = skia_transform_with_scaled_translation( - parent.transform * layer.combine_transform, - layer.window_scale as f32, - 1., - ); - let layer_pattern = Pattern::new( - layer.pixmap.as_ref(), - SpreadMode::Pad, - FilterQuality::Bilinear, - 1.0, - Transform::identity(), - ); - - paint.shader = layer_pattern; - - let layer_rect = try_ret!(tiny_skia::Rect::from_xywh( - 0.0, - 0.0, - layer.pixmap.width() as f32, - layer.pixmap.height() as f32, - )); - - parent.pixmap.fill_rect( - layer_rect, - &paint, - transform, - parent.clip.is_some().then_some(&parent.mask), - ); - - let intermediate = parent.pixmap.clone(); - - parent.pixmap = original_parent; - - let mut paint = Paint { - blend_mode: second_pass, - anti_alias: true, - ..Default::default() - }; - - let intermediate_pattern = Pattern::new( - intermediate.as_ref(), - SpreadMode::Pad, - FilterQuality::Bilinear, - 1.0, - Transform::identity(), - ); - - paint.shader = intermediate_pattern; - - parent.pixmap.fill_rect( - layer_rect, - &paint, - transform, - parent.clip.is_some().then_some(&parent.mask), - ) - } - } - parent.transform *= layer.transform; -} - -fn skia_transform(affine: Affine, window_scale: f32) -> Transform { - let transform = affine.as_coeffs(); - Transform::from_row( - transform[0] as f32, - transform[1] as f32, - transform[2] as f32, - transform[3] as f32, - transform[4] as f32, - transform[5] as f32, - ) - .post_scale(window_scale, window_scale) -} - -fn skia_transform_with_scaled_translation( - affine: Affine, - translation_scale: f32, - render_scale: f32, -) -> Transform { - let transform = affine.as_coeffs(); - Transform::from_row( - transform[0] as f32, - transform[1] as f32, - transform[2] as f32, - transform[3] as f32, - transform[4] as f32 * translation_scale, - transform[5] as f32 * translation_scale, - ) - .post_scale(render_scale, render_scale) -} diff --git a/vello/Cargo.toml b/vello/Cargo.toml deleted file mode 100644 index 208a98aad..000000000 --- a/vello/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "floem_vello_renderer" -version.workspace = true -edition = "2021" -repository = "https://github.com/lapce/floem" -description = "A native Rust UI library with fine-grained reactivity" -license.workspace = true - -[dependencies] -peniko = { workspace = true } -wgpu = { workspace = true } - -anyhow = "1.0" -vello = "0.6.0" -vello_svg = "0.8.0" -floem_renderer = { path = "../renderer", version = "0.2.0" } diff --git a/vello/src/lib.rs b/vello/src/lib.rs deleted file mode 100644 index 2267cf485..000000000 --- a/vello/src/lib.rs +++ /dev/null @@ -1,694 +0,0 @@ -use std::collections::HashMap; -use std::mem; -use std::num::NonZero; -use std::sync::mpsc::sync_channel; -use std::sync::Arc; - -use anyhow::Result; -use floem_renderer::gpu_resources::GpuResources; -use floem_renderer::text::fontdb::ID; -use floem_renderer::text::{LayoutGlyph, LayoutRun, FONT_SYSTEM}; -use floem_renderer::{Img, Renderer}; -use peniko::kurbo::Size; -use peniko::{ - color::palette, - kurbo::{Affine, Point, Rect, Shape}, - Blob, BrushRef, Color, -}; -use peniko::{Compose, Fill, ImageAlphaType, ImageData, Mix}; -use vello::kurbo::Stroke; -use vello::util::RenderSurface; -use vello::wgpu::Device; -use vello::{AaConfig, RendererOptions, Scene}; -use wgpu::util::TextureBlitter; -use wgpu::{Adapter, DeviceType, Queue, TextureAspect, TextureFormat}; - -pub struct VelloRenderer { - device: Device, - #[allow(unused)] - queue: Queue, - surface: RenderSurface<'static>, - renderer: vello::Renderer, - scene: Scene, - alt_scene: Option, - window_scale: f64, - transform: Affine, - capture: bool, - font_cache: HashMap, - adapter: Adapter, -} - -impl VelloRenderer { - pub fn new( - gpu_resources: GpuResources, - surface: wgpu::Surface<'static>, - width: u32, - height: u32, - scale: f64, - _font_embolden: f32, - ) -> Result { - let GpuResources { - adapter, - device, - queue, - .. - } = gpu_resources; - - if adapter.get_info().device_type == DeviceType::Cpu { - return Err(anyhow::anyhow!("only cpu adapter found")); - } - - let mut required_downlevel_flags = wgpu::DownlevelFlags::empty(); - required_downlevel_flags.set(wgpu::DownlevelFlags::VERTEX_STORAGE, true); - - if !adapter - .get_downlevel_capabilities() - .flags - .contains(required_downlevel_flags) - { - return Err(anyhow::anyhow!( - "adapter doesn't support required downlevel flags" - )); - } - - let surface_caps = surface.get_capabilities(&adapter); - let texture_format = surface_caps - .formats - .into_iter() - .find(|it| matches!(it, TextureFormat::Rgba8Unorm | TextureFormat::Bgra8Unorm)) - .ok_or_else(|| anyhow::anyhow!("surface should support Rgba8Unorm or Bgra8Unorm"))?; - - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: texture_format, - width, - height, - present_mode: wgpu::PresentMode::AutoVsync, - alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![], - desired_maximum_frame_latency: 1, - }; - - surface.configure(&device, &config); - - let target_texture = device.create_texture(&wgpu::TextureDescriptor { - label: None, - size: wgpu::Extent3d { - width, - height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING, - format: TextureFormat::Rgba8Unorm, - view_formats: &[], - }); - - let target_view = target_texture.create_view(&wgpu::TextureViewDescriptor::default()); - - let render_surface = RenderSurface { - surface, - config, - dev_id: 0, - format: texture_format, - target_texture, - target_view, - blitter: TextureBlitter::new(&device, texture_format), - }; - - let scene = Scene::new(); - let renderer = vello::Renderer::new( - &device, - RendererOptions { - pipeline_cache: None, - use_cpu: false, - antialiasing_support: vello::AaSupport::all(), - num_init_threads: Some(NonZero::new(1).unwrap()), - }, - ) - .unwrap(); - - Ok(Self { - device, - queue, - surface: render_surface, - renderer, - scene, - alt_scene: None, - window_scale: scale, - transform: Affine::IDENTITY, - capture: false, - font_cache: HashMap::new(), - adapter, - }) - } - - pub fn resize(&mut self, width: u32, height: u32, scale: f64) { - if width != self.surface.config.width || height != self.surface.config.height { - let target_texture = self.device.create_texture(&wgpu::TextureDescriptor { - label: None, - size: wgpu::Extent3d { - width, - height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING, - format: TextureFormat::Rgba8Unorm, - view_formats: &[], - }); - let target_view = target_texture.create_view(&wgpu::TextureViewDescriptor::default()); - self.surface.target_texture = target_texture; - self.surface.target_view = target_view; - self.surface.config.width = width; - self.surface.config.height = height; - self.surface - .surface - .configure(&self.device, &self.surface.config); - } - self.window_scale = scale; - } - - pub const fn set_scale(&mut self, scale: f64) { - self.window_scale = scale; - } - - pub const fn scale(&self) -> f64 { - self.window_scale - } - - pub const fn size(&self) -> Size { - Size::new( - self.surface.config.width as f64, - self.surface.config.height as f64, - ) - } -} - -impl Renderer for VelloRenderer { - fn begin(&mut self, capture: bool) { - if self.capture == capture { - self.scene.reset(); - } else { - self.capture = capture; - if self.alt_scene.is_none() { - self.alt_scene = Some(Scene::new()); - } - if let Some(scene) = self.alt_scene.as_mut() { - scene.reset(); - } - self.scene.reset(); - mem::swap(&mut self.scene, self.alt_scene.as_mut().unwrap()); - } - self.transform = Affine::IDENTITY; - } - - fn stroke<'b, 's>( - &mut self, - shape: &impl Shape, - brush: impl Into>, - stroke: &'s Stroke, - ) { - self.scene.stroke( - stroke, - self.transform.then_scale(self.window_scale), - brush, - None, - shape, - ); - } - - fn fill<'b>(&mut self, path: &impl Shape, brush: impl Into>, blur_radius: f64) { - let brush: BrushRef<'b> = brush.into(); - - // For solid colors with specific shapes, use optimized methods - if blur_radius > 0.0 { - if let BrushRef::Solid(color) = brush { - if let Some(rounded) = path.as_rounded_rect() { - if rounded.radii().top_left == rounded.radii().top_right - && rounded.radii().top_left == rounded.radii().bottom_left - && rounded.radii().top_left == rounded.radii().bottom_right - { - let rect_radius = rounded.radii().top_left; - let rect = rounded.rect(); - self.scene.draw_blurred_rounded_rect( - self.transform.then_scale(self.window_scale), - rect, - color, - rect_radius, - blur_radius, - ); - return; - } - } else if let Some(rect) = path.as_rect() { - self.scene.draw_blurred_rounded_rect( - self.transform.then_scale(self.window_scale), - rect, - color, - 0., - blur_radius, - ); - return; - } - } - } - - self.scene.fill( - vello::peniko::Fill::NonZero, - self.transform.then_scale(self.window_scale), - brush, - None, - path, - ); - } - - fn push_layer( - &mut self, - blend: impl Into, - alpha: f32, - transform: Affine, - clip: &impl Shape, - ) { - self.scene.push_layer( - blend, - alpha, - self.transform.then_scale(self.window_scale) * transform, - clip, - ); - } - - fn pop_layer(&mut self) { - self.scene.pop_layer(); - } - - fn draw_text_with_layout<'b>( - &mut self, - layout: impl Iterator>, - pos: impl Into, - ) { - let pos: Point = pos.into(); - let transform = self - .transform - .pre_translate((pos.x, pos.y).into()) - .then_scale(self.window_scale); - - for line in layout { - let mut current_run: Option = None; - - for glyph in line.glyphs { - let color = glyph.color_opt.map_or(palette::css::BLACK, |c| { - Color::from_rgba8(c.r(), c.g(), c.b(), c.a()) - }); - let font_size = glyph.font_size; - let font_id = glyph.font_id; - let metadata = glyph.metadata; - - if current_run.as_ref().is_none_or(|run| { - run.color != color - || run.font_size != font_size - || run.font_id != font_id - || run.metadata != metadata - }) { - if let Some(run) = current_run.take() { - self.draw_glyph_run( - run, - transform.pre_translate((0., line.line_y.into()).into()), - ); - } - current_run = Some(GlyphRun { - color, - font_size, - font_id, - metadata, - glyphs: Vec::new(), - }); - } - - if let Some(run) = &mut current_run { - run.glyphs.push(glyph); - } - } - - if let Some(run) = current_run.take() { - self.draw_glyph_run( - run, - transform.pre_translate((0., line.line_y.into()).into()), - ); - } - } - } - - fn draw_img(&mut self, img: Img<'_>, rect: Rect) { - let rect_width = rect.width().max(1.); - let rect_height = rect.height().max(1.); - - let scale_x = rect_width / img.img.image.width as f64; - let scale_y = rect_height / img.img.image.height as f64; - - let translate_x = rect.min_x(); - let translate_y = rect.min_y(); - - self.scene.draw_image( - &img.img, - self.transform - .pre_scale_non_uniform(scale_x, scale_y) - .then_translate((translate_x, translate_y).into()) - .then_scale(self.window_scale), - ); - } - - fn draw_svg<'b>( - &mut self, - svg: floem_renderer::Svg<'b>, - rect: Rect, - brush: Option>>, - ) { - let rect_width = rect.width().max(1.); - let rect_height = rect.height().max(1.); - - let svg_size = svg.tree.size(); - - let scale_x = rect_width / f64::from(svg_size.width()); - let scale_y = rect_height / f64::from(svg_size.height()); - - let translate_x = rect.min_x(); - let translate_y = rect.min_y(); - - let new = brush.map_or_else( - || vello_svg::render_tree(svg.tree), - |brush| { - let brush = brush.into(); - let size = Size::new(svg_size.width() as _, svg_size.height() as _); - let fill_rect = Rect::from_origin_size(Point::ZERO, size); - - alpha_mask_scene( - size, - |scene| { - scene.append(&vello_svg::render_tree(svg.tree), None); - }, - move |scene| { - scene.fill(Fill::NonZero, Affine::IDENTITY, brush, None, &fill_rect); - }, - ) - }, - ); - - // Apply transformations to fit the SVG within the provided rectangle - self.scene.append( - &new, - Some( - self.transform - .pre_scale_non_uniform(scale_x, scale_y) - .pre_translate((translate_x, translate_y).into()) - .then_scale(self.window_scale), - ), - ); - } - - fn set_transform(&mut self, transform: Affine) { - self.transform = transform; - } - - fn set_z_index(&mut self, _z_index: i32) {} - - fn clip(&mut self, _shape: &impl Shape) { - // if shape.bounding_box().is_zero_area() { - // return; - // } - // self.scene.pop_layer(); - // self.scene.push_layer( - // vello::peniko::BlendMode::default(), - // 1., - // self.transform.then_scale(self.window_scale), - // shape, - // ); - } - - fn clear_clip(&mut self) { - // self.scene.pop_layer(); - } - - fn finish(&mut self) -> Option { - if self.capture { - self.render_capture_image() - } else { - if let Ok(surface_texture) = self.surface.surface.get_current_texture() { - self.renderer - .render_to_texture( - &self.device, - &self.queue, - &self.scene, - &self.surface.target_view, - &vello::RenderParams { - base_color: palette::css::TRANSPARENT, // Background color - width: self.surface.config.width, - height: self.surface.config.height, - antialiasing_method: vello::AaConfig::Msaa16, - }, - ) - .unwrap(); - - // Perform the copy - let mut encoder = - self.device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Surface Blit"), - }); - - self.surface.blitter.copy( - &self.device, - &mut encoder, - &self.surface.target_view, - &surface_texture - .texture - .create_view(&wgpu::TextureViewDescriptor::default()), - ); - self.queue.submit([encoder.finish()]); - // Queue the texture to be presented on the surface - surface_texture.present(); - } - None - } - } - - fn debug_info(&self) -> String { - use std::fmt::Write; - - let mut out = String::new(); - writeln!(out, "name: Vello").ok(); - writeln!(out, "info: {:#?}", self.adapter.get_info()).ok(); - - out - } -} - -impl VelloRenderer { - fn render_capture_image(&mut self) -> Option { - let width_align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT - 1; - let width = (self.surface.config.width + width_align) & !width_align; - let height = self.surface.config.height; - let texture_desc = wgpu::TextureDescriptor { - size: wgpu::Extent3d { - width: self.surface.config.width, - height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8Unorm, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::COPY_SRC - | wgpu::TextureUsages::STORAGE_BINDING, - label: Some("render_texture"), - view_formats: &[wgpu::TextureFormat::Rgba8Unorm], - }; - let texture = self.device.create_texture(&texture_desc); - let view = texture.create_view(&wgpu::TextureViewDescriptor { - label: Some("Floem Inspector Preview"), - format: Some(TextureFormat::Rgba8Unorm), - dimension: Some(wgpu::TextureViewDimension::D2), - aspect: TextureAspect::default(), - base_mip_level: 0, - mip_level_count: None, - base_array_layer: 0, - array_layer_count: None, - ..Default::default() - }); - - self.renderer - .render_to_texture( - &self.device, - &self.queue, - &self.scene, - &view, - &vello::RenderParams { - base_color: palette::css::BLACK, // Background color - width: self.surface.config.width * self.window_scale as u32, - height: self.surface.config.height * self.window_scale as u32, - antialiasing_method: AaConfig::Area, - }, - ) - .unwrap(); - - let bytes_per_pixel = 4; - let buffer = self.device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: (u64::from(width * height) * bytes_per_pixel), - usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - let bytes_per_row = width * bytes_per_pixel as u32; - assert!(bytes_per_row.is_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT)); - - let mut encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - encoder.copy_texture_to_buffer( - texture.as_image_copy(), - wgpu::TexelCopyBufferInfo { - buffer: &buffer, - layout: wgpu::TexelCopyBufferLayout { - offset: 0, - bytes_per_row: Some(bytes_per_row), - rows_per_image: None, - }, - }, - texture_desc.size, - ); - let command_buffer = encoder.finish(); - self.queue.submit(Some(command_buffer)); - self.device.poll(wgpu::PollType::Wait).ok()?; - - let slice = buffer.slice(..); - let (tx, rx) = sync_channel(1); - slice.map_async(wgpu::MapMode::Read, move |r| tx.send(r).unwrap()); - - loop { - if let Ok(r) = rx.try_recv() { - break r.ok()?; - } - if matches!( - self.device.poll(wgpu::PollType::Wait).ok()?, - wgpu::PollStatus::WaitSucceeded - ) { - rx.recv().ok()?.ok()?; - break; - } - } - - let mut cropped_buffer = Vec::new(); - let buffer: Vec = slice.get_mapped_range().to_owned(); - - let mut cursor = 0; - let row_size = self.surface.config.width as usize * bytes_per_pixel as usize; - for _ in 0..height { - cropped_buffer.extend_from_slice(&buffer[cursor..(cursor + row_size)]); - cursor += bytes_per_row as usize; - } - - Some(vello::peniko::ImageBrush::new(ImageData { - data: Blob::new(Arc::new(cropped_buffer)), - format: vello::peniko::ImageFormat::Rgba8, - alpha_type: ImageAlphaType::AlphaPremultiplied, - width: self.surface.config.width, - height, - })) - } -} - -fn common_alpha_mask_scene( - size: Size, - alpha_mask: impl FnOnce(&mut Scene), - item: impl FnOnce(&mut Scene), - compose_mode: Compose, -) -> Scene { - let mut scene = Scene::new(); - scene.push_layer( - Mix::Normal, - 1.0, - Affine::IDENTITY, - &Rect::from_origin_size((0., 0.), size), - ); - - alpha_mask(&mut scene); - - scene.push_layer( - vello::peniko::BlendMode { - mix: Mix::Normal, - compose: compose_mode, - }, - 1., - Affine::IDENTITY, - &Rect::from_origin_size((0., 0.), size), - ); - - item(&mut scene); - - scene.pop_layer(); - scene.pop_layer(); - scene -} - -fn alpha_mask_scene( - size: Size, - alpha_mask: impl FnOnce(&mut Scene), - item: impl FnOnce(&mut Scene), -) -> Scene { - common_alpha_mask_scene(size, alpha_mask, item, Compose::SrcIn) -} -#[allow(unused)] -fn invert_alpha_mask_scene( - size: Size, - alpha_mask: impl FnOnce(&mut Scene), - item: impl FnOnce(&mut Scene), -) -> Scene { - common_alpha_mask_scene(size, alpha_mask, item, Compose::SrcOut) -} - -struct GlyphRun<'a> { - color: Color, - font_size: f32, - font_id: ID, - metadata: usize, - glyphs: Vec<&'a LayoutGlyph>, -} - -impl VelloRenderer { - fn get_font(&mut self, font_id: ID) -> vello::peniko::FontData { - self.font_cache.get(&font_id).cloned().unwrap_or_else(|| { - let mut font_system = FONT_SYSTEM.lock(); - let font = font_system.get_font(font_id).unwrap(); - let face = font_system.db().face(font_id).unwrap(); - let font_data = font.data(); - let font_index = face.index; - drop(font_system); - let font = - vello::peniko::FontData::new(Blob::new(Arc::new(font_data.to_vec())), font_index); - self.font_cache.insert(font_id, font.clone()); - font - }) - } - - fn draw_glyph_run(&mut self, run: GlyphRun, transform: Affine) { - let font = self.get_font(run.font_id); - self.scene - .draw_glyphs(&font) - .font_size(run.font_size) - .brush(run.color) - .hint(false) - .transform(transform) - .draw( - Fill::NonZero, - run.glyphs.into_iter().map(|glyph| vello::Glyph { - id: glyph.glyph_id.into(), - x: glyph.x, - y: glyph.y, - }), - ); - } -}