├── examples ├── output │ └── .gitkeep ├── assets │ ├── tall.png │ ├── wide.png │ ├── square.png │ ├── servo-color-negative-no-container.png │ ├── google_reduced.html │ ├── noscript.html │ ├── gosub_reduced.html │ ├── servo_reduced.html │ ├── github_profile_reduced.html │ ├── input.html │ ├── servo_header_reduced.html │ ├── servo-new-reduced-1.html │ ├── docsrs_header.html │ ├── svg.html │ ├── animation.html │ ├── border.html │ ├── animated_layout.html │ ├── object_fit.html │ ├── pseudo.html │ ├── servo-new-reduced.html │ └── gosub.html ├── url.rs ├── wgpu_texture │ ├── Cargo.toml │ └── src │ │ ├── main.rs │ │ ├── styles.css │ │ ├── html.rs │ │ ├── dioxus_native.rs │ │ └── shader.wgsl ├── box_shadow.rs ├── counter │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── todomvc │ └── Cargo.toml ├── outline.rs ├── inner_html.rs ├── restyle.rs ├── flex.rs ├── gradient.rs ├── html.rs ├── inline.rs └── form.rs ├── apps ├── browser │ ├── assets │ │ ├── blitz-logo.png │ │ ├── 404.html │ │ ├── icons │ │ │ ├── chevron-left.svg │ │ │ ├── chevron-right.svg │ │ │ ├── arrow-left.svg │ │ │ ├── arrow-right.svg │ │ │ ├── rotate-cw.svg │ │ │ ├── ellipsis-vertical.svg │ │ │ ├── external-link.svg │ │ │ └── house.svg │ │ └── browser.css │ ├── Dioxus.toml │ ├── src │ │ └── icons.rs │ └── Cargo.toml ├── bump │ ├── Cargo.toml │ └── src │ │ └── main.rs └── readme │ ├── assets │ └── blitz-markdown-overrides.css │ ├── src │ └── markdown │ │ ├── pulldown_cmark.rs │ │ └── comrak.rs │ └── Cargo.toml ├── packages ├── blitz-dom │ ├── assets │ │ └── moz-bullet-font.otf │ ├── src │ │ ├── node │ │ │ ├── mod.rs │ │ │ └── attributes.rs │ │ ├── html.rs │ │ ├── config.rs │ │ ├── events │ │ │ ├── focus.rs │ │ │ └── ime.rs │ │ ├── url.rs │ │ ├── stylo_to_cursor_icon.rs │ │ ├── accessibility.rs │ │ ├── query_selector.rs │ │ ├── lib.rs │ │ └── util.rs │ └── Cargo.toml ├── blitz-html │ ├── src │ │ ├── lib.rs │ │ └── html_document.rs │ └── Cargo.toml ├── blitz-traits │ ├── src │ │ ├── lib.rs │ │ ├── devtools.rs │ │ ├── navigation.rs │ │ ├── shell.rs │ │ └── net.rs │ └── Cargo.toml ├── debug_timer │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── stylo_taffy │ ├── src │ │ └── lib.rs │ └── Cargo.toml ├── dioxus-native │ ├── src │ │ ├── link_handler.rs │ │ ├── prelude.rs │ │ ├── contexts.rs │ │ ├── assets.rs │ │ └── dioxus_renderer.rs │ └── Cargo.toml ├── blitz-shell │ ├── src │ │ ├── accessibility.rs │ │ ├── event.rs │ │ ├── net.rs │ │ └── application.rs │ └── Cargo.toml ├── blitz-paint │ ├── src │ │ ├── color.rs │ │ ├── lib.rs │ │ ├── kurbo_css │ │ │ ├── mod.rs │ │ │ └── non_uniform_radii.rs │ │ ├── layers.rs │ │ ├── sizing.rs │ │ ├── render │ │ │ ├── form_controls.rs │ │ │ └── box_shadow.rs │ │ └── text.rs │ └── Cargo.toml ├── blitz-net │ └── Cargo.toml ├── dioxus-native-dom │ ├── src │ │ ├── sub_document.rs │ │ └── lib.rs │ └── Cargo.toml └── blitz │ └── Cargo.toml ├── .gitignore ├── LICENSE-MIT ├── CONTRIBUTING.MD ├── wpt └── runner │ ├── Cargo.toml │ └── src │ ├── panic_backtrace.rs │ ├── report.rs │ └── test_runners │ └── mod.rs ├── .github └── workflows │ └── wpt.yml └── justfile /examples/output/.gitkeep: -------------------------------------------------------------------------------- 1 | .gitkeep -------------------------------------------------------------------------------- /examples/assets/tall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DioxusLabs/blitz/main/examples/assets/tall.png -------------------------------------------------------------------------------- /examples/assets/wide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DioxusLabs/blitz/main/examples/assets/wide.png -------------------------------------------------------------------------------- /examples/assets/square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DioxusLabs/blitz/main/examples/assets/square.png -------------------------------------------------------------------------------- /apps/browser/assets/blitz-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DioxusLabs/blitz/main/apps/browser/assets/blitz-logo.png -------------------------------------------------------------------------------- /packages/blitz-dom/assets/moz-bullet-font.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DioxusLabs/blitz/main/packages/blitz-dom/assets/moz-bullet-font.otf -------------------------------------------------------------------------------- /examples/assets/servo-color-negative-no-container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DioxusLabs/blitz/main/examples/assets/servo-color-negative-no-container.png -------------------------------------------------------------------------------- /apps/browser/Dioxus.toml: -------------------------------------------------------------------------------- 1 | [application] 2 | 3 | [bundle] 4 | publisher = "DioxusLabs" 5 | identifier = "com.dioxuslabs.blitz" 6 | icon = ["./assets/blitz-logo.png"] -------------------------------------------------------------------------------- /examples/assets/google_reduced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Google 5 | 6 | 7 |
Foo
8 | 9 | -------------------------------------------------------------------------------- /packages/blitz-html/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::collapsible_if)] 2 | 3 | mod html_document; 4 | mod html_sink; 5 | 6 | pub use html_document::HtmlDocument; 7 | pub use html_sink::DocumentHtmlParser; 8 | pub use html_sink::HtmlProvider; 9 | -------------------------------------------------------------------------------- /examples/assets/noscript.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | /scratch 4 | /.vscode 5 | /examples/output/**/*.png 6 | /examples/output/**/*.jpg 7 | /examples/output/**/*.jpeg 8 | /out 9 | /wpt/output 10 | /wpt/*.json 11 | /apps/wpt/output 12 | /sites 13 | .blitz-cache -------------------------------------------------------------------------------- /apps/bump/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bump" 3 | version = "0.0.0" 4 | edition = "2024" 5 | description = "Utility to aid publishing blitz" 6 | license.workspace = true 7 | publish = false 8 | 9 | [dependencies] 10 | toml_edit = "0.22" 11 | semver = "1" -------------------------------------------------------------------------------- /apps/browser/assets/404.html: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 404 Not found 13 | -------------------------------------------------------------------------------- /packages/blitz-traits/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Types and traits to enable interoperability between the other Blitz crates without 2 | //! circular or unnecessary dependencies. 3 | 4 | pub mod devtools; 5 | pub mod events; 6 | pub mod navigation; 7 | pub mod net; 8 | pub mod shell; 9 | -------------------------------------------------------------------------------- /examples/url.rs: -------------------------------------------------------------------------------- 1 | //! Load first CLI argument as a url. Fallback to google.com if no CLI argument is provided. 2 | 3 | fn main() { 4 | let url = std::env::args() 5 | .nth(1) 6 | .unwrap_or_else(|| "https://www.google.com".into()); 7 | blitz::launch_url(&url); 8 | } 9 | -------------------------------------------------------------------------------- /apps/browser/assets/icons/chevron-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/browser/assets/icons/chevron-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/browser/assets/icons/arrow-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/browser/assets/icons/arrow-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/browser/assets/icons/rotate-cw.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/browser/assets/icons/ellipsis-vertical.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/debug_timer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "debug_timer" 3 | description = "Utilities for simple timings" 4 | version = "0.1.3" 5 | homepage = "https://github.com/dioxuslabs/blitz" 6 | repository = "https://github.com/dioxuslabs/blitz" 7 | documentation = "https://docs.rs/debug_timer" 8 | license.workspace = true 9 | edition = "2021" 10 | 11 | [features] 12 | enable = [] 13 | -------------------------------------------------------------------------------- /apps/browser/assets/icons/external-link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/browser/assets/icons/house.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/assets/gosub_reduced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
GitHub
7 |
Global Discord
8 |
Developer chat - Zudivp
9 |
10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/blitz-dom/src/node/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::module_inception)] 2 | 3 | mod attributes; 4 | mod element; 5 | mod node; 6 | 7 | pub use attributes::{Attribute, Attributes}; 8 | pub use element::{ 9 | BackgroundImageData, CanvasData, ElementData, ImageData, ListItemLayout, 10 | ListItemLayoutPosition, Marker, RasterImageData, SpecialElementData, SpecialElementType, 11 | Status, TextBrush, TextInputData, TextLayout, 12 | }; 13 | pub use node::*; 14 | -------------------------------------------------------------------------------- /examples/assets/servo_reduced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
WRAP WRAP WRAP WRAP WRAP
7 | 8 |
9 |
10 | 11 | -------------------------------------------------------------------------------- /packages/stylo_taffy/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Conversion functions from Stylo types to Taffy types 2 | //! 3 | //! This crate is an implementation detail of [`blitz-dom`](https://docs.rs/blitz-dom), but can also be 4 | //! used standalone, and serves as useful reference for anyone wanting to integrate [`stylo`](::style) with [`taffy`] 5 | 6 | mod wrapper; 7 | pub use wrapper::TaffyStyloStyle; 8 | 9 | pub mod convert; 10 | #[doc(inline)] 11 | pub use convert::to_taffy_style; 12 | 13 | pub use style::Atom; 14 | -------------------------------------------------------------------------------- /examples/assets/github_profile_reduced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 17 | 18 |
Foo
19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/blitz-html/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blitz-html" 3 | description = "Blitz HTML parser" 4 | documentation = "https://docs.rs/blitz-html" 5 | version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | categories.workspace = true 10 | edition.workspace = true 11 | rust-version.workspace = true 12 | 13 | [dependencies] 14 | # Blitz dependencies 15 | blitz-dom = { workspace = true } 16 | blitz-traits = { workspace = true } 17 | 18 | # Servo dependencies 19 | html5ever = { workspace = true } 20 | xml5ever = { workspace = true } -------------------------------------------------------------------------------- /examples/assets/input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | 13 | -------------------------------------------------------------------------------- /apps/readme/assets/blitz-markdown-overrides.css: -------------------------------------------------------------------------------- 1 | .markdown-body { 2 | max-width: 892px; 3 | padding: 16px 32px; 4 | margin: 0 auto; 5 | } 6 | 7 | .markdown-body table { 8 | display: table; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | html, body { 13 | background-color: #0d1117; 14 | } 15 | 16 | [href$="#gh-light-mode-only"] { 17 | display: none !important; 18 | } 19 | [src$="#gh-light-mode-only"] { 20 | display: none !important; 21 | } 22 | } 23 | 24 | @media (prefers-color-scheme: light) { 25 | [href$="#gh-dark-mode-only"] { 26 | display: none !important; 27 | } 28 | [src$="#gh-dark-mode-only"] { 29 | display: none !important; 30 | } 31 | } -------------------------------------------------------------------------------- /packages/blitz-traits/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blitz-traits" 3 | description = "Shared traits and types for Blitz" 4 | documentation = "https://docs.rs/blitz-traits" 5 | version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | categories.workspace = true 10 | edition.workspace = true 11 | rust-version.workspace = true 12 | 13 | [dependencies] 14 | http = { workspace = true } 15 | url = { workspace = true } 16 | bytes = { workspace = true } 17 | keyboard-types = { workspace = true } 18 | smol_str = { workspace = true } 19 | bitflags = { workspace = true } 20 | cursor-icon = { workspace = true } 21 | serde = { workspace = true } 22 | -------------------------------------------------------------------------------- /packages/dioxus-native/src/link_handler.rs: -------------------------------------------------------------------------------- 1 | use blitz_traits::{ 2 | navigation::{NavigationOptions, NavigationProvider}, 3 | net::Method, 4 | }; 5 | 6 | pub(crate) struct DioxusNativeNavigationProvider; 7 | 8 | impl NavigationProvider for DioxusNativeNavigationProvider { 9 | fn navigate_to(&self, options: NavigationOptions) { 10 | if options.method == Method::GET 11 | && matches!(options.url.scheme(), "http" | "https" | "mailto") 12 | { 13 | if let Err(_err) = webbrowser::open(options.url.as_str()) { 14 | #[cfg(feature = "tracing")] 15 | tracing::error!("Failed to open URL: {}", _err); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/stylo_taffy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stylo_taffy" 3 | license = "MIT OR Apache-2.0 OR MPL-2.0" 4 | description = "Interop crate for the stylo and taffy crates" 5 | keywords = ["css", "layout"] 6 | version.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | categories.workspace = true 10 | edition.workspace = true 11 | rust-version.workspace = true 12 | 13 | 14 | [dependencies] 15 | taffy = { workspace = true } 16 | style = { workspace = true } 17 | style_atoms = { workspace = true } 18 | 19 | [features] 20 | default = ["std", "block", "flexbox", "grid"] 21 | std = ["taffy/std"] 22 | block = ["taffy/block_layout"] 23 | flexbox = ["taffy/flexbox"] 24 | grid = ["taffy/grid"] 25 | floats = ["taffy/float_layout"] 26 | -------------------------------------------------------------------------------- /examples/assets/servo_header_reduced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 21 | 22 | -------------------------------------------------------------------------------- /examples/wgpu_texture/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright © SixtyFPS GmbH 2 | # SPDX-License-Identifier: MIT 3 | 4 | [package] 5 | name = "wgpu_texture" 6 | version = "0.0.0" 7 | edition = "2021" 8 | license = "MIT" 9 | publish = false 10 | 11 | [dependencies] 12 | anyrender = { workspace = true } 13 | anyrender_vello = { workspace = true } 14 | blitz-traits = { workspace = true} 15 | blitz-dom = { workspace = true, features = ["tracing"]} 16 | blitz-html = { workspace = true } 17 | blitz-shell = { workspace = true, features = ["tracing"] } 18 | dioxus-native = { workspace = true, features = ["vello", "system-fonts", "prelude"] } 19 | wgpu_context = { workspace = true } 20 | wgpu = { workspace = true } 21 | color = { workspace = true } 22 | bytemuck = { workspace = true } 23 | pollster = { workspace = true } 24 | -------------------------------------------------------------------------------- /packages/blitz-dom/src/html.rs: -------------------------------------------------------------------------------- 1 | use crate::DocumentMutator; 2 | 3 | pub trait HtmlParserProvider { 4 | fn parse_inner_html<'m, 'doc>( 5 | &self, 6 | mutr: &'m mut DocumentMutator<'doc>, 7 | element_id: usize, 8 | html: &str, 9 | ); 10 | } 11 | 12 | pub struct DummyHtmlParserProvider; 13 | impl HtmlParserProvider for DummyHtmlParserProvider { 14 | fn parse_inner_html<'m, 'doc>( 15 | &self, 16 | mutr: &'m mut DocumentMutator<'doc>, 17 | element_id: usize, 18 | html: &str, 19 | ) { 20 | let _ = mutr; 21 | let _ = element_id; 22 | let _ = html; 23 | // Do nothing for now 24 | // 25 | // TODO: do something: 26 | // - Print warning? 27 | // - Parse HTML as plain text? 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/blitz-shell/src/accessibility.rs: -------------------------------------------------------------------------------- 1 | use crate::event::BlitzShellEvent; 2 | use accesskit_winit::Adapter; 3 | use blitz_dom::BaseDocument; 4 | use winit::{event_loop::EventLoopProxy, window::Window}; 5 | 6 | /// State of the accessibility node tree and platform adapter. 7 | pub struct AccessibilityState { 8 | /// Adapter to connect to the [`EventLoop`](`winit::event_loop::EventLoop`). 9 | adapter: accesskit_winit::Adapter, 10 | } 11 | 12 | impl AccessibilityState { 13 | pub fn new(window: &Window, proxy: EventLoopProxy) -> Self { 14 | Self { 15 | adapter: Adapter::with_event_loop_proxy(window, proxy.clone()), 16 | } 17 | } 18 | pub fn update_tree(&mut self, doc: &BaseDocument) { 19 | self.adapter 20 | .update_if_active(|| doc.build_accessibility_tree()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/blitz-paint/src/color.rs: -------------------------------------------------------------------------------- 1 | use color::{AlphaColor, DynamicColor, Srgb}; 2 | use style::color::AbsoluteColor; 3 | 4 | pub type Color = AlphaColor; 5 | 6 | pub trait ToColorColor { 7 | /// Converts a color into the `AlphaColor` type from the `color` crate 8 | fn as_srgb_color(&self) -> Color; 9 | 10 | /// Converts a color into the `DynamicColor` type from the `color` crate 11 | fn as_dynamic_color(&self) -> DynamicColor; 12 | } 13 | impl ToColorColor for AbsoluteColor { 14 | fn as_srgb_color(&self) -> Color { 15 | Color::new( 16 | *self 17 | .to_color_space(style::color::ColorSpace::Srgb) 18 | .raw_components(), 19 | ) 20 | } 21 | 22 | fn as_dynamic_color(&self) -> DynamicColor { 23 | DynamicColor::from_alpha_color(self.as_srgb_color()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/box_shadow.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | 3 | fn main() { 4 | dioxus_native::launch(app); 5 | } 6 | 7 | fn app() -> Element { 8 | rsx! { 9 | div { 10 | style { {CSS} } 11 | div { id: "box-shadow-1", class: "box-shadow" } 12 | div { id: "box-shadow-2", class: "box-shadow" } 13 | div { id: "box-shadow-3", class: "box-shadow" } 14 | } 15 | } 16 | } 17 | 18 | const CSS: &str = r#" 19 | .box-shadow { 20 | width: 200px; 21 | height: 200px; 22 | background-color: red; 23 | margin: 60px; 24 | } 25 | 26 | #box-shadow-1 { 27 | width: 100px; 28 | height: 100px; 29 | box-shadow: 140px 0 blue; 30 | } 31 | 32 | #box-shadow-2 { 33 | box-shadow: 10px 10px 5px 10px rgb(238 255 7), 10px 10px 5px 30px blue; 34 | } 35 | 36 | #box-shadow-3 { 37 | box-shadow: 0 0 10px 20px rgb(238 255 7); 38 | } 39 | "#; 40 | -------------------------------------------------------------------------------- /apps/browser/src/icons.rs: -------------------------------------------------------------------------------- 1 | use dioxus_native::prelude::*; 2 | 3 | pub const REFRESH_ICON: Asset = asset!("../assets/icons/rotate-cw.svg"); 4 | pub const HOME_ICON: Asset = asset!("../assets/icons/house.svg"); 5 | pub const BACK_ICON: Asset = asset!("../assets/icons/arrow-left.svg"); 6 | pub const FORWARDS_ICON: Asset = asset!("../assets/icons/arrow-right.svg"); 7 | pub const MENU_ICON: Asset = asset!("../assets/icons/ellipsis-vertical.svg"); 8 | pub const EXTERNAL_LINK_ICON: Asset = asset!("../assets/icons/external-link.svg"); 9 | 10 | #[component] 11 | pub fn IconButton(icon: Asset, action: Option) -> Element { 12 | rsx!( 13 | div { 14 | class: "iconbutton", 15 | onclick: move |_| { 16 | if let Some(action) = action { 17 | action(()) 18 | } 19 | }, 20 | img { class: "urlbar-icon", src: icon } 21 | } 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /examples/assets/servo-new-reduced-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Servo items reduced. 7 | 20 | 21 | 22 |
23 |
24 |
25 |
26 |

Embeddable

27 |
28 |
29 |
30 |
31 | 32 | -------------------------------------------------------------------------------- /packages/blitz-traits/src/devtools.rs: -------------------------------------------------------------------------------- 1 | //! Types configure developer inspection and debug tools 2 | 3 | /// Configuration for debug overlays and other debugging tools 4 | #[derive(Debug, Default, Clone, Copy)] 5 | pub struct DevtoolSettings { 6 | /// Outline elements with different border colors depending on 7 | /// inner display style of that element 8 | pub show_layout: bool, 9 | /// Render browser-style colored overlay showing the content-box, 10 | /// padding, border, and margin of the hovered element 11 | pub highlight_hover: bool, 12 | } 13 | 14 | impl DevtoolSettings { 15 | /// Toggle the [`show_layout`](Self::show_layout) setting 16 | pub fn toggle_show_layout(&mut self) { 17 | self.show_layout = !self.show_layout 18 | } 19 | 20 | /// Toggle the [`highlight_hover`](Self::highlight_hover) setting 21 | pub fn toggle_highlight_hover(&mut self) { 22 | self.highlight_hover = !self.highlight_hover 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/wgpu_texture/src/main.rs: -------------------------------------------------------------------------------- 1 | use color::{OpaqueColor, Srgb}; 2 | use demo_renderer::{DemoMessage, DemoPaintSource}; 3 | use std::env; 4 | use wgpu::{Features, Limits}; 5 | 6 | mod demo_renderer; 7 | mod dioxus_native; 8 | mod html; 9 | 10 | use dioxus_native::launch_dx_native; 11 | use html::launch_html; 12 | 13 | // CSS Styles 14 | static STYLES: &str = include_str!("./styles.css"); 15 | 16 | // WGPU settings required by this example 17 | const FEATURES: Features = Features::PUSH_CONSTANTS; 18 | fn limits() -> Limits { 19 | Limits { 20 | max_push_constant_size: 16, 21 | ..Limits::default() 22 | } 23 | } 24 | 25 | type Color = OpaqueColor; 26 | 27 | fn main() { 28 | let use_html_renderer = env::args().any(|arg| arg == "--html"); 29 | 30 | if use_html_renderer { 31 | // Render WGPU demo using Blitz HTML document 32 | launch_html(); 33 | } else { 34 | // Render WGPU demo using dioxus-native 35 | launch_dx_native(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/counter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "counter" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license.workspace = true 6 | publish = false 7 | 8 | [features] 9 | default = ["system_fonts", "vello"] 10 | system_fonts = ["blitz-dom/system_fonts"] 11 | vello = ["dioxus-native/vello"] 12 | cpu = ["cpu-pixels"] 13 | svg = ["blitz-paint/svg"] 14 | cpu-pixels = ["dioxus-native/vello-cpu-pixels"] 15 | cpu-softbuffer = ["dioxus-native/vello-cpu-softbuffer"] 16 | incremental = ["dioxus-native/incremental"] 17 | log_frame_times = ["dioxus-native/log-frame-times"] 18 | log_phase_times = ["dioxus-native/log-phase-times"] 19 | 20 | [dependencies] 21 | dioxus-native = { workspace = true, default-features = false, features = ["prelude"]} 22 | 23 | # Control whether system font support is enabled 24 | blitz-dom = { workspace = true, default-features = false } 25 | blitz-paint = { workspace = true, default-features = false } 26 | 27 | # Disable unicode URL support 28 | # See https://github.com/hsivonen/idna_adapter 29 | idna_adapter = "=1.0.0" -------------------------------------------------------------------------------- /packages/blitz-net/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blitz-net" 3 | description = "Blitz networking" 4 | documentation = "https://docs.rs/blitz-net" 5 | version = "0.2.1" 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | categories.workspace = true 10 | edition.workspace = true 11 | rust-version.workspace = true 12 | 13 | [features] 14 | cookies = ["reqwest/cookies"] 15 | multipart = ["reqwest/multipart", "reqwest/stream"] 16 | cache = ["dep:reqwest-middleware", "dep:http-cache-reqwest", "dep:directories"] 17 | debug_log = [] 18 | 19 | [dependencies] 20 | # Blitz dependencies 21 | blitz-traits = { workspace = true } 22 | 23 | # Networking dependencies 24 | tokio = { workspace = true } 25 | reqwest = { workspace = true } 26 | data-url = { workspace = true } 27 | 28 | # Caching 29 | reqwest-middleware = { workspace = true, optional = true } 30 | http-cache-reqwest = { workspace = true, optional = true, features = ["manager-cacache"] } 31 | directories = { version = "6.0.0", optional = true } 32 | -------------------------------------------------------------------------------- /examples/todomvc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "todomvc" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license.workspace = true 6 | publish = false 7 | 8 | [features] 9 | default = ["vello"] 10 | vello = ["dioxus-native/vello"] 11 | hybrid = ["dioxus-native/vello-hybrid"] 12 | skia = ["dioxus-native/skia"] 13 | cpu = ["cpu-pixels"] 14 | cpu-pixels = ["dioxus-native/vello-cpu-pixels"] 15 | cpu-softbuffer = ["dioxus-native/vello-cpu-softbuffer"] 16 | incremental = ["dioxus-native/incremental"] 17 | log_frame_times = ["dioxus-native/log-frame-times"] 18 | log_phase_times = ["dioxus-native/log-phase-times"] 19 | floats = ["dioxus-native/floats"] 20 | tracing = ["dioxus-native/tracing", "dep:tracing-subscriber"] 21 | hot-reload = ["dioxus-native/hot-reload"] 22 | 23 | [dependencies] 24 | dioxus-native = { workspace = true, features = ["svg", "system-fonts", "data-uri", "prelude"] } 25 | tracing-subscriber = { workspace = true, optional = true} 26 | 27 | # Disable unicode URL support 28 | # See https://github.com/hsivonen/idna_adapter 29 | idna_adapter = "=1.0.0" -------------------------------------------------------------------------------- /packages/dioxus-native-dom/src/sub_document.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc}; 2 | 3 | use blitz_dom::{BaseDocument, PlainDocument}; 4 | use dioxus_core::{AttributeValue, IntoAttributeValue}; 5 | 6 | // Hack to get write-once semantics for an attribute 7 | #[derive(Clone)] 8 | pub struct SubDocumentAttr { 9 | id: usize, 10 | doc: Rc>>>, 11 | } 12 | 13 | impl SubDocumentAttr { 14 | pub fn new(doc: BaseDocument) -> Self { 15 | let id = doc.id(); 16 | let wrapped = Rc::new(RefCell::new(Some(Box::new(PlainDocument(doc))))); 17 | Self { id, doc: wrapped } 18 | } 19 | pub fn take_document(&self) -> Option> { 20 | self.doc.borrow_mut().take() 21 | } 22 | } 23 | 24 | impl PartialEq for SubDocumentAttr { 25 | fn eq(&self, other: &Self) -> bool { 26 | self.id == other.id 27 | } 28 | } 29 | 30 | impl IntoAttributeValue for SubDocumentAttr { 31 | fn into_value(self) -> AttributeValue { 32 | AttributeValue::Any(Rc::new(self)) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /apps/readme/src/markdown/pulldown_cmark.rs: -------------------------------------------------------------------------------- 1 | //! Render the readme.md using the gpu renderer 2 | 3 | use pulldown_cmark::{Options, Parser}; 4 | 5 | pub(crate) fn markdown_to_html(contents: String) -> String { 6 | // Set up options and parser. 7 | let mut options = Options::empty(); 8 | options.insert(Options::ENABLE_STRIKETHROUGH); 9 | options.insert(Options::ENABLE_TABLES); 10 | options.insert(Options::ENABLE_TASKLISTS); 11 | options.insert(Options::ENABLE_FOOTNOTES); 12 | options.insert(Options::ENABLE_GFM); 13 | let parser = Parser::new_ext(&contents, options); 14 | 15 | // Write to String buffer. 16 | let mut body_html = String::new(); 17 | pulldown_cmark::html::push_html(&mut body_html, parser); 18 | 19 | // Strip trailing newlines in code blocks 20 | let body_html = body_html.replace("\n 25 | 26 | 27 |
{body_html}
28 | 29 | 30 | "# 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /examples/assets/docsrs_header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

Crate taffy

12 | 13 |
14 | Settings 15 |
16 |
17 | Help 18 |
19 | 20 |
21 | Source
22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /apps/browser/assets/browser.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | box-sizing: border-box; 4 | } 5 | 6 | html, body, #main, #frame { 7 | margin: 0; 8 | padding: 0; 9 | height: 100%; 10 | } 11 | 12 | #frame { 13 | display: flex; 14 | flex-direction: column; 15 | } 16 | 17 | .urlbar { 18 | padding: 6px 6px; 19 | gap: 6px; 20 | background: #F9F9F9; 21 | border-bottom: 1px solid #EEE; 22 | display: flex; 23 | } 24 | 25 | .urlbar-input { 26 | flex: 1 1 60px; 27 | border: 1px solid #CCC; 28 | border-radius: 4px; 29 | line-height: 1; 30 | padding: 6px; 31 | min-width: 60px; 32 | font-size: 14px; 33 | 34 | &:focus { 35 | border-color: #5E9ED6; 36 | outline: 1px solid #5E9ED6; 37 | } 38 | } 39 | 40 | .iconbutton { 41 | padding: 4px; 42 | cursor: pointer; 43 | border-radius: 4px; 44 | 45 | &:hover { 46 | background: #CCC; 47 | } 48 | 49 | &:active { 50 | background: #BBB; 51 | } 52 | 53 | > .urlbar-icon { 54 | height: 20px; 55 | } 56 | } 57 | 58 | .webview { 59 | flex: 1 1 0px; 60 | height: 100%; 61 | width: 100%; 62 | } 63 | -------------------------------------------------------------------------------- /packages/blitz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blitz" 3 | description = "High-level APIs for rendering HTML with Blitz" 4 | documentation = "https://docs.rs/blitz" 5 | version = "0.2.1" 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | categories.workspace = true 10 | edition.workspace = true 11 | rust-version.workspace = true 12 | 13 | [features] 14 | default = ["net", "accessibility"] 15 | net = ["dep:tokio", "dep:url", "dep:blitz-net"] 16 | accessibility = ["blitz-shell/accessibility"] 17 | tracing = ["blitz-shell/tracing"] 18 | 19 | [dependencies] 20 | # Blitz dependencies 21 | anyrender_vello = { workspace = true } 22 | blitz-traits = { workspace = true } 23 | blitz-dom = { workspace = true } 24 | blitz-html = { workspace = true } 25 | blitz-shell = { workspace = true } 26 | blitz-paint = { workspace = true } 27 | blitz-net = { workspace = true, optional = true } 28 | 29 | # IO & Networking 30 | url = { workspace = true, features = ["serde"], optional = true } 31 | tokio = { workspace = true, features = ["rt-multi-thread"], optional = true } 32 | 33 | [package.metadata.docs.rs] 34 | all-features = true 35 | rustdoc-args = ["--cfg", "docsrs"] 36 | -------------------------------------------------------------------------------- /examples/wgpu_texture/src/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, body, main { 6 | height: 100%; 7 | font-family: system-ui, sans; 8 | margin: 0; 9 | } 10 | 11 | main { 12 | display: grid; 13 | grid-template-rows: 100px 1fr; 14 | grid-template-columns: 100%; 15 | background: #f4e8d2; 16 | } 17 | 18 | #canvas-container { 19 | display: grid; 20 | opacity: 0.8; 21 | } 22 | 23 | header { 24 | padding: 10px 40px; 25 | background-color: white; 26 | z-index: 100; 27 | } 28 | 29 | #overlay { 30 | position: absolute; 31 | width: 33%; 32 | height: 100%; 33 | right: 0; 34 | z-index: 10; 35 | background-color: rgba(0, 0, 0, 0.5); 36 | padding-top: 40%; 37 | padding-inline: 20px; 38 | color: white; 39 | } 40 | 41 | #underlay { 42 | position: absolute; 43 | width: 33%; 44 | height: 100%; 45 | z-index: -10; 46 | background-color: black; 47 | padding-top: 40%; 48 | padding-inline: 20px; 49 | color: white; 50 | } 51 | 52 | .color-control { 53 | display: flex; 54 | gap: 12px; 55 | 56 | > input { 57 | width: 150px; 58 | color: black; 59 | } 60 | } -------------------------------------------------------------------------------- /packages/blitz-paint/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blitz-paint" 3 | description = "Paint a Blitz Document using anyrender" 4 | documentation = "https://docs.rs/blitz-paint" 5 | version = "0.2.1" 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | categories.workspace = true 10 | edition.workspace = true 11 | rust-version.workspace = true 12 | 13 | [features] 14 | default = ["svg"] 15 | tracing = ["dep:tracing"] 16 | svg = ["dep:anyrender_svg", "dep:usvg", "blitz-dom/svg"] 17 | 18 | [dependencies] 19 | # Blitz dependencies 20 | anyrender = { workspace = true } 21 | anyrender_svg = { workspace = true, optional = true } 22 | blitz-traits = { workspace = true } 23 | blitz-dom = { workspace = true } 24 | 25 | # Servo dependencies 26 | style = { workspace = true } 27 | euclid = { workspace = true } 28 | 29 | # DioxusLabs dependencies 30 | taffy = { workspace = true } 31 | 32 | # Linebender + WGPU dependencies 33 | parley = { workspace = true } 34 | color = { workspace = true } 35 | peniko = { workspace = true } 36 | kurbo = { workspace = true } 37 | usvg = { workspace = true, optional = true } 38 | 39 | # Other dependencies 40 | tracing = { workspace = true, optional = true } 41 | -------------------------------------------------------------------------------- /packages/blitz-dom/src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::HtmlParserProvider; 2 | use blitz_traits::{ 3 | navigation::NavigationProvider, 4 | net::NetProvider, 5 | shell::{ShellProvider, Viewport}, 6 | }; 7 | use parley::FontContext; 8 | use std::sync::Arc; 9 | 10 | /// Options used when constructing a [`BaseDocument`](crate::BaseDocument) 11 | #[derive(Default)] 12 | pub struct DocumentConfig { 13 | /// The initial `Viewport` 14 | pub viewport: Option, 15 | /// The base url which relative URLs are resolved against 16 | pub base_url: Option, 17 | /// User Agent stylesheets 18 | pub ua_stylesheets: Option>, 19 | /// Net provider to handle network requests for resources 20 | pub net_provider: Option>, 21 | /// Navigation provider to handle link clicks and form submissions 22 | pub navigation_provider: Option>, 23 | /// Shell provider to redraw requests, clipboard, etc 24 | pub shell_provider: Option>, 25 | /// HTML parser provider. Used to parse HTML for setInnerHTML 26 | pub html_parser_provider: Option>, 27 | /// Parley `FontContext` 28 | pub font_ctx: Option, 29 | } 30 | -------------------------------------------------------------------------------- /packages/blitz-dom/src/events/focus.rs: -------------------------------------------------------------------------------- 1 | use blitz_traits::events::{BlitzFocusEvent, DomEvent, DomEventData}; 2 | 3 | use crate::BaseDocument; 4 | 5 | pub(crate) fn generate_focus_events( 6 | doc: &mut BaseDocument, 7 | update_focus: &mut dyn FnMut(&mut BaseDocument), 8 | dispatch_event: &mut dyn FnMut(DomEvent), 9 | ) { 10 | // Update focus, tracking which node was focussed before and after 11 | let old_focus = doc.get_focussed_node_id(); 12 | update_focus(doc); 13 | let new_focus = doc.get_focussed_node_id(); 14 | 15 | if old_focus == new_focus { 16 | return; 17 | } 18 | 19 | if let Some(old_focus) = old_focus { 20 | dispatch_event(DomEvent::new( 21 | old_focus, 22 | DomEventData::Blur(BlitzFocusEvent), 23 | )); 24 | dispatch_event(DomEvent::new( 25 | old_focus, 26 | DomEventData::FocusOut(BlitzFocusEvent), 27 | )); 28 | } 29 | 30 | if let Some(new_focus) = new_focus { 31 | dispatch_event(DomEvent::new( 32 | new_focus, 33 | DomEventData::Focus(BlitzFocusEvent), 34 | )); 35 | dispatch_event(DomEvent::new( 36 | new_focus, 37 | DomEventData::FocusIn(BlitzFocusEvent), 38 | )); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /CONTRIBUTING.MD: -------------------------------------------------------------------------------- 1 | # Contributing to Blitz 2 | 3 | Welcome to the Dioxus community! 4 | Blitz is a "native" HTML/CSS renderer built to support the "Dioxus Native" project. It is effectively a lightweight webview except that the JavaScript engine is replaced with a native Rust API which allows Rust reactivity / state management libraries like Dioxus to interface with it directly. 5 | 6 | Talk to us in: the #native channel in the [Dioxus Discord](https://discord.gg/BWTrn6d3) 7 | 8 | ## Development 9 | 10 | ### Windows 11 | Building Blitz requires Python, which can be installed from the Windows app store. 12 | 13 | ### Linux 14 | Requirements: 15 | * asound2 16 | * atk1.0 17 | * gtk-3 18 | * udev 19 | * pango1.0 20 | * xdo 21 | 22 | For example on Ubuntu you can install these by running: 23 | ```sh 24 | sudo apt-get update 25 | sudo apt-get install \ 26 | libasound2-dev \ 27 | libatk1.0-dev \ 28 | libgtk-3-dev \ 29 | libudev-dev \ 30 | libpango1.0-dev \ 31 | libxdo-dev 32 | ``` 33 | 34 | ### VSCode 35 | You can add the following JSON to your `.vscode/settings.json` to automically build Blitz on all supported targets. 36 | ```json 37 | { 38 | "rust-analyzer.check.features": "all", 39 | "rust-analyzer.cargo.features": "all", 40 | "rust-analyzer.check.allTargets": true, 41 | "rust-analyzer.cargo.allTargets": true 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /packages/dioxus-native/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // #[cfg(feature = "document")] 2 | // #[cfg_attr(docsrs, doc(cfg(feature = "document")))] 3 | pub use dioxus_document::{self as document, Meta, Stylesheet, Title}; 4 | // #[cfg(feature = "document")] 5 | // #[cfg_attr(docsrs, doc(cfg(feature = "document")))] 6 | pub use dioxus_history::{history, History}; 7 | 8 | // RSX and component definition 9 | pub use dioxus_core; 10 | pub use dioxus_core::{ 11 | consume_context, provide_context, spawn, suspend, try_consume_context, use_drop, use_hook, 12 | AnyhowContext, Attribute, Callback, Component, Element, ErrorBoundary, ErrorContext, Event, 13 | EventHandler, Fragment, HasAttributes, IntoDynNode, RenderError, ScopeId, SuspenseBoundary, 14 | SuspenseContext, VNode, VirtualDom, 15 | }; 16 | #[allow(deprecated)] 17 | pub use dioxus_core_macro::{component, rsx, Props}; 18 | pub use dioxus_html as dioxus_elements; 19 | pub use dioxus_html::{ 20 | events::*, extensions::*, global_attributes, keyboard_types, svg_attributes, traits::*, 21 | GlobalAttributesExtension, SvgAttributesExtension, 22 | }; 23 | pub use dioxus_html::{Code, Key, Location, Modifiers}; 24 | 25 | // Assets 26 | pub use manganis::{self, *}; 27 | 28 | // Hooks, signals, stores 29 | pub use dioxus_hooks::*; 30 | pub use dioxus_signals::{self, *}; 31 | pub use dioxus_stores::{self, store, use_store, GlobalStore, ReadStore, Store, WriteStore}; 32 | -------------------------------------------------------------------------------- /apps/browser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "browser" 3 | version = "0.0.0" 4 | edition = "2024" 5 | license.workspace = true 6 | publish = false 7 | 8 | [[bin]] 9 | name = "blitz" 10 | path = "src/main.rs" 11 | 12 | [features] 13 | default = ["vello", "floats", "incremental", "cookies", "cache"] 14 | vello = ["dioxus-native/vello"] 15 | hybrid = ["dioxus-native/vello-hybrid"] 16 | skia = ["dioxus-native/skia"] 17 | cpu = ["cpu-pixels"] 18 | cpu-pixels = ["dioxus-native/vello-cpu-pixels"] 19 | cpu-softbuffer = ["dioxus-native/vello-cpu-softbuffer"] 20 | incremental = ["dioxus-native/incremental"] 21 | log_frame_times = ["dioxus-native/log-frame-times"] 22 | log_phase_times = ["dioxus-native/log-phase-times"] 23 | floats = ["dioxus-native/floats"] 24 | cache = ["blitz-net/cache"] 25 | cookies = ["blitz-net/cookies"] 26 | tracing = ["dioxus-native/tracing", "dep:tracing-subscriber"] 27 | hot-reload = ["dioxus-native/hot-reload"] 28 | 29 | [dependencies] 30 | dioxus-native = { workspace = true, features = [ 31 | "svg", 32 | "system-fonts", 33 | "net", 34 | "clipboard", 35 | "prelude", 36 | ] } 37 | blitz-traits = { workspace = true } 38 | blitz-dom = { workspace = true, features = ["woff-rust", "parallel-construct"] } 39 | blitz-net = { workspace = true } 40 | blitz-html = { workspace = true } 41 | linebender_resource_handle = { workspace = true } 42 | tracing-subscriber = { workspace = true, optional = true } 43 | webbrowser = { workspace = true } 44 | -------------------------------------------------------------------------------- /packages/dioxus-native-dom/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dioxus-native-dom" 3 | version = "0.7.0" 4 | authors = ["Jonathan Kelley", "Nico Burns"] 5 | edition = "2021" 6 | description = "Core headless native renderer for Dioxus based on blitz" 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/DioxusLabs/dioxus/" 9 | homepage = "https://dioxuslabs.com/learn/0.7/getting_started" 10 | keywords = ["dom", "ui", "gui", "react"] 11 | 12 | [features] 13 | default = ["accessibility", "svg", "system-fonts"] 14 | 15 | # DOM features 16 | svg = ["blitz-dom/svg"] 17 | floats = ["blitz-dom/floats"] 18 | incremental = ["blitz-dom/incremental"] 19 | accessibility = ["blitz-dom/accessibility"] 20 | tracing = ["dep:tracing", "blitz-dom/tracing"] 21 | system-fonts = ["blitz-dom/system_fonts"] 22 | autofocus = ["blitz-dom/autofocus"] 23 | 24 | [dependencies] 25 | # Blitz dependencies 26 | blitz-dom = { workspace = true, default-features = false } 27 | blitz-traits = { workspace = true } 28 | 29 | # DioxusLabs dependencies 30 | dioxus-core = { workspace = true } 31 | dioxus-html = { workspace = true } 32 | 33 | # Windowing & Input 34 | keyboard-types = { workspace = true } 35 | 36 | 37 | # Other dependencies 38 | tracing = { workspace = true, optional = true } 39 | rustc-hash = { workspace = true } 40 | futures-util = { workspace = true } 41 | 42 | [dev-dependencies] 43 | dioxus = { workspace = true } 44 | 45 | [package.metadata.docs.rs] 46 | all-features = true 47 | -------------------------------------------------------------------------------- /wpt/runner/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wpt" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license.workspace = true 6 | rust-version.workspace = true 7 | publish = false 8 | 9 | [features] 10 | default = ["cpu"] 11 | gpu = ["dep:anyrender_vello"] 12 | cpu = ["dep:anyrender_vello_cpu"] 13 | incremental = ["blitz-dom/incremental"] 14 | 15 | [dependencies] 16 | blitz-dom = { workspace = true, features = ["svg", "floats", "system_fonts", "woff-rust"] } 17 | blitz-html = {workspace = true } 18 | blitz-traits = { workspace = true } 19 | blitz-paint = { workspace = true, features = ["default"] } 20 | anyrender = { workspace = true } 21 | anyrender_vello = { workspace = true, optional = true } 22 | anyrender_vello_cpu = { workspace = true, optional = true } 23 | 24 | taffy = { workspace = true } 25 | parley = { workspace = true } 26 | image = { workspace = true, features = ["png"] } 27 | url = { workspace = true } 28 | data-url = { workspace = true } 29 | png = { version = "0.17" } 30 | glob = { version = "0.3.1" } 31 | dify = { version = "0.7.4", default-features = false } 32 | env_logger = { version = "0.11.5" } 33 | owo-colors = "4.1.0" 34 | log = "0.4.22" 35 | regex = "1.11.1" 36 | rayon = { workspace = true } 37 | thread_local = { workspace = true } 38 | bitflags = "2.6.0" 39 | pollster = "0.4.0" 40 | atomic_float = "1" 41 | supports-hyperlinks = "3.1.0" 42 | terminal-link = "0.1.0" 43 | wptreport = { version = "0.0.5", default-features = false } 44 | os_info = "3.10.0" 45 | serde_json = "1.0.140" 46 | -------------------------------------------------------------------------------- /packages/blitz-dom/src/url.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::str::FromStr; 3 | 4 | use style::servo_arc::Arc as ServoArc; 5 | use style::stylesheets::UrlExtraData; 6 | use url::Url; 7 | 8 | #[derive(Clone)] 9 | pub(crate) struct DocumentUrl { 10 | base_url: ServoArc, 11 | } 12 | 13 | impl DocumentUrl { 14 | /// Create a stylo `UrlExtraData` from the URL 15 | pub(crate) fn url_extra_data(&self) -> UrlExtraData { 16 | UrlExtraData(ServoArc::clone(&self.base_url)) 17 | } 18 | 19 | pub(crate) fn resolve_relative(&self, raw: &str) -> Option { 20 | self.base_url.join(raw).ok() 21 | } 22 | } 23 | 24 | impl Default for DocumentUrl { 25 | fn default() -> Self { 26 | Self::from_str("data:text/css;charset=utf-8;base64,").unwrap() 27 | } 28 | } 29 | impl FromStr for DocumentUrl { 30 | type Err = ::Err; 31 | fn from_str(s: &str) -> Result { 32 | let base_url = ServoArc::new(Url::parse(s)?); 33 | Ok(Self { base_url }) 34 | } 35 | } 36 | impl From for DocumentUrl { 37 | fn from(base_url: Url) -> Self { 38 | Self { 39 | base_url: ServoArc::new(base_url), 40 | } 41 | } 42 | } 43 | impl From> for DocumentUrl { 44 | fn from(base_url: ServoArc) -> Self { 45 | Self { base_url } 46 | } 47 | } 48 | impl Deref for DocumentUrl { 49 | type Target = Url; 50 | fn deref(&self) -> &Self::Target { 51 | &self.base_url 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/wpt.yml: -------------------------------------------------------------------------------- 1 | name: WPT 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 11 | cancel-in-progress: true 12 | 13 | env: 14 | RUSTDOCFLAGS: "-D warnings" 15 | CARGO_REGISTRIES_CRATES_IO_PROTOCOL: "sparse" 16 | WPT_DIR: "./wpt/tests" 17 | 18 | jobs: 19 | wpt: 20 | name: "Run WPT Tests" 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: dtolnay/rust-toolchain@stable 25 | - uses: Swatinem/rust-cache@v2 26 | with: 27 | key: "blitz-wpt-linux" 28 | cache-all-crates: "true" 29 | save-if: ${{ github.ref == 'refs/heads/main' }} 30 | - uses: awalsh128/cache-apt-pkgs-action@latest 31 | with: 32 | packages: libfontconfig1-dev 33 | version: 1.0 34 | - name: Clone WPT tests 35 | run: git clone --depth 1 --single-branch https://github.com/web-platform-tests/wpt ./wpt/tests 36 | - name: Build WPT runner 37 | run: cargo build -rp wpt 38 | - name: Run WPT tests 39 | run: cargo run -rp wpt css 40 | - name: Compress report (zstd) 41 | run: zstd -22 --ultra -o ./wpt/output/wptreport.json.zst ./wpt/output/wptreport.json 42 | - uses: actions/upload-artifact@v4 43 | with: 44 | name: wpt-report.json.zst 45 | path: ./wpt/output/wptreport.json.zst 46 | compression: 0 # We are already using zstd compression -------------------------------------------------------------------------------- /examples/outline.rs: -------------------------------------------------------------------------------- 1 | // background: rgb(2,0,36); 2 | // background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(9,9,121,1) 35%, rgba(0,212,255,1) 100%); 3 | 4 | use dioxus::prelude::*; 5 | 6 | fn main() { 7 | dioxus_native::launch(app); 8 | } 9 | 10 | fn app() -> Element { 11 | rsx! { 12 | style { {CSS} } 13 | div { "padd " } 14 | div { "padd " } 15 | div { "padd " } 16 | div { "padd " } 17 | div { class: "colorful", id: "a", 18 | div { "Dioxus12312312312321" } 19 | div { "Dioxus12312312312321" } 20 | div { "Dioxus12312312312321" } 21 | div { "Dioxus12312312312321" } 22 | div { "Dioxus12312312312321" } 23 | div { "Dioxus12312312312321" } 24 | } 25 | } 26 | } 27 | 28 | const CSS: &str = r#" 29 | .colorful { 30 | border-right-color: #000; 31 | border-left-color: #ff0; 32 | border-top-color: #F01; 33 | border-bottom-color: #0f0; 34 | } 35 | #a { 36 | height:300px; 37 | background-color: gray; 38 | border: 1px solid black; 39 | // border-radius: 50px 20px; 40 | border-top-color: red; 41 | // padding:20px; 42 | // margin:20px; 43 | // border-radius: 10px; 44 | border-radius: 10% 30% 50% 70%; 45 | border-left: 4px solid #000; 46 | border-top: 10px solid #ff0; 47 | border-right: 3px solid #F01; 48 | border-bottom: 9px solid #0f0; 49 | // box-shadow: 10px 10px gray; 50 | 51 | margin: 100px; 52 | outline-width: 50px; 53 | outline-style: solid; 54 | outline-color: blue; 55 | } 56 | "#; 57 | -------------------------------------------------------------------------------- /packages/blitz-html/src/html_document.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | use crate::DocumentHtmlParser; 4 | 5 | use blitz_dom::{BaseDocument, DEFAULT_CSS, Document, DocumentConfig}; 6 | 7 | pub struct HtmlDocument { 8 | inner: BaseDocument, 9 | } 10 | 11 | // Implement DocumentLike and required traits for HtmlDocument 12 | impl Deref for HtmlDocument { 13 | type Target = BaseDocument; 14 | fn deref(&self) -> &BaseDocument { 15 | &self.inner 16 | } 17 | } 18 | impl DerefMut for HtmlDocument { 19 | fn deref_mut(&mut self) -> &mut Self::Target { 20 | &mut self.inner 21 | } 22 | } 23 | impl From for BaseDocument { 24 | fn from(doc: HtmlDocument) -> BaseDocument { 25 | doc.inner 26 | } 27 | } 28 | impl Document for HtmlDocument { 29 | fn as_any_mut(&mut self) -> &mut dyn std::any::Any { 30 | self 31 | } 32 | } 33 | 34 | impl HtmlDocument { 35 | /// Parse HTML (or XHTML) into an [`HtmlDocument`] 36 | pub fn from_html(html: &str, mut config: DocumentConfig) -> Self { 37 | if let Some(ss) = &mut config.ua_stylesheets { 38 | if !ss.iter().any(|s| s == DEFAULT_CSS) { 39 | ss.push(String::from(DEFAULT_CSS)); 40 | } 41 | } 42 | let mut doc = BaseDocument::new(config); 43 | let mut mutr = doc.mutate(); 44 | DocumentHtmlParser::parse_into_mutator(&mut mutr, html); 45 | drop(mutr); 46 | HtmlDocument { inner: doc } 47 | } 48 | 49 | /// Convert the [`HtmlDocument`] into it's inner [`BaseDocument`] 50 | pub fn into_inner(self) -> BaseDocument { 51 | self.into() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/assets/svg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/blitz-dom/src/node/attributes.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | use markup5ever::QualName; 4 | 5 | /// A tag attribute, e.g. `class="test"` in `
`. 6 | #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)] 7 | pub struct Attribute { 8 | /// The name of the attribute (e.g. the `class` in `
`) 9 | pub name: QualName, 10 | /// The value of the attribute (e.g. the `"test"` in `
`) 11 | pub value: String, 12 | } 13 | 14 | #[derive(Clone, Debug)] 15 | pub struct Attributes { 16 | inner: Vec, 17 | } 18 | 19 | impl Attributes { 20 | pub fn new(inner: Vec) -> Self { 21 | Self { inner } 22 | } 23 | 24 | pub fn set(&mut self, name: QualName, value: &str) { 25 | let existing_attr = self.inner.iter_mut().find(|a| a.name == name); 26 | if let Some(existing_attr) = existing_attr { 27 | existing_attr.value.clear(); 28 | existing_attr.value.push_str(value); 29 | } else { 30 | self.push(Attribute { 31 | name: name.clone(), 32 | value: value.to_string(), 33 | }); 34 | } 35 | } 36 | 37 | pub fn remove(&mut self, name: &QualName) -> Option { 38 | let idx = self.inner.iter().position(|attr| attr.name == *name); 39 | idx.map(|idx| self.inner.remove(idx)) 40 | } 41 | } 42 | 43 | impl Deref for Attributes { 44 | type Target = Vec; 45 | fn deref(&self) -> &Self::Target { 46 | &self.inner 47 | } 48 | } 49 | impl DerefMut for Attributes { 50 | fn deref_mut(&mut self) -> &mut Self::Target { 51 | &mut self.inner 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/assets/animation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Google 8 | 54 | 55 | 56 |
57 |
58 | 59 |
Submit
60 | 61 | -------------------------------------------------------------------------------- /examples/inner_html.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyrender_vello::VelloWindowRenderer; 4 | use blitz_dom::DocumentConfig; 5 | use blitz_html::{HtmlDocument, HtmlProvider}; 6 | use blitz_shell::{BlitzApplication, BlitzShellEvent, WindowConfig, create_default_event_loop}; 7 | 8 | pub fn main() { 9 | // Create renderer 10 | 11 | // Parse the HTML into a Blitz document 12 | let mut doc = HtmlDocument::from_html( 13 | HTML, 14 | DocumentConfig { 15 | html_parser_provider: Some(Arc::new(HtmlProvider) as _), 16 | ..Default::default() 17 | }, 18 | ); 19 | 20 | let node_id = doc.query_selector("#content_area").unwrap().unwrap(); 21 | doc.mutate().set_inner_html(node_id, INNER_HTML); 22 | doc.resolve(0.0); 23 | 24 | // Create the Winit application and window 25 | let event_loop = create_default_event_loop::(); 26 | let mut application = BlitzApplication::new(event_loop.create_proxy()); 27 | let renderer = VelloWindowRenderer::new(); 28 | let window = WindowConfig::new(Box::new(doc), renderer); 29 | application.add_window(window); 30 | 31 | // Run event loop 32 | event_loop.run_app(&mut application).unwrap() 33 | } 34 | 35 | static HTML: &str = r#" 36 | 37 | 38 | 39 | 45 | 46 | 47 |
48 |

Inner HTML Demo

49 |

Text set with innerHTML should appear below:

50 |
51 |
52 | 53 | 54 | "#; 55 | 56 | static INNER_HTML: &str = r#" 57 | INNER HTML TEXT HERE 58 | "#; 59 | -------------------------------------------------------------------------------- /packages/blitz-paint/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Paint a [`blitz_dom::BaseDocument`] by pushing [`anyrender`] drawing commands into 2 | //! an impl [`anyrender::PaintScene`]. 3 | 4 | #![allow(clippy::collapsible_if)] 5 | 6 | mod color; 7 | mod debug_overlay; 8 | mod gradient; 9 | mod kurbo_css; 10 | mod layers; 11 | mod render; 12 | mod sizing; 13 | mod text; 14 | 15 | use anyrender::PaintScene; 16 | use blitz_dom::BaseDocument; 17 | use layers::reset_layer_stats; 18 | use render::BlitzDomPainter; 19 | 20 | /// Paint a [`blitz_dom::BaseDocument`] by pushing drawing commands into 21 | /// an impl [`anyrender::PaintScene`]. 22 | /// 23 | /// This function assumes that the styles and layout in the [`BaseDocument`] are already 24 | /// resolved. Please ensure that this is the case before trying to paint. 25 | /// 26 | /// The implementation of [`PaintScene`] is responsible for handling the commands that are pushed into it. 27 | /// Generally this will involve executing them to draw a rasterized image/texture. But in some cases it may choose to 28 | /// transform them to a vector format (e.g. SVG/PDF) or serialize them in raw form for later use. 29 | pub fn paint_scene( 30 | scene: &mut impl PaintScene, 31 | dom: &BaseDocument, 32 | scale: f64, 33 | width: u32, 34 | height: u32, 35 | ) { 36 | reset_layer_stats(); 37 | 38 | let devtools = *dom.devtools(); 39 | let generator = BlitzDomPainter { 40 | dom, 41 | scale, 42 | width, 43 | height, 44 | initial_x: 0.0, 45 | initial_y: 0.0, 46 | devtools, 47 | }; 48 | generator.paint_scene(scene); 49 | 50 | // println!( 51 | // "Rendered using {} clips (depth: {}) (wanted: {})", 52 | // CLIPS_USED.load(atomic::Ordering::SeqCst), 53 | // CLIP_DEPTH_USED.load(atomic::Ordering::SeqCst), 54 | // CLIPS_WANTED.load(atomic::Ordering::SeqCst) 55 | // ); 56 | } 57 | -------------------------------------------------------------------------------- /packages/blitz-traits/src/navigation.rs: -------------------------------------------------------------------------------- 1 | //! Abstractions allow embedders to handle link clicks and form submissions 2 | 3 | use http::{HeaderMap, Method}; 4 | use url::Url; 5 | 6 | use crate::net::{Body, Request}; 7 | 8 | /// An abstraction to allow embedders to hook into "navigation events" such as clicking a link 9 | /// or submitting a form. 10 | pub trait NavigationProvider: Send + Sync + 'static { 11 | fn navigate_to(&self, options: NavigationOptions); 12 | } 13 | 14 | pub struct DummyNavigationProvider; 15 | 16 | impl NavigationProvider for DummyNavigationProvider { 17 | fn navigate_to(&self, _options: NavigationOptions) { 18 | // Default impl: do nothing 19 | } 20 | } 21 | 22 | #[non_exhaustive] 23 | #[derive(Debug, Clone)] 24 | pub struct NavigationOptions { 25 | /// The URL to navigate to 26 | pub url: Url, 27 | 28 | pub content_type: String, 29 | 30 | /// Source document for the navigation 31 | pub source_document: usize, 32 | 33 | pub method: Method, 34 | 35 | pub document_resource: Body, 36 | } 37 | 38 | impl NavigationOptions { 39 | pub fn new(url: Url, content_type: String, source_document: usize) -> Self { 40 | Self { 41 | url, 42 | content_type, 43 | source_document, 44 | method: Method::GET, 45 | document_resource: Body::Empty, 46 | } 47 | } 48 | pub fn set_document_resource(mut self, document_resource: Body) -> Self { 49 | self.document_resource = document_resource; 50 | self 51 | } 52 | 53 | pub fn set_method(mut self, method: Method) -> Self { 54 | self.method = method; 55 | self 56 | } 57 | 58 | pub fn into_request(self) -> Request { 59 | Request { 60 | url: self.url, 61 | method: self.method, 62 | content_type: self.content_type, 63 | headers: HeaderMap::new(), 64 | body: self.document_resource, 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/blitz-paint/src/kurbo_css/mod.rs: -------------------------------------------------------------------------------- 1 | //! A rounded rect closer to the browser 2 | //! Implemented in such a way that splits the border into 4 parts at the midway of each radius 3 | //! 4 | //! This object is meant to be updated only when the data changes - BezPaths are expensive! 5 | //! 6 | //! Can I just say, this is a lot of work for a border 7 | //! HTML/css is annoyingly wild 8 | 9 | use kurbo::{Insets, Vec2}; 10 | 11 | mod css_box; 12 | mod non_uniform_radii; 13 | 14 | pub use css_box::CssBox; 15 | pub use non_uniform_radii::NonUniformRoundedRectRadii; 16 | 17 | #[derive(Debug, Clone, Copy)] 18 | pub enum Edge { 19 | Top, 20 | Right, 21 | Bottom, 22 | Left, 23 | } 24 | 25 | #[derive(Debug, Clone, Copy)] 26 | pub(crate) enum Corner { 27 | TopLeft, 28 | TopRight, 29 | BottomLeft, 30 | BottomRight, 31 | } 32 | 33 | #[derive(Debug, Clone, Copy, PartialEq)] 34 | #[allow(clippy::enum_variant_names, reason = "Use CSS standard terminology")] 35 | pub(crate) enum CssBoxKind { 36 | OutlineBox, 37 | BorderBox, 38 | PaddingBox, 39 | ContentBox, 40 | } 41 | 42 | #[derive(Debug, Clone, Copy)] 43 | pub(crate) enum Direction { 44 | Clockwise, 45 | Anticlockwise, 46 | } 47 | 48 | fn add_insets(a: Insets, b: Insets) -> Insets { 49 | Insets { 50 | x0: a.x0 + b.x0, 51 | y0: a.y0 + b.y0, 52 | x1: a.x1 + b.x1, 53 | y1: a.y1 + b.y1, 54 | } 55 | } 56 | 57 | #[inline(always)] 58 | fn get_corner_insets(insets: Insets, corner: Corner) -> Vec2 { 59 | match corner { 60 | Corner::TopLeft => Vec2 { 61 | x: insets.x0, 62 | y: insets.y0, 63 | }, 64 | Corner::TopRight => Vec2 { 65 | x: insets.x1, 66 | y: insets.y0, 67 | }, 68 | Corner::BottomLeft => Vec2 { 69 | x: insets.x0, 70 | y: insets.y1, 71 | }, 72 | Corner::BottomRight => Vec2 { 73 | x: insets.x1, 74 | y: insets.y1, 75 | }, 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/blitz-dom/src/events/ime.rs: -------------------------------------------------------------------------------- 1 | use blitz_traits::events::{BlitzImeEvent, BlitzInputEvent, DomEvent, DomEventData}; 2 | 3 | use crate::BaseDocument; 4 | 5 | pub(crate) fn handle_ime_event( 6 | doc: &mut BaseDocument, 7 | event: BlitzImeEvent, 8 | mut dispatch_event: F, 9 | ) { 10 | if let Some(node_id) = doc.focus_node_id { 11 | let node = &mut doc.nodes[node_id]; 12 | let text_input_data = node 13 | .data 14 | .downcast_element_mut() 15 | .and_then(|el| el.text_input_data_mut()); 16 | if let Some(input_data) = text_input_data { 17 | let editor = &mut input_data.editor; 18 | let mut font_ctx = doc.font_ctx.lock().unwrap(); 19 | let mut driver = editor.driver(&mut font_ctx, &mut doc.layout_ctx); 20 | 21 | match event { 22 | BlitzImeEvent::Enabled => { /* Do nothing */ } 23 | BlitzImeEvent::Disabled => { 24 | driver.clear_compose(); 25 | doc.shell_provider.request_redraw(); 26 | } 27 | BlitzImeEvent::Commit(text) => { 28 | driver.insert_or_replace_selection(&text); 29 | let value = input_data.editor.raw_text().to_string(); 30 | dispatch_event(DomEvent::new( 31 | node_id, 32 | DomEventData::Input(BlitzInputEvent { value }), 33 | )); 34 | doc.shell_provider.request_redraw(); 35 | } 36 | BlitzImeEvent::Preedit(text, cursor) => { 37 | if text.is_empty() { 38 | driver.clear_compose(); 39 | } else { 40 | driver.set_compose(&text, cursor); 41 | } 42 | doc.shell_provider.request_redraw(); 43 | } 44 | } 45 | println!("Sent ime event to {node_id}"); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/dioxus-native-dom/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(docsrs, feature(doc_cfg))] 2 | 3 | //! Core headless native renderer for Dioxus. 4 | //! 5 | //! ## Feature flags 6 | //! - `default`: Enables the features listed below. 7 | //! - `accessibility`: Enables [`accesskit`](https://docs.rs/accesskit/latest/accesskit/) accessibility support. 8 | //! - `hot-reload`: Enables hot-reloading of Dioxus RSX. 9 | //! - `menu`: Enables the [`muda`](https://docs.rs/muda/latest/muda/) menubar. 10 | //! - `tracing`: Enables tracing support. 11 | 12 | mod dioxus_document; 13 | mod events; 14 | mod mutation_writer; 15 | mod sub_document; 16 | pub use blitz_dom::DocumentConfig; 17 | pub use dioxus_document::DioxusDocument; 18 | pub use sub_document::SubDocumentAttr; 19 | 20 | use blitz_dom::{ns, LocalName, Namespace, QualName}; 21 | type NodeId = usize; 22 | 23 | pub(crate) fn qual_name(local_name: &str, namespace: Option<&str>) -> QualName { 24 | QualName { 25 | prefix: None, 26 | ns: namespace.map(Namespace::from).unwrap_or(ns!(html)), 27 | local: LocalName::from(local_name), 28 | } 29 | } 30 | 31 | // Syntax sugar to make tracing calls less noisy in function below 32 | macro_rules! trace { 33 | ($pattern:literal) => {{ 34 | #[cfg(feature = "tracing")] 35 | tracing::debug!($pattern); 36 | }}; 37 | ($pattern:literal, $item1:expr) => {{ 38 | #[cfg(feature = "tracing")] 39 | tracing::debug!($pattern, $item1); 40 | }}; 41 | ($pattern:literal, $item1:expr, $item2:expr) => {{ 42 | #[cfg(feature = "tracing")] 43 | tracing::debug!($pattern, $item1, $item2); 44 | }}; 45 | ($pattern:literal, $item1:expr, $item2:expr, $item3:expr) => {{ 46 | #[cfg(feature = "tracing")] 47 | tracing::debug!($pattern, $item1, $item2); 48 | }}; 49 | ($pattern:literal, $item1:expr, $item2:expr, $item3:expr, $item4:expr) => {{ 50 | #[cfg(feature = "tracing")] 51 | tracing::debug!($pattern, $item1, $item2, $item3, $item4); 52 | }}; 53 | } 54 | pub(crate) use trace; 55 | -------------------------------------------------------------------------------- /wpt/runner/src/panic_backtrace.rs: -------------------------------------------------------------------------------- 1 | use std::backtrace::{Backtrace, BacktraceStatus}; 2 | use std::{cell::Cell, panic::PanicHookInfo}; 3 | 4 | thread_local! { 5 | static STASHED_PANIC_INFO: Cell> = const { Cell::new(None) }; 6 | } 7 | 8 | pub fn take_stashed_panic_info() -> Option { 9 | STASHED_PANIC_INFO.take() 10 | } 11 | 12 | pub struct StashedPanicInfo { 13 | pub message: Option, 14 | pub file: String, 15 | pub line: u32, 16 | pub column: u32, 17 | pub backtrace: Backtrace, 18 | } 19 | 20 | pub fn stash_panic_handler(info: &PanicHookInfo) { 21 | let backtrace = Backtrace::force_capture(); 22 | let payload = info.payload(); 23 | let location = info.location().unwrap(); 24 | 25 | let str_msg = payload.downcast_ref::<&str>().map(|s| s.to_string()); 26 | let string_msg = payload.downcast_ref::().map(|s| s.to_owned()); 27 | let message = str_msg.or(string_msg); 28 | 29 | let info = StashedPanicInfo { 30 | message, 31 | backtrace, 32 | file: location.file().to_owned(), 33 | line: location.line(), 34 | column: location.column(), 35 | }; 36 | 37 | STASHED_PANIC_INFO.with(move |b| b.set(Some(info))); 38 | } 39 | 40 | #[inline(never)] 41 | pub fn backtrace_cutoff R>(cb: T) -> R { 42 | cb() 43 | } 44 | 45 | pub fn trim_backtrace(backtrace: &Backtrace) -> Option { 46 | if backtrace.status() != BacktraceStatus::Captured { 47 | return None; 48 | } 49 | 50 | let string_backtrace = backtrace.to_string(); 51 | let mut filtered = String::with_capacity(string_backtrace.len()); 52 | let mut started = false; 53 | 54 | for line in string_backtrace.lines() { 55 | if line.contains("wpt::panic_backtrace::backtrace_cutoff") { 56 | break; 57 | } 58 | 59 | if started { 60 | filtered.push_str(line); 61 | filtered.push('\n'); 62 | } 63 | 64 | if line.contains("core::panicking::panic") { 65 | started = true; 66 | } 67 | } 68 | 69 | Some(filtered) 70 | } 71 | -------------------------------------------------------------------------------- /packages/blitz-shell/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blitz-shell" 3 | description = "Blitz application shell" 4 | documentation = "https://docs.rs/blitz-shell" 5 | version = "0.2.2" 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | categories.workspace = true 10 | edition.workspace = true 11 | rust-version.workspace = true 12 | 13 | [features] 14 | default = ["accessibility", "clipboard", "file_dialog"] 15 | accessibility = [ 16 | "dep:accesskit", 17 | "dep:accesskit_winit", 18 | "blitz-dom/accessibility", 19 | ] 20 | clipboard = ["dep:arboard"] 21 | tracing = ["dep:tracing", "blitz-dom/tracing"] 22 | file_dialog = ["dep:rfd"] 23 | # Enables a data-uri-only NetProvider. Only needed if you aren't using the regular NetProvider 24 | data-uri = ["dep:data-url"] 25 | 26 | [dependencies] 27 | # Blitz dependencies 28 | blitz-traits = { workspace = true } 29 | blitz-dom = { workspace = true } 30 | blitz-paint = { workspace = true } 31 | anyrender = { workspace = true } 32 | 33 | # Windowing & Input 34 | winit = { workspace = true } 35 | keyboard-types = { workspace = true } 36 | accesskit = { workspace = true, optional = true } 37 | accesskit_winit = { workspace = true, optional = true } 38 | 39 | # Other dependencies 40 | tracing = { workspace = true, optional = true } 41 | futures-util = { workspace = true } 42 | data-url = { workspace = true, optional = true } 43 | 44 | [target.'cfg(target_os = "android")'.dependencies] 45 | android-activity = { version = "0.6.0", features = ["native-activity"] } 46 | 47 | [target.'cfg(any(target_os = "windows",target_os = "macos",target_os = "linux",target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies] 48 | arboard = { workspace = true, optional = true } 49 | 50 | 51 | [target.'cfg(any(target_os = "windows",target_os = "macos",target_os = "linux",target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))'.dependencies] 52 | rfd = { workspace = true, optional = true, features = ["xdg-portal", "tokio"] } 53 | 54 | [package.metadata.docs.rs] 55 | all-features = true 56 | rustdoc-args = ["--cfg", "docsrs"] 57 | -------------------------------------------------------------------------------- /packages/blitz-paint/src/kurbo_css/non_uniform_radii.rs: -------------------------------------------------------------------------------- 1 | use kurbo::Vec2; 2 | use std::ops::{Mul, MulAssign}; 3 | 4 | /// Radii for each corner of a non-uniform rounded rectangle. 5 | /// 6 | /// The use of `top` as in `top_left` assumes a y-down coordinate space. Piet 7 | /// (and Druid by extension) uses a y-down coordinate space, but Kurbo also 8 | /// supports a y-up coordinate space, in which case `top_left` would actually 9 | /// refer to the bottom-left corner, and vice versa. Top may not always 10 | /// actually be the top, but `top` corners will always have a smaller y-value 11 | /// than `bottom` corners. 12 | #[derive(Clone, Copy, Default, Debug, PartialEq)] 13 | // #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] 14 | // #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 15 | pub struct NonUniformRoundedRectRadii { 16 | /// The radii of the top-left corner. 17 | pub top_left: Vec2, 18 | /// The radii of the top-right corner. 19 | pub top_right: Vec2, 20 | /// The radii of the bottom-right corner. 21 | pub bottom_right: Vec2, 22 | /// The radii of the bottom-left corner. 23 | pub bottom_left: Vec2, 24 | } 25 | 26 | impl NonUniformRoundedRectRadii { 27 | pub fn average(&self) -> f64 { 28 | (self.top_left.x 29 | + self.top_left.y 30 | + self.top_right.x 31 | + self.top_right.y 32 | + self.bottom_left.x 33 | + self.bottom_left.y 34 | + self.bottom_right.x 35 | + self.bottom_right.y) 36 | / 8.0 37 | } 38 | } 39 | 40 | impl Mul for NonUniformRoundedRectRadii { 41 | type Output = Self; 42 | 43 | fn mul(self, rhs: f64) -> Self::Output { 44 | Self { 45 | top_left: self.top_left * rhs, 46 | top_right: self.top_right * rhs, 47 | bottom_right: self.bottom_right * rhs, 48 | bottom_left: self.bottom_left * rhs, 49 | } 50 | } 51 | } 52 | 53 | impl MulAssign for NonUniformRoundedRectRadii { 54 | fn mul_assign(&mut self, rhs: f64) { 55 | self.top_left *= rhs; 56 | self.top_right *= rhs; 57 | self.bottom_left *= rhs; 58 | self.bottom_right *= rhs; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/blitz-paint/src/layers.rs: -------------------------------------------------------------------------------- 1 | use anyrender::PaintScene; 2 | use kurbo::{Affine, Shape}; 3 | use peniko::Mix; 4 | use std::sync::atomic::{AtomicUsize, Ordering}; 5 | 6 | const LAYER_LIMIT: usize = 1024; 7 | 8 | static LAYERS_USED: AtomicUsize = AtomicUsize::new(0); 9 | static LAYER_DEPTH: AtomicUsize = AtomicUsize::new(0); 10 | static LAYER_DEPTH_USED: AtomicUsize = AtomicUsize::new(0); 11 | static LAYERS_WANTED: AtomicUsize = AtomicUsize::new(0); 12 | 13 | pub(crate) fn reset_layer_stats() { 14 | LAYERS_USED.store(0, Ordering::SeqCst); 15 | LAYERS_WANTED.store(0, Ordering::SeqCst); 16 | LAYER_DEPTH.store(0, Ordering::SeqCst); 17 | LAYER_DEPTH_USED.store(0, Ordering::SeqCst); 18 | } 19 | 20 | pub(crate) fn maybe_with_layer( 21 | scene: &mut S, 22 | condition: bool, 23 | opacity: f32, 24 | transform: Affine, 25 | shape: &impl Shape, 26 | paint_layer: F, 27 | ) { 28 | let layer_used = maybe_push_layer(scene, condition, opacity, transform, shape); 29 | paint_layer(scene); 30 | maybe_pop_layer(scene, layer_used); 31 | } 32 | 33 | pub(crate) fn maybe_push_layer( 34 | scene: &mut impl PaintScene, 35 | condition: bool, 36 | opacity: f32, 37 | transform: Affine, 38 | shape: &impl Shape, 39 | ) -> bool { 40 | if !condition { 41 | return false; 42 | } 43 | LAYERS_WANTED.fetch_add(1, Ordering::SeqCst); 44 | 45 | // Check if clips are above limit 46 | let layers_available = LAYERS_USED.load(Ordering::SeqCst) <= LAYER_LIMIT; 47 | if !layers_available { 48 | return false; 49 | } 50 | let blend_mode = if opacity == 1.0 { 51 | #[allow(deprecated)] 52 | Mix::Clip 53 | } else { 54 | Mix::Normal 55 | }; 56 | 57 | // Actually push the clip layer 58 | scene.push_layer(blend_mode, opacity, transform, shape); 59 | 60 | // Update accounting 61 | LAYERS_USED.fetch_add(1, Ordering::SeqCst); 62 | let depth = LAYER_DEPTH.fetch_add(1, Ordering::SeqCst) + 1; 63 | LAYER_DEPTH_USED.fetch_max(depth, Ordering::SeqCst); 64 | 65 | true 66 | } 67 | 68 | pub(crate) fn maybe_pop_layer(scene: &mut impl PaintScene, condition: bool) { 69 | if condition { 70 | scene.pop_layer(); 71 | LAYER_DEPTH.fetch_sub(1, Ordering::SeqCst); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/assets/border.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 63 | 64 | 65 |
hi
66 |
Dioxus12312312312321
hi
67 |
Dioxus12312312312321
hi
68 |
Dioxus12312312312321
hi
69 |
Dioxus12312312312321
hi
70 |
Dioxus12312312312321
hi
71 | 72 | 73 | -------------------------------------------------------------------------------- /examples/assets/animated_layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | CSS Animation demo 8 | 45 | 46 | 47 |
48 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam faucibus tortor orci, nec tempus nisl rhoncus nec. Suspendisse egestas dictum suscipit. Suspendisse semper lectus ex, id pretium nisl sollicitudin mattis. Morbi ligula augue, auctor in aliquet quis, venenatis consectetur enim. Mauris non ligula euismod, semper mauris nec, mattis lectus. Integer facilisis ultricies orci, a mollis velit sodales a. Sed ac nulla vitae massa rhoncus fermentum. Pellentesque maximus neque nisi, eu malesuada eros tincidunt vitae. Morbi sed condimentum risus. Ut vel dui lacinia, pulvinar odio sit amet, lacinia ligula. Sed posuere odio augue, quis maximus sapien ullamcorper nec. 49 |
50 | 51 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | 2 | ## Build/lint commands 3 | 4 | check: 5 | cargo check --workspace 6 | 7 | clippy: 8 | cargo clippy --workspace 9 | 10 | fmt: 11 | cargo fmt --all 12 | 13 | small: 14 | cargo build --profile small -p counter --no-default-features --features cpu,system_fonts 15 | 16 | ## WPT test runner 17 | 18 | wpt *ARGS: 19 | cargo run --release --package wpt {{ARGS}} 20 | 21 | browser: 22 | cargo run --release --package browser 23 | 24 | browser-with-perf: 25 | cargo run --release --package browser --features log_frame_times,log_phase_times 26 | 27 | browskia: 28 | cargo run -rp browser --no-default-features --features skia,floats,incremental,cookies,cache,log_frame_times,log_phase_times 29 | 30 | ## Browser 31 | 32 | screenshot *ARGS: 33 | cargo run --release --example screenshot {{ARGS}} 34 | 35 | open *ARGS: 36 | cargo run --release --package readme --features log_frame_times,log_phase_times {{ARGS}} 37 | 38 | dev *ARGS: 39 | cargo run --package readme --features log_frame_times,log_phase_times {{ARGS}} 40 | 41 | incr *ARGS: 42 | cargo run --release --package readme --features incremental,comrak,floats,log_frame_times,log_phase_times {{ARGS}} 43 | 44 | cpu *ARGS: 45 | cargo run --release --package readme --no-default-features --features cpu,comrak,incremental,floats,log_frame_times,log_phase_times {{ARGS}} 46 | 47 | hybrid *ARGS: 48 | cargo run --release --package readme --no-default-features --features hybrid,comrak,incremental,floats,log_frame_times,log_phase_times {{ARGS}} 49 | 50 | skia *ARGS: 51 | cargo run --release --package readme --no-default-features --features skia,comrak,incremental,floats,log_frame_times,log_phase_times {{ARGS}} 52 | 53 | skia-pixels *ARGS: 54 | cargo run --release --package readme --no-default-features --features skia-pixels,comrak,floats,incremental,log_frame_times,log_phase_times {{ARGS}} 55 | 56 | skia-softbuffer *ARGS: 57 | cargo run --release --package readme --no-default-features --features skia-softbuffer,comrak,floats,incremental,log_frame_times,log_phase_times {{ARGS}} 58 | 59 | ## TodoMVC commands 60 | 61 | todomvc *ARGS: 62 | cargo run --release --package todomvc {{ARGS}} 63 | 64 | todoskia *ARGS: 65 | cargo run --release --package todomvc {{ARGS}} --no-default-features --features skia 66 | 67 | ## Ops 68 | 69 | bump *ARGS: 70 | cargo run --release --package bump {{ARGS}} -------------------------------------------------------------------------------- /packages/blitz-paint/src/sizing.rs: -------------------------------------------------------------------------------- 1 | use style::properties::generated::longhands::object_fit::computed_value::T as ObjectFit; 2 | 3 | pub(crate) fn compute_object_fit( 4 | container_size: taffy::Size, 5 | object_size: Option>, 6 | object_fit: ObjectFit, 7 | ) -> taffy::Size { 8 | match object_fit { 9 | ObjectFit::None => object_size.unwrap_or(container_size), 10 | ObjectFit::Fill => container_size, 11 | ObjectFit::Cover => compute_object_fit_cover(container_size, object_size), 12 | ObjectFit::Contain => compute_object_fit_contain(container_size, object_size), 13 | ObjectFit::ScaleDown => { 14 | let contain_size = compute_object_fit_contain(container_size, object_size); 15 | match object_size { 16 | Some(object_size) if object_size.width < contain_size.width => object_size, 17 | _ => contain_size, 18 | } 19 | } 20 | } 21 | } 22 | 23 | fn compute_object_fit_contain( 24 | container_size: taffy::Size, 25 | object_size: Option>, 26 | ) -> taffy::Size { 27 | let Some(object_size) = object_size else { 28 | return container_size; 29 | }; 30 | 31 | let x_ratio = container_size.width / object_size.width; 32 | let y_ratio = container_size.height / object_size.height; 33 | 34 | let ratio = match (x_ratio < 1.0, y_ratio < 1.0) { 35 | (true, true) => x_ratio.min(y_ratio), 36 | (true, false) => x_ratio, 37 | (false, true) => y_ratio, 38 | (false, false) => x_ratio.min(y_ratio), 39 | }; 40 | 41 | object_size.map(|dim| dim * ratio) 42 | } 43 | 44 | fn compute_object_fit_cover( 45 | container_size: taffy::Size, 46 | object_size: Option>, 47 | ) -> taffy::Size { 48 | let Some(object_size) = object_size else { 49 | return container_size; 50 | }; 51 | 52 | let x_ratio = container_size.width / object_size.width; 53 | let y_ratio = container_size.height / object_size.height; 54 | 55 | let ratio = match (x_ratio < 1.0, y_ratio < 1.0) { 56 | (true, true) => x_ratio.max(y_ratio), 57 | (true, false) => y_ratio, 58 | (false, true) => x_ratio, 59 | (false, false) => x_ratio.max(y_ratio), 60 | }; 61 | 62 | object_size.map(|dim| dim * ratio) 63 | } 64 | -------------------------------------------------------------------------------- /apps/readme/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "readme" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license.workspace = true 6 | rust-version.workspace = true 7 | publish = false 8 | 9 | [features] 10 | default = ["gpu", "comrak", "floats"] 11 | 12 | # Renderers 13 | gpu = ["dep:anyrender_vello"] 14 | hybrid = ["dep:anyrender_vello_hybrid"] 15 | skia = ["dep:anyrender_skia"] 16 | skia-pixels = ["dep:anyrender_skia", "anyrender_skia/pixels_window_renderer"] 17 | skia-softbuffer = ["dep:anyrender_skia", "anyrender_skia/softbuffer_window_renderer"] 18 | cpu = ["cpu-pixels"] 19 | cpu-pixels = ["cpu-base", "anyrender_vello_cpu/pixels_window_renderer"] 20 | cpu-softbuffer = ["cpu-base", "anyrender_vello_cpu/softbuffer_window_renderer"] 21 | cpu-base = ["dep:anyrender_vello_cpu"] 22 | 23 | avif = ["dep:image", "image?/avif-native"] 24 | comrak = ["dep:comrak"] 25 | pulldown_cmark = ["dep:pulldown-cmark"] 26 | floats = ["blitz-dom/floats"] 27 | cache = ["blitz-net/cache"] 28 | log_frame_times = [ 29 | "anyrender_vello_cpu?/log_frame_times", 30 | "anyrender_vello_hybrid?/log_frame_times", 31 | "anyrender_vello?/log_frame_times", 32 | "anyrender_skia?/log_frame_times", 33 | ] 34 | log_phase_times = ["blitz-dom/log_phase_times"] 35 | incremental = ["blitz-dom/incremental"] 36 | 37 | [dependencies] 38 | blitz-traits = { workspace = true } 39 | blitz-dom = { workspace = true, features = ["default", "parallel-construct"] } 40 | blitz-html = { workspace = true } 41 | blitz-paint = { workspace = true, features = ["default"] } 42 | blitz-net = { workspace = true, features = ["cookies", "debug_log"] } 43 | blitz-shell = { workspace = true, features = ["tracing", "default"] } 44 | anyrender_vello = { workspace = true, optional = true } 45 | anyrender_skia = { workspace = true, optional = true } 46 | anyrender_vello_cpu = { workspace = true, features = ["multithreading"], optional = true } 47 | anyrender_vello_hybrid = { workspace = true, optional = true } 48 | 49 | tokio = { workspace = true, features = ["rt", "rt-multi-thread"] } 50 | reqwest = { workspace = true } 51 | url = { workspace = true } 52 | winit = { workspace = true } 53 | comrak = { version = "0.49", default-features = false, optional = true } 54 | pulldown-cmark = { version = "0.13", default-features = false, features = ["html"], optional = true } 55 | image = { workspace = true, default-features = false, optional = true } 56 | notify = "8.0.0" -------------------------------------------------------------------------------- /packages/dioxus-native/src/contexts.rs: -------------------------------------------------------------------------------- 1 | use blitz_shell::BlitzShellEvent; 2 | use dioxus_document::{Document, NoOpDocument}; 3 | use winit::{event_loop::EventLoopProxy, window::WindowId}; 4 | 5 | use crate::DioxusNativeEvent; 6 | 7 | pub struct DioxusNativeDocument { 8 | pub(crate) proxy: EventLoopProxy, 9 | pub(crate) window: WindowId, 10 | } 11 | 12 | impl DioxusNativeDocument { 13 | pub(crate) fn new(proxy: EventLoopProxy, window: WindowId) -> Self { 14 | Self { proxy, window } 15 | } 16 | } 17 | 18 | impl Document for DioxusNativeDocument { 19 | fn eval(&self, _js: String) -> dioxus_document::Eval { 20 | NoOpDocument.eval(_js) 21 | } 22 | 23 | fn create_head_element( 24 | &self, 25 | name: &str, 26 | attributes: &[(&str, String)], 27 | contents: Option, 28 | ) { 29 | let window = self.window; 30 | _ = self.proxy.send_event(BlitzShellEvent::embedder_event( 31 | DioxusNativeEvent::CreateHeadElement { 32 | name: name.to_string(), 33 | attributes: attributes 34 | .iter() 35 | .map(|(name, value)| (name.to_string(), value.clone())) 36 | .collect(), 37 | contents, 38 | window, 39 | }, 40 | )); 41 | } 42 | 43 | fn set_title(&self, title: String) { 44 | self.create_head_element("title", &[], Some(title)); 45 | } 46 | 47 | fn create_meta(&self, props: dioxus_document::MetaProps) { 48 | let attributes = props.attributes(); 49 | self.create_head_element("meta", &attributes, None); 50 | } 51 | 52 | fn create_script(&self, props: dioxus_document::ScriptProps) { 53 | let attributes = props.attributes(); 54 | self.create_head_element("script", &attributes, props.script_contents().ok()); 55 | } 56 | 57 | fn create_style(&self, props: dioxus_document::StyleProps) { 58 | let attributes = props.attributes(); 59 | self.create_head_element("style", &attributes, props.style_contents().ok()); 60 | } 61 | 62 | fn create_link(&self, props: dioxus_document::LinkProps) { 63 | let attributes = props.attributes(); 64 | self.create_head_element("link", &attributes, None); 65 | } 66 | 67 | fn create_head_component(&self) -> bool { 68 | true 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/assets/object_fit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 |

Fill

13 | 14 | 15 | 16 | 17 |

Contain (50px)

18 | 19 | 20 | 21 | 22 |

Cover (50px)

23 | 24 | 25 | 26 | 27 |
28 | 29 |

Contain (100px)

30 | 31 | 32 | 33 | 34 |

Cover (100px)

35 | 36 | 37 | 38 | 39 |

Contain (200px)

40 | 41 | 42 | 43 | 44 |
45 | 46 |

Cover (200px)

47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /packages/blitz-shell/src/event.rs: -------------------------------------------------------------------------------- 1 | use blitz_traits::navigation::NavigationOptions; 2 | use futures_util::task::ArcWake; 3 | use std::{any::Any, sync::Arc}; 4 | use winit::{event_loop::EventLoopProxy, window::WindowId}; 5 | 6 | #[cfg(feature = "accessibility")] 7 | use accesskit_winit::{Event as AccessKitEvent, WindowEvent as AccessKitWindowEvent}; 8 | 9 | #[derive(Debug, Clone)] 10 | pub enum BlitzShellEvent { 11 | Poll { 12 | window_id: WindowId, 13 | }, 14 | 15 | RequestRedraw { 16 | doc_id: usize, 17 | }, 18 | 19 | /// An accessibility event from `accesskit`. 20 | #[cfg(feature = "accessibility")] 21 | Accessibility { 22 | window_id: WindowId, 23 | data: Arc, 24 | }, 25 | 26 | /// An arbitary event from the Blitz embedder 27 | Embedder(Arc), 28 | 29 | /// Navigate to another URL (triggered by e.g. clicking a link) 30 | Navigate(Box), 31 | 32 | /// Navigate to another URL (triggered by e.g. clicking a link) 33 | NavigationLoad { 34 | url: String, 35 | contents: String, 36 | retain_scroll_position: bool, 37 | is_md: bool, 38 | }, 39 | } 40 | impl BlitzShellEvent { 41 | pub fn embedder_event(value: T) -> Self { 42 | let boxed = Arc::new(value) as Arc; 43 | Self::Embedder(boxed) 44 | } 45 | } 46 | 47 | #[cfg(feature = "accessibility")] 48 | impl From for BlitzShellEvent { 49 | fn from(value: AccessKitEvent) -> Self { 50 | Self::Accessibility { 51 | window_id: value.window_id, 52 | data: Arc::new(value.window_event), 53 | } 54 | } 55 | } 56 | 57 | /// Create a waker that will send a poll event to the event loop. 58 | /// 59 | /// This lets the VirtualDom "come up for air" and process events while the main thread is blocked by the WebView. 60 | /// 61 | /// All other IO lives in the Tokio runtime, 62 | pub fn create_waker(proxy: &EventLoopProxy, id: WindowId) -> std::task::Waker { 63 | struct DomHandle { 64 | proxy: EventLoopProxy, 65 | id: WindowId, 66 | } 67 | impl ArcWake for DomHandle { 68 | fn wake_by_ref(arc_self: &Arc) { 69 | _ = arc_self.proxy.send_event(BlitzShellEvent::Poll { 70 | window_id: arc_self.id, 71 | }) 72 | } 73 | } 74 | 75 | let proxy = proxy.clone(); 76 | futures_util::task::waker(Arc::new(DomHandle { id, proxy })) 77 | } 78 | -------------------------------------------------------------------------------- /examples/assets/pseudo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 69 | 70 | 71 |
Hello
72 | 73 |
Hello
74 | 80 |
  
81 | 82 | -------------------------------------------------------------------------------- /examples/restyle.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | use tokio::time::{Duration, sleep}; 3 | 4 | fn main() { 5 | // Turn on the runtime and enter it 6 | let rt = tokio::runtime::Builder::new_multi_thread() 7 | .enable_all() 8 | .build() 9 | .unwrap(); 10 | let _guard = rt.enter(); 11 | 12 | dioxus_native::launch(app); 13 | } 14 | 15 | #[derive(Copy, Clone)] 16 | enum AnimationState { 17 | Increasing, 18 | Decreasing, 19 | } 20 | 21 | impl std::ops::Not for AnimationState { 22 | type Output = Self; 23 | fn not(self) -> Self::Output { 24 | match self { 25 | AnimationState::Increasing => AnimationState::Decreasing, 26 | AnimationState::Decreasing => AnimationState::Increasing, 27 | } 28 | } 29 | } 30 | 31 | const MIN_SIZE: i32 = 12; 32 | const MAX_SIZE: i32 = 120; 33 | 34 | fn app() -> Element { 35 | let mut size = use_signal(|| 12); 36 | let mut direction = use_signal(|| AnimationState::Increasing); 37 | let mut running = use_signal(|| true); 38 | 39 | // `use_future` will spawn an infinitely direction future that can be started and stopped 40 | use_future(move || async move { 41 | loop { 42 | if running() { 43 | match direction() { 44 | AnimationState::Increasing => size += 1, 45 | AnimationState::Decreasing => size -= 1, 46 | } 47 | 48 | let size = *size.read(); 49 | if size <= MIN_SIZE { 50 | *direction.write() = AnimationState::Increasing; 51 | } 52 | if size >= MAX_SIZE { 53 | *direction.write() = AnimationState::Decreasing; 54 | } 55 | } 56 | 57 | sleep(Duration::from_millis(16)).await; 58 | } 59 | }); 60 | rsx! { 61 | div { 62 | style { {STYLES} } 63 | h1 { "Current size: {size}" } 64 | div { style: "display: flex", 65 | div { class: "button", onclick: move |_| running.toggle(), "Start/Stop" } 66 | div { class: "button", onclick: move |_| size.set(12), "Reset the size" } 67 | } 68 | p { style: "font-size: {size}px", "Animate Font Size" } 69 | } 70 | } 71 | } 72 | 73 | static STYLES: &str = r#" 74 | .button { 75 | padding: 6px; 76 | border: 1px solid #999; 77 | margin-left: 12px; 78 | cursor: pointer; 79 | } 80 | 81 | .button:hover { 82 | background: #999; 83 | color: white; 84 | } 85 | "#; 86 | -------------------------------------------------------------------------------- /packages/blitz-dom/src/stylo_to_cursor_icon.rs: -------------------------------------------------------------------------------- 1 | use cursor_icon::CursorIcon; 2 | use style::values::computed::ui::CursorKind as StyloCursorKind; 3 | 4 | pub(crate) fn stylo_to_cursor_icon(cursor: StyloCursorKind) -> CursorIcon { 5 | match cursor { 6 | StyloCursorKind::None => todo!("set the cursor to none"), 7 | StyloCursorKind::Default => CursorIcon::Default, 8 | StyloCursorKind::Pointer => CursorIcon::Pointer, 9 | StyloCursorKind::ContextMenu => CursorIcon::ContextMenu, 10 | StyloCursorKind::Help => CursorIcon::Help, 11 | StyloCursorKind::Progress => CursorIcon::Progress, 12 | StyloCursorKind::Wait => CursorIcon::Wait, 13 | StyloCursorKind::Cell => CursorIcon::Cell, 14 | StyloCursorKind::Crosshair => CursorIcon::Crosshair, 15 | StyloCursorKind::Text => CursorIcon::Text, 16 | StyloCursorKind::VerticalText => CursorIcon::VerticalText, 17 | StyloCursorKind::Alias => CursorIcon::Alias, 18 | StyloCursorKind::Copy => CursorIcon::Copy, 19 | StyloCursorKind::Move => CursorIcon::Move, 20 | StyloCursorKind::NoDrop => CursorIcon::NoDrop, 21 | StyloCursorKind::NotAllowed => CursorIcon::NotAllowed, 22 | StyloCursorKind::Grab => CursorIcon::Grab, 23 | StyloCursorKind::Grabbing => CursorIcon::Grabbing, 24 | StyloCursorKind::EResize => CursorIcon::EResize, 25 | StyloCursorKind::NResize => CursorIcon::NResize, 26 | StyloCursorKind::NeResize => CursorIcon::NeResize, 27 | StyloCursorKind::NwResize => CursorIcon::NwResize, 28 | StyloCursorKind::SResize => CursorIcon::SResize, 29 | StyloCursorKind::SeResize => CursorIcon::SeResize, 30 | StyloCursorKind::SwResize => CursorIcon::SwResize, 31 | StyloCursorKind::WResize => CursorIcon::WResize, 32 | StyloCursorKind::EwResize => CursorIcon::EwResize, 33 | StyloCursorKind::NsResize => CursorIcon::NsResize, 34 | StyloCursorKind::NeswResize => CursorIcon::NeswResize, 35 | StyloCursorKind::NwseResize => CursorIcon::NwseResize, 36 | StyloCursorKind::ColResize => CursorIcon::ColResize, 37 | StyloCursorKind::RowResize => CursorIcon::RowResize, 38 | StyloCursorKind::AllScroll => CursorIcon::AllScroll, 39 | StyloCursorKind::ZoomIn => CursorIcon::ZoomIn, 40 | StyloCursorKind::ZoomOut => CursorIcon::ZoomOut, 41 | StyloCursorKind::Auto => { 42 | // todo: we should be the ones determining this based on the UA? 43 | // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor 44 | 45 | CursorIcon::Default 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/dioxus-native/src/assets.rs: -------------------------------------------------------------------------------- 1 | use blitz_shell::BlitzShellNetWaker; 2 | use std::sync::Arc; 3 | 4 | use blitz_shell::BlitzShellEvent; 5 | use blitz_traits::net::{NetHandler, NetProvider, Request}; 6 | use winit::event_loop::EventLoopProxy; 7 | 8 | pub struct DioxusNativeNetProvider { 9 | inner_net_provider: Option>, 10 | } 11 | 12 | #[allow(unused)] 13 | impl DioxusNativeNetProvider { 14 | pub fn shared(proxy: EventLoopProxy) -> Arc { 15 | Arc::new(Self::new(proxy)) as Arc 16 | } 17 | 18 | pub fn new(proxy: EventLoopProxy) -> Self { 19 | let net_waker = Some(BlitzShellNetWaker::shared(proxy)); 20 | 21 | #[cfg(feature = "net")] 22 | let inner_net_provider = Some(blitz_net::Provider::shared(net_waker.clone())); 23 | #[cfg(all(feature = "data-uri", not(feature = "net")))] 24 | let inner_net_provider = Some(blitz_shell::DataUriNetProvider::shared(net_waker.clone())); 25 | #[cfg(all(not(feature = "data-uri"), not(feature = "net")))] 26 | let inner_net_provider = None; 27 | 28 | Self { inner_net_provider } 29 | } 30 | 31 | pub fn with_inner(proxy: EventLoopProxy, inner: Arc) -> Self { 32 | Self { 33 | inner_net_provider: Some(inner), 34 | } 35 | } 36 | 37 | pub fn inner(&self) -> Option<&Arc> { 38 | self.inner_net_provider.as_ref() 39 | } 40 | } 41 | 42 | impl NetProvider for DioxusNativeNetProvider { 43 | fn fetch(&self, doc_id: usize, request: Request, handler: Box) { 44 | if request.url.scheme() == "dioxus" { 45 | #[allow(clippy::single_match)] // cfg'd code 46 | match dioxus_asset_resolver::native::serve_asset(request.url.path()) { 47 | Ok(res) => { 48 | #[cfg(feature = "tracing")] 49 | tracing::trace!("fetching asset from file system success {request:#?}"); 50 | handler.bytes(request.url.to_string(), res.into_body().into()) 51 | } 52 | Err(_) => { 53 | #[cfg(feature = "tracing")] 54 | tracing::warn!("fetching asset from file system error {request:#?}"); 55 | } 56 | } 57 | } else if let Some(inner) = &self.inner_net_provider { 58 | inner.fetch(doc_id, request, handler); 59 | } else { 60 | #[cfg(feature = "tracing")] 61 | tracing::warn!("net feature not enabled, cannot fetch {request:#?}"); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/blitz-shell/src/net.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use blitz_traits::net::NetWaker; 4 | use winit::event_loop::EventLoopProxy; 5 | 6 | use crate::BlitzShellEvent; 7 | 8 | /// A NetWaker that wakes up our winit event loop 9 | pub struct BlitzShellNetWaker(EventLoopProxy); 10 | 11 | impl BlitzShellNetWaker { 12 | pub fn new(proxy: EventLoopProxy) -> Self { 13 | Self(proxy) 14 | } 15 | 16 | pub fn shared(proxy: EventLoopProxy) -> Arc { 17 | Arc::new(Self(proxy)) 18 | } 19 | } 20 | impl NetWaker for BlitzShellNetWaker { 21 | fn wake(&self, doc_id: usize) { 22 | self.0 23 | .send_event(BlitzShellEvent::RequestRedraw { doc_id }) 24 | .unwrap() 25 | } 26 | } 27 | 28 | #[cfg(feature = "data-uri")] 29 | mod data_uri_net_provider { 30 | //! Data-URI only networking for Blitz 31 | //! 32 | //! Provides an implementation of the [`blitz_traits::net::NetProvider`] trait. 33 | 34 | use blitz_traits::net::{Bytes, NetHandler, NetProvider, NetWaker, Request}; 35 | use data_url::DataUrl; 36 | use std::sync::Arc; 37 | 38 | pub struct DataUriNetProvider { 39 | #[allow(unused)] 40 | waker: Option>, 41 | } 42 | impl DataUriNetProvider { 43 | pub fn new(waker: Option>) -> Self { 44 | Self { waker } 45 | } 46 | pub fn shared(waker: Option>) -> Arc { 47 | Arc::new(Self::new(waker)) 48 | } 49 | } 50 | 51 | impl NetProvider for DataUriNetProvider { 52 | fn fetch(&self, _doc_id: usize, request: Request, handler: Box) { 53 | // let callback = &self.resource_callback; 54 | match request.url.scheme() { 55 | "data" => { 56 | let Ok(data_url) = DataUrl::process(request.url.as_str()) else { 57 | // callback.call(doc_id, Err(Some(String::from("Failed to parse data uri")))); 58 | return; 59 | }; 60 | let Ok(decoded) = data_url.decode_to_vec() else { 61 | // callback.call(doc_id, Err(Some(String::from("Failed to decode data uri")))); 62 | return; 63 | }; 64 | let bytes = Bytes::from(decoded.0); 65 | handler.bytes(request.url.to_string(), bytes); 66 | } 67 | _ => { 68 | // callback.call(doc_id, Err(Some(String::from("UnsupportedScheme")))); 69 | } 70 | }; 71 | } 72 | } 73 | } 74 | #[cfg(feature = "data-uri")] 75 | pub use data_uri_net_provider::DataUriNetProvider; 76 | -------------------------------------------------------------------------------- /examples/assets/servo-new-reduced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Servo items reduced. 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |
16 | 17 |

Embeddable

18 |

Servo provides a WebView API so other applications can use it to embed web content.

19 |
20 |
21 | 22 |

Memory-safe

23 |

Servo takes advantage of the memory safety features of the Rust programming language, resulting in fewer vulnerabilities related to memory and concurrency.

24 |
25 |
26 | 27 |

Modular

28 |

Built with a modular architecture and powered by widely-used Rust crates, Servo makes it easier to customize and adapt a high-performance browser engine to your needs.

29 |
30 |
31 |
32 |
33 | 34 |

Parallel

35 |

Servo uses concurrency and parallelism for faster and more energy-efficient rendering of web content on multi-core devices.

36 |
37 |
38 | 39 |

Cross platform

40 |

Servo has multi-platform support, including Windows, macOS, Linux, Android, and OpenHarmony. In addition, Servo can be ported and adapted to embedded devices.

41 |
42 |
43 | 44 |

Independent

45 |

Servo is a project managed with open governance under Linux Foundation Europe through our Technical Steering Committee.

46 |
47 |
48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/wgpu_texture/src/html.rs: -------------------------------------------------------------------------------- 1 | use anyrender_vello::{VelloRendererOptions, VelloWindowRenderer}; 2 | use blitz_dom::{qual_name, DocumentConfig}; 3 | use blitz_html::HtmlDocument; 4 | use blitz_shell::{create_default_event_loop, BlitzApplication, BlitzShellEvent, WindowConfig}; 5 | 6 | use crate::{limits, DemoPaintSource, FEATURES, STYLES}; 7 | 8 | pub fn launch_html() { 9 | // Create renderer 10 | let mut renderer = VelloWindowRenderer::with_options(VelloRendererOptions { 11 | features: Some(FEATURES), 12 | limits: Some(limits()), 13 | ..VelloRendererOptions::default() 14 | }); 15 | 16 | // Create custom paint source and register it with the renderer 17 | let demo_paint_source = Box::new(DemoPaintSource::new()); 18 | let paint_source_id = renderer.register_custom_paint_source(demo_paint_source); 19 | 20 | // Parse the HTML into a Blitz document 21 | let html = HTML.replace("{{STYLES_PLACEHOLDER}}", STYLES); 22 | let mut doc = HtmlDocument::from_html(&html, DocumentConfig::default()); 23 | 24 | // Set the "src" attribute on the `` element to the paint source's id 25 | // (`` is proprietary blitz extension to HTML) 26 | let canvas_node_id = doc.query_selector("#demo-canvas").unwrap().unwrap(); 27 | let src_attr = qual_name!("src"); 28 | let src_str = paint_source_id.to_string(); 29 | doc.mutate() 30 | .set_attribute(canvas_node_id, src_attr, &src_str); 31 | 32 | // Create the Winit application and window 33 | let event_loop = create_default_event_loop::(); 34 | let mut application = BlitzApplication::new(event_loop.create_proxy()); 35 | let window = WindowConfig::new(Box::new(doc), renderer); 36 | application.add_window(window); 37 | 38 | // Run event loop 39 | event_loop.run_app(&mut application).unwrap() 40 | } 41 | 42 | static HTML: &str = r#" 43 | 44 | 45 | 46 | 49 | 50 | 51 |
52 |
53 |

Overlay

54 |

This overlay demonstrates that the custom WGPU content can be rendered beneath layers of HTML content

55 |
56 |
57 |

Underlay

58 |

This underlay demonstrates that the custom WGPU content can be rendered above layers and blended with the content underneath

59 |
60 |

Blitz WGPU Demo

61 |
62 | 63 |
64 |
65 | 66 | 67 | "#; 68 | -------------------------------------------------------------------------------- /examples/wgpu_texture/src/dioxus_native.rs: -------------------------------------------------------------------------------- 1 | use color::{palette::css::WHITE, parse_color}; 2 | use dioxus_native::prelude::*; 3 | use dioxus_native::use_wgpu; 4 | use std::any::Any; 5 | 6 | use crate::{limits, Color, DemoMessage, DemoPaintSource, FEATURES, STYLES}; 7 | 8 | pub fn launch_dx_native() { 9 | let config: Vec> = vec![Box::new(FEATURES), Box::new(limits())]; 10 | dioxus_native::launch_cfg(app, Vec::new(), config); 11 | } 12 | 13 | fn app() -> Element { 14 | let mut show_cube = use_signal(|| true); 15 | 16 | let color_str = use_signal(|| String::from("red")); 17 | let color = use_memo(move || { 18 | parse_color(&color_str()) 19 | .map(|c| c.to_alpha_color()) 20 | .unwrap_or(WHITE) 21 | .split() 22 | .0 23 | }); 24 | 25 | use_effect(move || println!("{:?}", color().components)); 26 | 27 | rsx!( 28 | style { {STYLES} } 29 | div { id: "overlay", 30 | h2 { "Control Panel" } 31 | button { onclick: move |_| *show_cube.write() = !show_cube(), 32 | if show_cube() { 33 | "Hide cube" 34 | } else { 35 | "Show cube" 36 | } 37 | } 38 | br {} 39 | ColorControl { label: "Color:", color_str } 40 | p { 41 | "This overlay demonstrates that the custom WGPU content can be rendered beneath layers of HTML content" 42 | } 43 | } 44 | div { id: "underlay", 45 | h2 { "Underlay" } 46 | p { 47 | "This underlay demonstrates that the custom WGPU content can be rendered above layers and blended with the content underneath" 48 | } 49 | } 50 | header { 51 | h2 { "Blitz WGPU Demo" } 52 | } 53 | if show_cube() { 54 | SpinningCube { color } 55 | } 56 | ) 57 | } 58 | 59 | #[component] 60 | fn ColorControl(label: &'static str, color_str: Signal) -> Element { 61 | rsx!( 62 | div { class: "color-control", 63 | {label} 64 | input { 65 | value: color_str(), 66 | oninput: move |evt| { *color_str.write() = evt.value() }, 67 | } 68 | } 69 | ) 70 | } 71 | 72 | #[component] 73 | fn SpinningCube(color: Memo) -> Element { 74 | // Create custom paint source and register it with the renderer 75 | let paint_source = DemoPaintSource::new(); 76 | let sender = paint_source.sender(); 77 | let paint_source_id = use_wgpu(move || paint_source); 78 | 79 | use_effect(move || { 80 | sender.send(DemoMessage::SetColor(color())).unwrap(); 81 | }); 82 | 83 | rsx!( 84 | div { id: "canvas-container", 85 | canvas { id: "demo-canvas", "src": paint_source_id } 86 | } 87 | ) 88 | } 89 | -------------------------------------------------------------------------------- /packages/blitz-dom/src/accessibility.rs: -------------------------------------------------------------------------------- 1 | use crate::{BaseDocument, Node as BlitzDomNode, local_name}; 2 | use accesskit::{Node as AccessKitNode, NodeId, Role, Tree, TreeUpdate}; 3 | 4 | impl BaseDocument { 5 | pub fn build_accessibility_tree(&self) -> TreeUpdate { 6 | let mut nodes = std::collections::HashMap::new(); 7 | let mut window = AccessKitNode::new(Role::Window); 8 | 9 | self.visit(|node_id, node| { 10 | let parent = node 11 | .parent 12 | .and_then(|parent_id| nodes.get_mut(&parent_id)) 13 | .map(|(_, parent)| parent) 14 | .unwrap_or(&mut window); 15 | let (id, builder) = self.build_accessibility_node(node, parent); 16 | 17 | nodes.insert(node_id, (id, builder)); 18 | }); 19 | 20 | let mut nodes: Vec<_> = nodes 21 | .into_iter() 22 | .map(|(_, (id, node))| (id, node)) 23 | .collect(); 24 | nodes.push((NodeId(u64::MAX), window)); 25 | 26 | let tree = Tree::new(NodeId(u64::MAX)); 27 | TreeUpdate { 28 | nodes, 29 | tree: Some(tree), 30 | focus: NodeId(self.focus_node_id.map(|id| id as u64).unwrap_or(u64::MAX)), 31 | } 32 | } 33 | 34 | fn build_accessibility_node( 35 | &self, 36 | node: &BlitzDomNode, 37 | parent: &mut AccessKitNode, 38 | ) -> (NodeId, AccessKitNode) { 39 | let id = NodeId(node.id as u64); 40 | 41 | let mut builder = AccessKitNode::default(); 42 | if node.id == 0 { 43 | builder.set_role(Role::Window) 44 | } else if let Some(element_data) = node.element_data() { 45 | let name = element_data.name.local.to_string(); 46 | 47 | // TODO match more roles 48 | let role = match &*name { 49 | "button" => Role::Button, 50 | "div" => Role::GenericContainer, 51 | "header" => Role::Header, 52 | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => Role::Heading, 53 | "p" => Role::Paragraph, 54 | "section" => Role::Section, 55 | "input" => { 56 | let ty = element_data.attr(local_name!("type")).unwrap_or("text"); 57 | match ty { 58 | "number" => Role::NumberInput, 59 | "checkbox" => Role::CheckBox, 60 | _ => Role::TextInput, 61 | } 62 | } 63 | _ => Role::Unknown, 64 | }; 65 | 66 | builder.set_role(role); 67 | builder.set_html_tag(name); 68 | } else if node.is_text_node() { 69 | builder.set_role(Role::TextRun); 70 | builder.set_value(node.text_content()); 71 | parent.push_labelled_by(id) 72 | } 73 | 74 | parent.push_child(id); 75 | 76 | (id, builder) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/blitz-dom/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blitz-dom" 3 | description = "Blitz DOM implementation" 4 | documentation = "https://docs.rs/blitz-dom" 5 | version = "0.2.2" 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | categories.workspace = true 10 | edition.workspace = true 11 | rust-version.workspace = true 12 | 13 | [features] 14 | default = [ 15 | "svg", 16 | "woff-rust", 17 | "accessibility", 18 | "system_fonts", 19 | "file_input", 20 | ] 21 | tracing = ["dep:tracing"] 22 | svg = ["dep:usvg"] 23 | # WOFF decoding using the "woff" crate which binds to C libraries 24 | woff-c = ["dep:woff"] 25 | # WOFF decoding using the "wuff" crate which is pure Rust 26 | woff-rust = ["dep:wuff"] 27 | accessibility = ["accesskit"] 28 | system_fonts = ["parley/system"] 29 | autofocus = [] 30 | floats = ["taffy/float_layout", "stylo_taffy/floats"] 31 | file_input = [] 32 | incremental = [] 33 | parallel-construct = [] 34 | log_phase_times = ["debug_timer/enable"] 35 | 36 | [dependencies] 37 | # Blitz dependencies 38 | blitz-traits = { workspace = true } 39 | stylo_taffy = { workspace = true, features = ["default"] } 40 | debug_timer = { workspace = true } 41 | 42 | # Servo dependencies 43 | style = { workspace = true } 44 | selectors = { workspace = true } 45 | cssparser = { workspace = true } 46 | style_config = { workspace = true } 47 | style_traits = { workspace = true } 48 | style_dom = { workspace = true } 49 | app_units = { workspace = true } 50 | euclid = { workspace = true } 51 | atomic_refcell = { workspace = true } 52 | markup5ever = { workspace = true } 53 | smallvec = { workspace = true } 54 | 55 | # DioxusLabs dependencies 56 | taffy = { workspace = true } 57 | 58 | # Linebender/Fontations dependencies 59 | accesskit = { workspace = true, optional = true } 60 | parley = { workspace = true } 61 | skrifa = { workspace = true } 62 | linebender_resource_handle = { workspace = true } 63 | color = { workspace = true } 64 | 65 | # Other dependencies 66 | slab = { workspace = true } 67 | bitflags = { workspace = true } 68 | tracing = { workspace = true, optional = true } 69 | fastrand = { workspace = true } 70 | rayon = { workspace = true } 71 | 72 | # Media & Decoding 73 | image = { workspace = true } 74 | usvg = { workspace = true, optional = true } 75 | woff = { workspace = true, optional = true, features = ["version1", "version2"] } 76 | wuff = { workspace = true, optional = true } 77 | html-escape = { workspace = true } 78 | percent-encoding = { workspace = true } 79 | 80 | # IO & Networking 81 | url = { workspace = true } 82 | 83 | # Input 84 | keyboard-types = { workspace = true } 85 | cursor-icon = { workspace = true } 86 | thread_local.workspace = true 87 | 88 | # HACK: Blitz doesn't need to depend on objc2 directly. But this feature flag is necessary 89 | # to prevent debug builds from panicking. 90 | [target.'cfg(any(target_vendor = "apple"))'.dependencies] 91 | objc2 = { version = "0.6", features = ["disable-encoding-assertions"] } 92 | -------------------------------------------------------------------------------- /examples/counter/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Drive the renderer from Dioxus 2 | use dioxus_native::prelude::*; 3 | 4 | fn main() { 5 | dioxus_native::launch(app); 6 | } 7 | 8 | fn app() -> Element { 9 | let mut count = use_signal(|| 0); 10 | 11 | rsx! { 12 | div { class: "container", 13 | style { {CSS} } 14 | h1 { class: "header", "Count: {count}" } 15 | div { class: "buttons", 16 | button { 17 | class: "counter-button btn-green", 18 | onclick: move |_| { count += 1 }, 19 | "Increment" 20 | } 21 | button { 22 | class: "counter-button btn-red", 23 | onclick: move |_| { count -= 1 }, 24 | "Decrement" 25 | } 26 | } 27 | button { 28 | class: "counter-button btn-blue", 29 | onclick: move |_| { count.set(0) }, 30 | "Reset" 31 | } 32 | } 33 | } 34 | } 35 | 36 | const CSS: &str = r#" 37 | 38 | html, body { 39 | padding: 0; 40 | margin: 0; 41 | background-color: white; 42 | } 43 | 44 | .header { 45 | background-color: pink; 46 | padding: 20px; 47 | line-height: 1; 48 | font-family: sans-serif; 49 | } 50 | 51 | .container { 52 | display: flex; 53 | flex-direction: column; 54 | justify-content: center; 55 | align-items: center; 56 | height: 100vh; 57 | width: 100vw; 58 | background: linear-gradient(217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70.71%), 59 | linear-gradient(127deg, rgba(0,255,0,.8), rgba(0,255,0,0) 70.71%), 60 | linear-gradient(336deg, rgba(0,0,255,.8), rgba(0,0,255,0) 70.71%); 61 | } 62 | 63 | .buttons { 64 | display: flex; 65 | flex-direction: row; 66 | justify-content: center; 67 | align-items: center; 68 | margin: 20px 0; 69 | } 70 | 71 | .counter-button { 72 | margin: 0 10px; 73 | padding: 10px 20px; 74 | border-radius: 5px; 75 | font-size: 1.5rem; 76 | cursor: pointer; 77 | line-height: 1; 78 | font-family: sans-serif; 79 | border-width: 2px; 80 | border-style: solid; 81 | } 82 | .counter-button:focus { 83 | outline: 4px solid black; 84 | } 85 | 86 | .btn-green { 87 | background-color: green; 88 | border-color: green; 89 | color: white; 90 | } 91 | .btn-green:hover { 92 | color: green; 93 | background-color: white; 94 | } 95 | 96 | .btn-red { 97 | background-color: red; 98 | border-color: red; 99 | color: white; 100 | } 101 | .btn-red:hover { 102 | color: red; 103 | background-color: white; 104 | } 105 | 106 | .btn-blue { 107 | background-color: blue; 108 | border-color: blue; 109 | color: white; 110 | } 111 | .btn-blue:hover { 112 | color: blue; 113 | background-color: white; 114 | } 115 | 116 | 117 | "#; 118 | -------------------------------------------------------------------------------- /examples/flex.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Servo doesn't have: 3 | - space-evenly? 4 | - gap 5 | */ 6 | 7 | use dioxus::prelude::*; 8 | 9 | fn main() { 10 | dioxus_native::launch(app); 11 | } 12 | 13 | fn app() -> Element { 14 | rsx! { 15 | div { 16 | style { {CSS} } 17 | div { 18 | h2 { "justify-content" } 19 | for row in [ 20 | "flex-start", 21 | "flex-end", 22 | "center", 23 | "space-between", 24 | "space-around", 25 | "space-evenly", 26 | ] 27 | { 28 | h3 { "{row}" } 29 | div { id: "container", justify_content: "{row}", 30 | div { class: "floater", "__1__" } 31 | div { class: "floater", "__2__" } 32 | div { class: "floater", "__3__" } 33 | } 34 | } 35 | } 36 | h3 { "CSS Grid Test" } 37 | div { id: "grid_container", 38 | for _ in 0..3 { 39 | div { class: "floater", "__1__" } 40 | div { class: "floater", "__2__" } 41 | div { class: "floater", "__3__" } 42 | div { class: "floater", "__4__" } 43 | div { class: "floater", "__5__" } 44 | div { class: "floater", "__6__" } 45 | div { class: "floater", "__7__" } 46 | div { class: "floater", "__8__" } 47 | div { class: "floater", "__9__" } 48 | div { class: "floater", "__0__" } 49 | div { class: "floater", "__A__" } 50 | div { class: "floater", "__B__" } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | const CSS: &str = r#" 58 | #container { 59 | flex: 1 1 auto; 60 | flex-direction: row; 61 | background-color: gray; 62 | border: 1px solid black; 63 | border-top-color: red; 64 | border-left: 4px solid #000; 65 | border-top: 10px solid #ff0; 66 | border-right: 3px solid #F01; 67 | border-bottom: 9px solid #0f0; 68 | box-shadow: 10px 10px gray; 69 | 70 | 71 | outline-style: solid; 72 | outline-color: blue; 73 | border-radius: 50px 20px; 74 | padding: 10px; 75 | margin: 5px; 76 | display: flex; 77 | gap: 10px; 78 | } 79 | 80 | div { 81 | font-family: sans-serif; 82 | } 83 | 84 | h3 { 85 | font-size: 2em; 86 | } 87 | 88 | #grid_container { 89 | display: grid; 90 | grid-template-columns: 100px 1fr 1fr 100px; 91 | gap: 10px; 92 | padding: 10px; 93 | } 94 | 95 | .floater { 96 | background-color: orange; 97 | border: 3px solid black; 98 | padding: 10px; 99 | border-radius: 5px; 100 | // margin: 0px 10px 0px 10px; 101 | } 102 | "#; 103 | -------------------------------------------------------------------------------- /packages/blitz-dom/src/query_selector.rs: -------------------------------------------------------------------------------- 1 | use selectors::SelectorList; 2 | use smallvec::SmallVec; 3 | use style::dom_apis::{MayUseInvalidation, QueryAll, QueryFirst, query_selector}; 4 | use style::selector_parser::{SelectorImpl, SelectorParser}; 5 | use style_traits::ParseError; 6 | 7 | use crate::{BaseDocument, Node}; 8 | 9 | impl BaseDocument { 10 | /// Find the node with the specified id attribute (if one exists) 11 | pub fn get_element_by_id(&self, id: &str) -> Option { 12 | self.nodes_to_id.get(id).copied() 13 | } 14 | 15 | /// Find the first node that matches the selector specified as a string 16 | /// Returns: 17 | /// - Err(_) if parsing the selector fails 18 | /// - Ok(None) if nothing matches 19 | /// - Ok(Some(node_id)) with the first node ID that matches if one is found 20 | pub fn query_selector<'input>( 21 | &self, 22 | selector: &'input str, 23 | ) -> Result, ParseError<'input>> { 24 | let selector_list = self.try_parse_selector_list(selector)?; 25 | Ok(self.query_selector_raw(&selector_list)) 26 | } 27 | 28 | /// Find the first node that matches the selector(s) specified in selector_list 29 | pub fn query_selector_raw(&self, selector_list: &SelectorList) -> Option { 30 | let root_node = self.root_node(); 31 | let mut result = None; 32 | query_selector::<&Node, QueryFirst>( 33 | root_node, 34 | selector_list, 35 | &mut result, 36 | MayUseInvalidation::Yes, 37 | ); 38 | 39 | result.map(|node| node.id) 40 | } 41 | 42 | /// Find all nodes that match the selector specified as a string 43 | /// Returns: 44 | /// - `Err(_)` if parsing the selector fails 45 | /// - `Ok(SmallVec)` with all matching nodes otherwise 46 | pub fn query_selector_all<'input>( 47 | &self, 48 | selector: &'input str, 49 | ) -> Result, ParseError<'input>> { 50 | let selector_list = self.try_parse_selector_list(selector)?; 51 | Ok(self.query_selector_all_raw(&selector_list)) 52 | } 53 | 54 | /// Find all nodes that match the selector(s) specified in selector_list 55 | pub fn query_selector_all_raw( 56 | &self, 57 | selector_list: &SelectorList, 58 | ) -> SmallVec<[usize; 32]> { 59 | let root_node = self.root_node(); 60 | let mut results = SmallVec::new(); 61 | query_selector::<&Node, QueryAll>( 62 | root_node, 63 | selector_list, 64 | &mut results, 65 | MayUseInvalidation::Yes, 66 | ); 67 | 68 | results.iter().map(|node| node.id).collect() 69 | } 70 | 71 | pub fn try_parse_selector_list<'input>( 72 | &self, 73 | input: &'input str, 74 | ) -> Result, ParseError<'input>> { 75 | let url_extra_data = self.url.url_extra_data(); 76 | SelectorParser::parse_author_origin_no_namespace(input, &url_extra_data) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /apps/readme/src/markdown/comrak.rs: -------------------------------------------------------------------------------- 1 | //! Render the readme.md using the gpu renderer 2 | 3 | use comrak::{Options, markdown_to_html_with_plugins, options}; 4 | 5 | pub(crate) fn markdown_to_html(contents: String) -> String { 6 | let plugins = options::Plugins::default(); 7 | // let syntax_highligher = CustomSyntectAdapter(SyntectAdapter::new(Some("InspiredGitHub"))); 8 | // plugins.render.codefence_syntax_highlighter = Some(&syntax_highligher as _); 9 | 10 | let body_html = markdown_to_html_with_plugins( 11 | &contents, 12 | &Options { 13 | extension: options::Extension { 14 | strikethrough: true, 15 | tagfilter: false, 16 | table: true, 17 | autolink: true, 18 | tasklist: true, 19 | superscript: false, 20 | header_ids: None, 21 | footnotes: false, 22 | description_lists: false, 23 | front_matter_delimiter: None, 24 | multiline_block_quotes: false, 25 | alerts: true, 26 | ..options::Extension::default() 27 | }, 28 | render: options::Render { 29 | r#unsafe: true, 30 | tasklist_classes: true, 31 | ..options::Render::default() 32 | }, 33 | ..Options::default() 34 | }, 35 | &plugins, 36 | ); 37 | 38 | // Strip trailing newlines in code blocks 39 | let body_html = body_html.replace("\n 44 | 45 | 46 |
{body_html}
47 | 48 | 49 | "# 50 | ) 51 | } 52 | 53 | // #[allow(unused)] 54 | // mod syntax_highlighter { 55 | // use comrak::adapters::SyntaxHighlighterAdapter; 56 | // use comrak::plugins::syntect::SyntectAdapter; 57 | // use std::collections::HashMap; 58 | 59 | // struct CustomSyntectAdapter(SyntectAdapter); 60 | 61 | // impl SyntaxHighlighterAdapter for CustomSyntectAdapter { 62 | // fn write_highlighted( 63 | // &self, 64 | // output: &mut dyn std::io::Write, 65 | // lang: Option<&str>, 66 | // code: &str, 67 | // ) -> std::io::Result<()> { 68 | // let norm_lang = lang.map(|l| l.split_once(',').map(|(lang, _)| lang).unwrap_or(l)); 69 | // self.0.write_highlighted(output, norm_lang, code) 70 | // } 71 | 72 | // fn write_pre_tag( 73 | // &self, 74 | // output: &mut dyn std::io::Write, 75 | // attributes: HashMap, 76 | // ) -> std::io::Result<()> { 77 | // self.0.write_pre_tag(output, attributes) 78 | // } 79 | 80 | // fn write_code_tag( 81 | // &self, 82 | // output: &mut dyn std::io::Write, 83 | // attributes: HashMap, 84 | // ) -> std::io::Result<()> { 85 | // self.0.write_code_tag(output, attributes) 86 | // } 87 | // } 88 | // } 89 | -------------------------------------------------------------------------------- /examples/gradient.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | 3 | fn main() { 4 | dioxus_native::launch(app); 5 | } 6 | 7 | fn app() -> Element { 8 | rsx! { 9 | style { {CSS} } 10 | div { class: "grid-container", 11 | div { id: "a1" } 12 | div { id: "a2" } 13 | div { id: "a3" } 14 | div { id: "a4" } 15 | 16 | div { id: "b1" } 17 | div { id: "b2" } 18 | div { id: "b3" } 19 | div { id: "b4" } 20 | div { id: "b5" } 21 | 22 | div { id: "c1" } 23 | div { id: "c2" } 24 | div { id: "c3" } 25 | 26 | div { id: "d1" } 27 | div { id: "d2" } 28 | div { id: "d3" } 29 | div { id: "d4" } 30 | div { id: "d5" } 31 | 32 | div { id: "e1" } 33 | div { id: "e2" } 34 | div { id: "e3" } 35 | div { id: "e4" } 36 | div { id: "e5" } 37 | } 38 | } 39 | } 40 | 41 | const CSS: &str = r#" 42 | .grid-container { 43 | display: grid; 44 | grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); 45 | gap: 10px; 46 | width: 95vw; 47 | height: 95vh; 48 | } 49 | 50 | div { 51 | min-width: 100px; 52 | min-height: 100px; 53 | } 54 | 55 | #a1 { background: linear-gradient(#e66465, #9198e5) } 56 | #a2 { background: linear-gradient(0.25turn, #3f87a6, #ebf8e1, #f69d3c) } 57 | #a3 { background: linear-gradient(to left, #333, #333 50%, #eee 75%, #333 75%) } 58 | #a4 { background: linear-gradient(217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70.71%), 59 | linear-gradient(127deg, rgba(0,255,0,.8), rgba(0,255,0,0) 70.71%), 60 | linear-gradient(336deg, rgba(0,0,255,.8), rgba(0,0,255,0) 70.71%) } 61 | 62 | #b1 { background: linear-gradient(to right, red 0%, 0%, blue 100%) } 63 | #b2 { background: linear-gradient(to right, red 0%, 25%, blue 100%) } 64 | #b3 { background: linear-gradient(to right, red 0%, 50%, blue 100%) } 65 | #b4 { background: linear-gradient(to right, red 0%, 100%, blue 100%) } 66 | #b5 { background: linear-gradient(to right, yellow, red 10%, 10%, blue 100%) } 67 | 68 | #c1 { background: repeating-linear-gradient(#e66465, #e66465 20px, #9198e5 20px, #9198e5 25px) } 69 | #c2 { background: repeating-linear-gradient(45deg, #3f87a6, #ebf8e1 15%, #f69d3c 20%) } 70 | #c3 { background: repeating-linear-gradient(transparent, #4d9f0c 40px), 71 | repeating-linear-gradient(0.25turn, transparent, #3f87a6 20px) } 72 | 73 | #d1 { background: radial-gradient(circle, red 20px, black 21px, blue) } 74 | #d2 { background: radial-gradient(closest-side, #3f87a6, #ebf8e1, #f69d3c) } 75 | #d3 { background: radial-gradient(circle at 100%, #333, #333 50%, #eee 75%, #333 75%) } 76 | #d4 { background: radial-gradient(ellipse at top, #e66465, transparent), 77 | radial-gradient(ellipse at bottom, #4d9f0c, transparent) } 78 | #d5 { background: radial-gradient(closest-corner circle at 20px 30px, red, yellow, green) } 79 | #e1 { background: repeating-conic-gradient(red 0%, yellow 15%, red 33%) } 80 | #e2 { background: repeating-conic-gradient( 81 | from 45deg at 10% 50%, 82 | brown 0deg 10deg, 83 | darkgoldenrod 10deg 20deg, 84 | chocolate 20deg 30deg 85 | ) } 86 | #e3 { background: repeating-radial-gradient(#e66465, #9198e5 20%) } 87 | #e4 { background: repeating-radial-gradient(closest-side, #3f87a6, #ebf8e1, #f69d3c) } 88 | #e5 { background: repeating-radial-gradient(circle at 100%, #333, #333 10px, #eee 10px, #eee 20px) } 89 | "#; 90 | -------------------------------------------------------------------------------- /apps/bump/src/main.rs: -------------------------------------------------------------------------------- 1 | use semver::Version; 2 | use std::fs; 3 | use toml_edit::{DocumentMut, Item}; 4 | 5 | // The set of the "anyrender" packages that are versioned together 6 | const ANYRENDER_PACKAGES: &[&str] = &[ 7 | "anyrender", 8 | "anyrender_vello", 9 | "anyrender_vello_cpu", 10 | "anyrender_svg", 11 | ]; 12 | 13 | // The set of the "blitz" packages that are versioned together 14 | const BLITZ_PACKAGES: &[&str] = &[ 15 | "blitz", 16 | "blitz-dom", 17 | "blitz-html", 18 | "blitz-net", 19 | "blitz-paint", 20 | "blitz-shell", 21 | "blitz-traits", 22 | "stylo_taffy", 23 | ]; 24 | 25 | macro_rules! bail { 26 | ($err:expr) => {{ 27 | eprintln!(); 28 | eprintln!("{}", $err); 29 | std::process::exit(1); 30 | }}; 31 | } 32 | 33 | fn with_cargo_toml(package_path: &str, cb: impl FnOnce(&mut DocumentMut)) { 34 | let cargo_toml_path = format!("{package_path}/Cargo.toml"); 35 | let cargo_toml_str = fs::read_to_string(&cargo_toml_path).unwrap(); 36 | let mut cargo_toml_doc = cargo_toml_str 37 | .parse::() 38 | .expect("invalid Cargo.toml"); 39 | 40 | cb(&mut cargo_toml_doc); 41 | 42 | fs::write(&cargo_toml_path, cargo_toml_doc.to_string()).unwrap(); 43 | } 44 | 45 | fn set_package_version(dep_name: &str, version: &str) { 46 | let package_path = format!("./packages/{dep_name}"); 47 | with_cargo_toml(&package_path, move |cargo_toml| { 48 | cargo_toml["package"]["version"] = Item::from(version); 49 | }); 50 | } 51 | 52 | fn set_workspace_version(version: &str) { 53 | with_cargo_toml(".", move |cargo_toml| { 54 | cargo_toml["workspace"]["package"]["version"] = Item::from(version); 55 | }); 56 | } 57 | 58 | fn set_workspace_dep_version(dep_name: &str, version: &str) { 59 | with_cargo_toml(".", move |cargo_toml| { 60 | cargo_toml["workspace"]["dependencies"][dep_name]["version"] = Item::from(version); 61 | }); 62 | } 63 | 64 | fn main() { 65 | // --- Parse CLI args 66 | 67 | let mut args = std::env::args().skip(1); 68 | 69 | // Parse "target" CLI arg 70 | let target = args.next(); 71 | let target = match target.as_deref() { 72 | Some(target @ ("blitz" | "anyrender")) => target, 73 | Some(target) => { 74 | println!("{target}"); 75 | bail!("Invalid target. Must be 'blitz' or 'anyrender'") 76 | } 77 | _ => bail!("Missing target. Must be 'blitz' or 'anyrender'"), 78 | }; 79 | 80 | // Parse "version" CLI arg 81 | let version = args.next(); 82 | let version = match version { 83 | Some(version) => { 84 | if Version::parse(&version).is_ok() { 85 | version 86 | } else { 87 | bail!("Invalid version. Must be valid cargo version.") 88 | } 89 | } 90 | _ => bail!("Missing version Must be valid cargo version."), 91 | }; 92 | 93 | // --- Bump versions 94 | 95 | if target == "anyrender" { 96 | for package in ANYRENDER_PACKAGES { 97 | set_package_version(package, &version); 98 | set_workspace_dep_version(package, &version); 99 | } 100 | println!("Bumped anyrender versions") 101 | } 102 | 103 | if target == "blitz" { 104 | set_workspace_version(&version); 105 | for package in BLITZ_PACKAGES { 106 | set_workspace_dep_version(package, &version); 107 | } 108 | 109 | println!("Bumped blitz versions") 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /examples/wgpu_texture/src/shader.wgsl: -------------------------------------------------------------------------------- 1 | // Copyright © SixtyFPS GmbH 2 | // SPDX-License-Identifier: MIT 3 | 4 | struct VertexOutput { 5 | @builtin(position) position: vec4, 6 | @location(0) frag_position: vec2, 7 | }; 8 | 9 | @vertex 10 | fn vs_main( 11 | @builtin(vertex_index) vertex_index: u32 12 | ) -> VertexOutput { 13 | var output: VertexOutput; 14 | 15 | var positions = array, 3>( 16 | vec2(-1.0, 3.0), 17 | vec2(-1.0, -1.0), 18 | vec2( 3.0, -1.0) 19 | ); 20 | 21 | let pos = positions[vertex_index]; 22 | output.position = vec4(pos.x, -pos.y, 0.0, 1.0); 23 | output.frag_position = pos; 24 | return output; 25 | } 26 | 27 | struct PushConstants { 28 | light_color_and_time: vec4, 29 | }; 30 | 31 | var pc: PushConstants; 32 | 33 | fn sdRoundBox(p: vec3, b: vec3, r: f32) -> f32 { 34 | let q = abs(p) - b; 35 | return length(max(q, vec3(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0) - r; 36 | } 37 | 38 | fn rotateY(r: vec3, angle: f32) -> vec3 { 39 | let c = cos(angle); 40 | let s = sin(angle); 41 | let rotation_matrix = mat3x3( 42 | vec3( c, 0.0, s), 43 | vec3(0.0, 1.0, 0.0), 44 | vec3(-s, 0.0, c) 45 | ); 46 | return rotation_matrix * r; 47 | } 48 | 49 | fn rotateZ(r: vec3, angle: f32) -> vec3 { 50 | let c = cos(angle); 51 | let s = sin(angle); 52 | let rotation_matrix = mat3x3( 53 | vec3( c, -s, 0.0), 54 | vec3( s, c, 0.0), 55 | vec3(0.0, 0.0, 1.0) 56 | ); 57 | return rotation_matrix * r; 58 | } 59 | 60 | // Distance from the scene 61 | fn scene(r: vec3) -> f32 { 62 | let iTime = pc.light_color_and_time.w; 63 | let pos = rotateZ(rotateY(r + vec3(-1.0, -1.0, 4.0), iTime), iTime); 64 | let cube = vec3(0.5, 0.5, 0.5); 65 | let edge = 0.1; 66 | return sdRoundBox(pos, cube, edge); 67 | } 68 | 69 | // https://iquilezles.org/articles/normalsSDF 70 | fn normal(pos: vec3) -> vec3 { 71 | let e = vec2(1.0, -1.0) * 0.5773; 72 | let eps = 0.0005; 73 | return normalize( 74 | e.xyy * scene(pos + e.xyy * eps) + 75 | e.yyx * scene(pos + e.yyx * eps) + 76 | e.yxy * scene(pos + e.yxy * eps) + 77 | e.xxx * scene(pos + e.xxx * eps) 78 | ); 79 | } 80 | 81 | fn render(fragCoord: vec2, light_color: vec3) -> vec4 { 82 | var color = vec4(0.0, 0.0, 0.0, 1.0); 83 | 84 | var camera = vec3(1.0, 2.0, 1.0); 85 | var p = vec3(fragCoord.x, fragCoord.y + 1.0, -1.0); 86 | var dir = normalize(p - camera); 87 | 88 | var i = 0; 89 | loop { 90 | if (i >= 90) { break; } 91 | let dist = scene(p); 92 | if (dist < 0.0001) { break; } 93 | p = p + dir * dist; 94 | i = i + 1; 95 | } 96 | 97 | let surf_normal = normal(p); 98 | let light_position = vec3(2.0, 4.0, -0.5); 99 | var light = 7.0 + 2.0 * dot(surf_normal, light_position); 100 | light = light / (0.2 * pow(length(light_position - p), 3.5)); 101 | 102 | let alpha = select(0.0, 1.0, i < 90); 103 | return vec4(light * light_color, alpha) * 2.0; 104 | } 105 | 106 | @fragment 107 | fn fs_main(@location(0) frag_position: vec2) -> @location(0) vec4 { 108 | let selected_light_color = pc.light_color_and_time.xyz; 109 | let r = vec2(0.5 * frag_position.x + 1.0, 0.5 - 0.5 * frag_position.y); 110 | return render(r, selected_light_color); 111 | } -------------------------------------------------------------------------------- /packages/blitz-dom/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The core DOM abstraction in Blitz 2 | //! 3 | //! This crate implements a flexible headless DOM ([`BaseDocument`]), which is designed to emebedded in and "driven" by external code. Most users will want 4 | //! to use a wrapper: 5 | //! 6 | //! - [`HtmlDocument`](https://docs.rs/blitz-html/latest/blitz_html/struct.HtmlDocument.html) from the [blitz-html](https://docs.rs/blitz-html) crate. 7 | //! Allows you to parse HTML (or XHTML) into a Blitz [`BaseDocument`], and can be combined with a markdown-to-html converter like [comrak](https://docs.rs/comrak) 8 | //! or [pulldown-cmark](https://docs.rs/pulldown-cmark) to render/process markdown. 9 | //! - [`DioxusDocument`](https://docs.rs/dioxus-native/latest/dioxus_native/struct.DioxusDocument.html) from the [dioxus-native](https://docs.rs/dioxus-native) crate. 10 | //! Combines a [`BaseDocument`] with a Dioxus `VirtualDom` to enable dynamic rendering and event handling. 11 | //! 12 | //! It includes: A DOM tree respresentation, CSS parsing and resolution, layout and event handling. Additional functionality is available in 13 | //! separate crates, including html parsing ([blitz-html](https://docs.rs/blitz-html)), networking ([blitz-net](https://docs.rs/blitz-html)), 14 | //! rendering ([blitz-paint](https://docs.rs/blitz-paint)) and windowing ([blitz-shell](https://docs.rs/blitz-shell)). 15 | //! 16 | //! Most of the functionality in this crates is provided through the struct. 17 | //! 18 | //! `blitz-dom` has a native Rust API that is designed for higher-level abstractions to be built on top (although it can also be used directly). 19 | //! 20 | //! The goal behind this crate is that any implementor can interact with the DOM and render it out using any renderer 21 | //! they want. 22 | //! 23 | 24 | #![allow(clippy::collapsible_if)] 25 | 26 | // TODO: Document features 27 | // ## Feature flags 28 | // - `default`: Enables the features listed below. 29 | // - `tracing`: Enables tracing support. 30 | 31 | pub const DEFAULT_CSS: &str = include_str!("../assets/default.css"); 32 | pub const BULLET_FONT: &[u8] = include_bytes!("../assets/moz-bullet-font.otf"); 33 | 34 | const INCREMENTAL: bool = cfg!(feature = "incremental"); 35 | const NON_INCREMENTAL: bool = !INCREMENTAL; 36 | 37 | /// The DOM implementation. 38 | /// 39 | /// This is the primary entry point for this crate. 40 | mod document; 41 | 42 | /// The nodes themsleves, and their data. 43 | pub mod node; 44 | 45 | mod config; 46 | mod debug; 47 | mod events; 48 | mod font_metrics; 49 | mod form; 50 | mod html; 51 | /// Integration of taffy and the DOM. 52 | mod layout; 53 | mod mutator; 54 | mod query_selector; 55 | mod resolve; 56 | /// Implementations that interact with servo's style engine 57 | mod stylo; 58 | mod stylo_to_cursor_icon; 59 | mod stylo_to_parley; 60 | mod traversal; 61 | mod url; 62 | 63 | pub mod net; 64 | pub mod util; 65 | 66 | #[cfg(feature = "accessibility")] 67 | mod accessibility; 68 | 69 | pub use config::DocumentConfig; 70 | pub use document::{BaseDocument, Document, PlainDocument}; 71 | pub use markup5ever::{ 72 | LocalName, Namespace, NamespaceStaticSet, Prefix, PrefixStaticSet, QualName, local_name, 73 | namespace_prefix, namespace_url, ns, 74 | }; 75 | pub use mutator::DocumentMutator; 76 | pub use node::{Attribute, ElementData, Node, NodeData, TextNodeData}; 77 | pub use parley::FontContext; 78 | pub use style::Atom; 79 | pub use style::invalidation::element::restyle_hints::RestyleHint; 80 | pub type SelectorList = selectors::SelectorList; 81 | pub use events::{EventDriver, EventHandler, NoopEventHandler}; 82 | pub use html::{DummyHtmlParserProvider, HtmlParserProvider}; 83 | pub use util::Point; 84 | -------------------------------------------------------------------------------- /examples/assets/gosub.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | Gosub - The gateway to optimized searching and browsing 94 | 95 | 96 | 97 |
98 |

Gosub

99 |

The gateway to optimized searching and browsing

100 | 101 | 102 |
103 | Join us on the journey to a new web browser 104 |
105 | 106 | 111 |
112 | 113 | 114 | -------------------------------------------------------------------------------- /packages/blitz-traits/src/shell.rs: -------------------------------------------------------------------------------- 1 | //! Abstraction over windowing / operating system ("shell") functionality 2 | 3 | use cursor_icon::CursorIcon; 4 | 5 | /// Type representing an error performing a clipboard operation 6 | // TODO: fill out with meaningful errors 7 | pub struct ClipboardError; 8 | 9 | /// Abstraction over windowing / operating system ("shell") functionality that allows a Blitz document 10 | /// to access that functionality without depending on a specific shell environment. 11 | pub trait ShellProvider: Send + Sync + 'static { 12 | fn request_redraw(&self) {} 13 | fn set_cursor(&self, icon: CursorIcon) { 14 | let _ = icon; 15 | } 16 | fn set_window_title(&self, title: String) { 17 | let _ = title; 18 | } 19 | fn set_ime_enabled(&self, is_enabled: bool) { 20 | let _ = is_enabled; 21 | } 22 | fn set_ime_cursor_area(&self, x: f32, y: f32, width: f32, height: f32) { 23 | let _ = x; 24 | let _ = y; 25 | let _ = width; 26 | let _ = height; 27 | } 28 | fn get_clipboard_text(&self) -> Result { 29 | Err(ClipboardError) 30 | } 31 | fn set_clipboard_text(&self, text: String) -> Result<(), ClipboardError> { 32 | let _ = text; 33 | Err(ClipboardError) 34 | } 35 | fn open_file_dialog( 36 | &self, 37 | multiple: bool, 38 | filter: Option, 39 | ) -> Vec { 40 | let _ = multiple; 41 | let _ = filter; 42 | vec![] 43 | } 44 | } 45 | 46 | pub struct DummyShellProvider; 47 | impl ShellProvider for DummyShellProvider {} 48 | 49 | /// The system color scheme (light and dark mode) 50 | #[derive(Default, Debug, Clone, Copy, PartialEq)] 51 | pub enum ColorScheme { 52 | #[default] 53 | Light, 54 | Dark, 55 | } 56 | 57 | #[derive(Debug, Clone, PartialEq)] 58 | pub struct Viewport { 59 | pub color_scheme: ColorScheme, 60 | pub window_size: (u32, u32), 61 | pub hidpi_scale: f32, 62 | pub zoom: f32, 63 | } 64 | 65 | impl Default for Viewport { 66 | fn default() -> Self { 67 | Self { 68 | window_size: (0, 0), 69 | hidpi_scale: 1.0, 70 | zoom: 1.0, 71 | color_scheme: ColorScheme::Light, 72 | } 73 | } 74 | } 75 | 76 | impl Viewport { 77 | pub fn new( 78 | physical_width: u32, 79 | physical_height: u32, 80 | scale_factor: f32, 81 | color_scheme: ColorScheme, 82 | ) -> Self { 83 | Self { 84 | window_size: (physical_width, physical_height), 85 | hidpi_scale: scale_factor, 86 | zoom: 1.0, 87 | color_scheme, 88 | } 89 | } 90 | 91 | /// Total scaling, computed as `hidpi_scale_factor * zoom` 92 | pub fn scale(&self) -> f32 { 93 | self.hidpi_scale * self.zoom 94 | } 95 | /// Same as [`scale`](Self::scale) but `f64` instead of `f32` 96 | pub fn scale_f64(&self) -> f64 { 97 | self.scale() as f64 98 | } 99 | 100 | /// Set hidpi scale factor 101 | pub fn set_hidpi_scale(&mut self, scale: f32) { 102 | self.hidpi_scale = scale; 103 | } 104 | 105 | /// Get document zoom level 106 | pub fn zoom(&self) -> f32 { 107 | self.zoom 108 | } 109 | 110 | /// Set document zoom level (`1.0` is unzoomed) 111 | pub fn set_zoom(&mut self, zoom: f32) { 112 | self.zoom = zoom; 113 | } 114 | 115 | pub fn zoom_by(&mut self, zoom: f32) { 116 | self.zoom += zoom; 117 | } 118 | 119 | pub fn zoom_mut(&mut self) -> &mut f32 { 120 | &mut self.zoom 121 | } 122 | } 123 | 124 | /// Filter provided by the dom for an file picker 125 | pub struct FileDialogFilter { 126 | pub name: String, 127 | pub extensions: Vec, 128 | } 129 | -------------------------------------------------------------------------------- /packages/blitz-paint/src/render/form_controls.rs: -------------------------------------------------------------------------------- 1 | use super::ElementCx; 2 | use crate::color::{Color, ToColorColor as _}; 3 | use anyrender::PaintScene; 4 | use blitz_dom::local_name; 5 | use kurbo::{Affine, BezPath, Cap, Circle, Join, Point, RoundedRect, Stroke, Vec2}; 6 | use peniko::Fill; 7 | use style::dom::TElement as _; 8 | 9 | impl ElementCx<'_> { 10 | pub(super) fn draw_input(&self, scene: &mut impl PaintScene) { 11 | if self.node.local_name() != "input" { 12 | return; 13 | } 14 | let Some(checked) = self.element.checkbox_input_checked() else { 15 | return; 16 | }; 17 | 18 | let type_attr = self.node.attr(local_name!("type")); 19 | let disabled = self.node.attr(local_name!("disabled")).is_some(); 20 | 21 | // TODO this should be coming from css accent-color, but I couldn't find how to retrieve it 22 | let accent_color = if disabled { 23 | Color::from_rgba8(209, 209, 209, 255) 24 | } else { 25 | self.style.clone_color().as_srgb_color() 26 | }; 27 | 28 | let width = self.frame.border_box.width(); 29 | let height = self.frame.border_box.height(); 30 | let min_dimension = width.min(height); 31 | let scale = (min_dimension - 4.0).max(0.0) / 16.0; 32 | 33 | let frame = self.frame.border_box.to_rounded_rect(scale * 2.0); 34 | 35 | match type_attr { 36 | Some("checkbox") => { 37 | draw_checkbox(scene, checked, frame, self.transform, accent_color, scale); 38 | } 39 | Some("radio") => { 40 | let center = frame.center(); 41 | draw_radio_button(scene, checked, center, self.transform, accent_color, scale); 42 | } 43 | _ => {} 44 | } 45 | } 46 | } 47 | 48 | fn draw_checkbox( 49 | scene: &mut impl PaintScene, 50 | checked: bool, 51 | frame: RoundedRect, 52 | transform: Affine, 53 | accent_color: Color, 54 | scale: f64, 55 | ) { 56 | if checked { 57 | scene.fill(Fill::NonZero, transform, accent_color, None, &frame); 58 | //Tick code derived from masonry 59 | let mut path = BezPath::new(); 60 | path.move_to((2.0, 9.0)); 61 | path.line_to((6.0, 13.0)); 62 | path.line_to((14.0, 2.0)); 63 | 64 | path.apply_affine(Affine::translate(Vec2 { x: 2.0, y: 1.0 }).then_scale(scale)); 65 | 66 | let style = Stroke { 67 | width: 2.0 * scale, 68 | join: Join::Round, 69 | miter_limit: 10.0, 70 | start_cap: Cap::Round, 71 | end_cap: Cap::Round, 72 | dash_pattern: Default::default(), 73 | dash_offset: 0.0, 74 | }; 75 | 76 | scene.stroke(&style, transform, Color::WHITE, None, &path); 77 | } else { 78 | scene.fill(Fill::NonZero, transform, Color::WHITE, None, &frame); 79 | scene.stroke(&Stroke::default(), transform, accent_color, None, &frame); 80 | } 81 | } 82 | 83 | fn draw_radio_button( 84 | scene: &mut impl PaintScene, 85 | checked: bool, 86 | center: Point, 87 | transform: Affine, 88 | accent_color: Color, 89 | scale: f64, 90 | ) { 91 | let outer_ring = Circle::new(center, 8.0 * scale); 92 | let gap = Circle::new(center, 6.0 * scale); 93 | let inner_circle = Circle::new(center, 4.0 * scale); 94 | if checked { 95 | scene.fill(Fill::NonZero, transform, accent_color, None, &outer_ring); 96 | scene.fill(Fill::NonZero, transform, Color::WHITE, None, &gap); 97 | scene.fill(Fill::NonZero, transform, accent_color, None, &inner_circle); 98 | } else { 99 | const GRAY: Color = color::palette::css::GRAY; 100 | scene.fill(Fill::NonZero, transform, GRAY, None, &outer_ring); 101 | scene.fill(Fill::NonZero, transform, Color::WHITE, None, &gap); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /packages/blitz-paint/src/text.rs: -------------------------------------------------------------------------------- 1 | use anyrender::PaintScene; 2 | use blitz_dom::{BaseDocument, node::TextBrush, util::ToColorColor}; 3 | use kurbo::{Affine, Stroke}; 4 | use parley::{Line, PositionedLayoutItem}; 5 | use peniko::Fill; 6 | use style::values::computed::TextDecorationLine; 7 | 8 | pub(crate) fn stroke_text<'a>( 9 | scene: &mut impl PaintScene, 10 | lines: impl Iterator>, 11 | doc: &BaseDocument, 12 | transform: Affine, 13 | ) { 14 | for line in lines { 15 | for item in line.items() { 16 | if let PositionedLayoutItem::GlyphRun(glyph_run) = item { 17 | let run = glyph_run.run(); 18 | let font = run.font(); 19 | let font_size = run.font_size(); 20 | let metrics = run.metrics(); 21 | let style = glyph_run.style(); 22 | let synthesis = run.synthesis(); 23 | let glyph_xform = synthesis 24 | .skew() 25 | .map(|angle| Affine::skew(angle.to_radians().tan() as f64, 0.0)); 26 | 27 | // Styles 28 | let styles = doc 29 | .get_node(style.brush.id) 30 | .unwrap() 31 | .primary_styles() 32 | .unwrap(); 33 | let itext_styles = styles.get_inherited_text(); 34 | let text_styles = styles.get_text(); 35 | let text_color = itext_styles.color.as_color_color(); 36 | let text_decoration_color = text_styles 37 | .text_decoration_color 38 | .as_absolute() 39 | .map(ToColorColor::as_color_color) 40 | .unwrap_or(text_color); 41 | let text_decoration_brush = anyrender::Paint::from(text_decoration_color); 42 | let text_decoration_line = text_styles.text_decoration_line; 43 | let has_underline = text_decoration_line.contains(TextDecorationLine::UNDERLINE); 44 | let has_strikethrough = 45 | text_decoration_line.contains(TextDecorationLine::LINE_THROUGH); 46 | 47 | scene.draw_glyphs( 48 | font, 49 | font_size, 50 | true, // hint 51 | run.normalized_coords(), 52 | Fill::NonZero, 53 | &anyrender::Paint::from(text_color), 54 | 1.0, // alpha 55 | transform, 56 | glyph_xform, 57 | glyph_run.positioned_glyphs().map(|glyph| anyrender::Glyph { 58 | id: glyph.id as _, 59 | x: glyph.x, 60 | y: glyph.y, 61 | }), 62 | ); 63 | 64 | let mut draw_decoration_line = 65 | |offset: f32, size: f32, brush: &anyrender::Paint| { 66 | let x = glyph_run.offset() as f64; 67 | let w = glyph_run.advance() as f64; 68 | let y = (glyph_run.baseline() - offset + size / 2.0) as f64; 69 | let line = kurbo::Line::new((x, y), (x + w, y)); 70 | scene.stroke(&Stroke::new(size as f64), transform, brush, None, &line) 71 | }; 72 | 73 | if has_underline { 74 | let offset = metrics.underline_offset; 75 | let size = metrics.underline_size; 76 | 77 | // TODO: intercept line when crossing an descending character like "gqy" 78 | draw_decoration_line(offset, size, &text_decoration_brush); 79 | } 80 | if has_strikethrough { 81 | let offset = metrics.strikethrough_offset; 82 | let size = metrics.strikethrough_size; 83 | 84 | draw_decoration_line(offset, size, &text_decoration_brush); 85 | } 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /examples/html.rs: -------------------------------------------------------------------------------- 1 | //! Example of rendering a static html string to a window 2 | 3 | const HTML: &str = r#" 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | "#; 46 | 47 | fn main() { 48 | blitz::launch_static_html(HTML); 49 | } 50 | -------------------------------------------------------------------------------- /examples/inline.rs: -------------------------------------------------------------------------------- 1 | //! https://www.w3schools.com/css/tryit.asp?filename=trycss_inline-block_span1 2 | 3 | use dioxus::prelude::*; 4 | 5 | fn main() { 6 | dioxus_native::launch(app); 7 | } 8 | 9 | fn app() -> Element { 10 | rsx! { 11 | head { 12 | style { {CSS} } 13 | } 14 | body { 15 | h1 { "The display Property" } 16 | h2 { "display: inline" } 17 | div { 18 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum consequat scelerisque elit sit amet consequat. Aliquam erat volutpat. " 19 | span { class: "a", "Aliquam" } 20 | span { class: "a", "venenatis" } 21 | " gravida nisl sit amet facilisis. Nullam cursus fermentum velit sed laoreet. " 22 | } 23 | h2 { "display: inline-block" } 24 | div { 25 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum consequat scelerisque elit sit amet consequat. Aliquam erat volutpat. " 26 | span { class: "b", "Aliquam" } 27 | span { class: "b", "venenatis" } 28 | " gravida nisl sit amet facilisis. Nullam cursus fermentum velit sed laoreet. " 29 | } 30 | h2 { "display: block" } 31 | div { 32 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum consequat scelerisque elit sit amet consequat. Aliquam erat volutpat. " 33 | span { class: "c", "Aliquam" } 34 | span { class: "c", "venenatis" } 35 | " gravida nisl sit amet facilisis. Nullam cursus fermentum velit sed laoreet. " 36 | } 37 | div { id: "a", 38 | "Some text" 39 | em { "Another block of text" } 40 | "Should connect no space between" 41 | } 42 | h1 { "ul" } 43 | ul { 44 | li { "Item 1" } 45 | li { "Item 2" } 46 | li { class: "square", "Square item" } 47 | li { class: "circle", "Circle item" } 48 | li { class: "disclosure-open", "Disclosure open item" } 49 | li { class: "disclosure-closed", "Disclosure closed item" } 50 | } 51 | h1 { "ol - decimal" } 52 | ol { 53 | li { "Item 1" } 54 | li { "Item 2" } 55 | li { 56 | ul { 57 | li { "Nested Item 1" } 58 | li { "Nested Item 2" } 59 | } 60 | } 61 | li { "Item 3" } 62 | li { "Item 4" } 63 | ol { 64 | li { "Sub 1" } 65 | li { "Sub 2" } 66 | } 67 | } 68 | h1 { "ol - alpha" } 69 | ol { class: "alpha", 70 | li { "Item 1" } 71 | li { "Item 2" } 72 | li { "Item 3" } 73 | } 74 | } 75 | } 76 | } 77 | 78 | const CSS: &str = r#" 79 | span.a { 80 | display: inline; /* the default for span */ 81 | width: 100px; 82 | height: 100px; 83 | padding: 5px; 84 | border: 1px solid blue; 85 | background-color: yellow; 86 | } 87 | 88 | span.b { 89 | display: inline-block; 90 | width: 100px; 91 | height: 100px; 92 | padding: 5px; 93 | border: 1px solid blue; 94 | background-color: yellow; 95 | } 96 | 97 | span.c { 98 | display: block; 99 | width: 100px; 100 | height: 100px; 101 | padding: 5px; 102 | border: 1px solid blue; 103 | background-color: yellow; 104 | } 105 | 106 | #a { 107 | } 108 | h1 { 109 | font-size: 20px; 110 | } 111 | ol.alpha { 112 | list-style-type: lower-alpha; 113 | } 114 | li.square { 115 | list-style-type: square; 116 | } 117 | li.circle { 118 | list-style-type: circle; 119 | } 120 | li.disclosure-open { 121 | list-style-type: disclosure-open; 122 | } 123 | li.disclosure-closed { 124 | list-style-type: disclosure-closed; 125 | } 126 | 127 | "#; 128 | -------------------------------------------------------------------------------- /packages/debug_timer/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "enable")] 2 | mod real_debug_timer { 3 | use std::io::{stdout, Write}; 4 | use std::time::{Duration, Instant}; 5 | 6 | const SECOND: Duration = Duration::from_secs(1); 7 | const MILLISECOND: Duration = Duration::from_millis(1); 8 | const MICROSECOND: Duration = Duration::from_micros(1); 9 | 10 | pub struct DebugTimer { 11 | recorded_times: Vec<(&'static str, Instant)>, 12 | } 13 | 14 | fn value_and_units(duration: Duration) -> (f32, &'static str) { 15 | if duration < MICROSECOND { 16 | (duration.subsec_nanos() as f32, "ns") 17 | } else if duration < MILLISECOND { 18 | (duration.subsec_nanos() as f32 / 1000.0, "us") 19 | } else if duration < SECOND { 20 | (duration.subsec_micros() as f32 / 1000.0, "ms") 21 | } else { 22 | (duration.as_millis() as f32 / 1000.0, "s") 23 | } 24 | } 25 | 26 | impl DebugTimer { 27 | pub fn init() -> Self { 28 | Self { 29 | recorded_times: vec![("start", Instant::now())], 30 | } 31 | } 32 | 33 | pub fn record_time(&mut self, message: &'static str) { 34 | self.recorded_times.push((message, Instant::now())); 35 | } 36 | 37 | pub fn print_times(&self, message: &str) { 38 | let now = Instant::now(); 39 | let (overall_val, overall_unit) = value_and_units(now - self.recorded_times[0].1); 40 | 41 | let mut out = stdout().lock(); 42 | if overall_val < 10.0 { 43 | write!(out, "{message}{overall_val:.1}{overall_unit} (").unwrap(); 44 | } else { 45 | write!(out, "{message}{overall_val:.0}{overall_unit} (").unwrap(); 46 | } 47 | 48 | for (idx, times) in self.recorded_times.windows(2).enumerate() { 49 | let last = times[0]; 50 | let current = times[1]; 51 | 52 | if idx != 0 { 53 | write!(out, ", ").unwrap(); 54 | } 55 | 56 | let duration = current.1.duration_since(last.1); 57 | 58 | let (val, unit) = value_and_units(duration); 59 | if val < 10.0 { 60 | write!(out, "{}: {val:.1}{unit}", current.0).unwrap(); 61 | } else { 62 | write!(out, "{}: {val:.0}{unit}", current.0).unwrap(); 63 | } 64 | } 65 | writeln!(out, ")").unwrap(); 66 | } 67 | } 68 | } 69 | 70 | mod dummy_debug_timer { 71 | pub struct DebugTimer; 72 | impl DebugTimer { 73 | #[inline(always)] 74 | pub fn init() -> Self { 75 | Self 76 | } 77 | #[inline(always)] 78 | pub fn record_time(&mut self, _message: &'static str) {} 79 | #[inline(always)] 80 | pub fn print_times(&self, _message: &str) {} 81 | } 82 | } 83 | 84 | #[cfg(feature = "enable")] 85 | #[macro_export] 86 | macro_rules! debug_timer { 87 | ($id:ident, $($cond:tt)*) => { 88 | let mut $id = { 89 | #[cfg($($cond)*)] 90 | let timer = $crate::RealDebugTimer::init(); 91 | #[cfg(not($($cond)*))] 92 | let timer = $crate::DummyDebugTimer::init(); 93 | timer 94 | }; 95 | }; 96 | } 97 | 98 | #[cfg(feature = "enable")] 99 | #[macro_export] 100 | macro_rules! debug_timer_type { 101 | ($id:ident, $($cond:tt)*) => { 102 | #[cfg($($cond)*)] 103 | pub type $id = $crate::RealDebugTimer; 104 | #[cfg(not($($cond)*))] 105 | pub type $id = $crate::DummyDebugTimer; 106 | }; 107 | } 108 | 109 | #[cfg(not(feature = "enable"))] 110 | #[macro_export] 111 | macro_rules! debug_timer { 112 | ($id:ident, $($cond:tt)*) => { 113 | let mut $id = $crate::DummyDebugTimer::init(); 114 | }; 115 | } 116 | 117 | #[cfg(not(feature = "enable"))] 118 | #[macro_export] 119 | macro_rules! debug_timer_type { 120 | ($id:ident, $($cond:tt)*) => { 121 | pub type $id = $crate::DummyDebugTimer; 122 | }; 123 | } 124 | 125 | pub use dummy_debug_timer::DebugTimer as DummyDebugTimer; 126 | #[cfg(feature = "enable")] 127 | pub use real_debug_timer::DebugTimer as RealDebugTimer; 128 | -------------------------------------------------------------------------------- /packages/dioxus-native/src/dioxus_renderer.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | use std::sync::Arc; 4 | 5 | use anyrender::WindowRenderer; 6 | 7 | #[cfg(any( 8 | feature = "vello", 9 | all( 10 | not(feature = "alt-renderer"), 11 | not(all(target_os = "ios", target_abi = "sim")) 12 | ) 13 | ))] 14 | pub use anyrender_vello::{ 15 | wgpu::{Features, Limits}, 16 | CustomPaintSource, VelloRendererOptions, VelloWindowRenderer as InnerRenderer, 17 | }; 18 | 19 | #[cfg(any( 20 | feature = "vello-cpu-base", 21 | all( 22 | not(feature = "alt-renderer"), 23 | all(target_os = "ios", target_abi = "sim") 24 | ) 25 | ))] 26 | use anyrender_vello_cpu::VelloCpuWindowRenderer as InnerRenderer; 27 | 28 | #[cfg(feature = "vello-hybrid")] 29 | use anyrender_vello_hybrid::VelloHybridWindowRenderer as InnerRenderer; 30 | 31 | #[cfg(feature = "skia")] 32 | use anyrender_skia::SkiaWindowRenderer as InnerRenderer; 33 | 34 | #[cfg(any( 35 | feature = "vello", 36 | all( 37 | not(feature = "alt-renderer"), 38 | not(all(target_os = "ios", target_abi = "sim")) 39 | ) 40 | ))] 41 | pub fn use_wgpu(create_source: impl FnOnce() -> T) -> u64 { 42 | use dioxus_core::{consume_context, use_hook_with_cleanup}; 43 | 44 | let (_renderer, id) = use_hook_with_cleanup( 45 | || { 46 | let renderer = consume_context::(); 47 | let source = Box::new(create_source()); 48 | let id = renderer.register_custom_paint_source(source); 49 | (renderer, id) 50 | }, 51 | |(renderer, id)| { 52 | renderer.unregister_custom_paint_source(id); 53 | }, 54 | ); 55 | 56 | id 57 | } 58 | 59 | #[derive(Clone)] 60 | pub struct DioxusNativeWindowRenderer { 61 | inner: Rc>, 62 | } 63 | 64 | impl Default for DioxusNativeWindowRenderer { 65 | fn default() -> Self { 66 | Self::new() 67 | } 68 | } 69 | 70 | impl DioxusNativeWindowRenderer { 71 | pub fn new() -> Self { 72 | let vello_renderer = InnerRenderer::new(); 73 | Self::with_inner_renderer(vello_renderer) 74 | } 75 | 76 | #[cfg(any( 77 | feature = "vello", 78 | all( 79 | not(feature = "alt-renderer"), 80 | not(all(target_os = "ios", target_abi = "sim")) 81 | ) 82 | ))] 83 | pub fn with_features_and_limits(features: Option, limits: Option) -> Self { 84 | let vello_renderer = InnerRenderer::with_options(VelloRendererOptions { 85 | features, 86 | limits, 87 | ..Default::default() 88 | }); 89 | Self::with_inner_renderer(vello_renderer) 90 | } 91 | 92 | fn with_inner_renderer(vello_renderer: InnerRenderer) -> Self { 93 | Self { 94 | inner: Rc::new(RefCell::new(vello_renderer)), 95 | } 96 | } 97 | } 98 | 99 | #[cfg(any( 100 | feature = "vello", 101 | all( 102 | not(feature = "alt-renderer"), 103 | not(all(target_os = "ios", target_abi = "sim")) 104 | ) 105 | ))] 106 | impl DioxusNativeWindowRenderer { 107 | pub fn register_custom_paint_source(&self, source: Box) -> u64 { 108 | self.inner.borrow_mut().register_custom_paint_source(source) 109 | } 110 | 111 | pub fn unregister_custom_paint_source(&self, id: u64) { 112 | self.inner.borrow_mut().unregister_custom_paint_source(id) 113 | } 114 | } 115 | 116 | impl WindowRenderer for DioxusNativeWindowRenderer { 117 | type ScenePainter<'a> 118 | = ::ScenePainter<'a> 119 | where 120 | Self: 'a; 121 | 122 | fn resume(&mut self, window: Arc, width: u32, height: u32) { 123 | self.inner.borrow_mut().resume(window, width, height) 124 | } 125 | 126 | fn suspend(&mut self) { 127 | self.inner.borrow_mut().suspend() 128 | } 129 | 130 | fn is_active(&self) -> bool { 131 | self.inner.borrow().is_active() 132 | } 133 | 134 | fn set_size(&mut self, width: u32, height: u32) { 135 | self.inner.borrow_mut().set_size(width, height) 136 | } 137 | 138 | fn render)>(&mut self, draw_fn: F) { 139 | self.inner.borrow_mut().render(draw_fn) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/form.rs: -------------------------------------------------------------------------------- 1 | //! Drive the renderer from Dioxus 2 | 3 | use dioxus::prelude::*; 4 | 5 | fn main() { 6 | dioxus_native::launch(app); 7 | } 8 | 9 | fn app() -> Element { 10 | let mut checkbox_checked = use_signal(|| false); 11 | 12 | rsx! { 13 | div { class: "container", 14 | style { {CSS} } 15 | form { 16 | div { 17 | input { 18 | r#type: "checkbox", 19 | id: "check1", 20 | name: "check1", 21 | value: "check1", 22 | checked: checkbox_checked(), 23 | // This works too 24 | // checked: "{checkbox_checked}", 25 | oninput: move |ev| checkbox_checked.set(ev.checked()), 26 | } 27 | label { r#for: "check1", "Checkbox 1 (controlled)" } 28 | } 29 | div { 30 | input { 31 | r#type: "checkbox", 32 | id: "check3", 33 | name: "check3", 34 | value: "check3", 35 | } 36 | label { r#for: "check3", "Checkbox 1 (uncontrolled with for)" } 37 | } 38 | div { 39 | label { 40 | input { 41 | r#type: "checkbox", 42 | name: "check2", 43 | value: "check2", 44 | } 45 | "Checkbox 2 (uncontrolled nested)" 46 | } 47 | } 48 | div { 49 | label { r#for: "radio1", id: "radio1label", 50 | input { 51 | r#type: "radio", 52 | name: "radiobuttons", 53 | id: "radio1", 54 | value: "radiovalue1", 55 | checked: true, 56 | } 57 | "Radio Button 1" 58 | } 59 | } 60 | div { 61 | label { r#for: "radio2", id: "radio2label", 62 | input { 63 | r#type: "radio", 64 | name: "radiobuttons", 65 | id: "radio2", 66 | value: "radiovalue2", 67 | } 68 | "Radio Button 2" 69 | } 70 | } 71 | div { 72 | label { r#for: "radio3", id: "radio3label", 73 | input { 74 | r#type: "radio", 75 | name: "radiobuttons", 76 | id: "radio3", 77 | value: "radiovalue3", 78 | } 79 | "Radio Button 3" 80 | } 81 | } 82 | div { 83 | input { r#type: "file", name: "single_file", id: "file1" } 84 | label { r#for: "file1", "File Select Single" } 85 | } 86 | div { 87 | input { 88 | r#type: "file", 89 | name: "multiple_files", 90 | id: "file2", 91 | multiple: true, 92 | } 93 | label { r#for: "file2", "File Select Multiple" } 94 | } 95 | } 96 | div { "Checkbox 1 checked: {checkbox_checked}" } 97 | } 98 | } 99 | } 100 | 101 | const CSS: &str = r#" 102 | 103 | .container { 104 | display: flex; 105 | flex-direction: column; 106 | justify-content: center; 107 | align-items: center; 108 | height: 100vh; 109 | width: 100vw; 110 | } 111 | 112 | 113 | form { 114 | margin: 12px 0; 115 | display: block; 116 | } 117 | 118 | form > div { 119 | margin: 8px 0; 120 | } 121 | 122 | label { 123 | display: inline-block; 124 | } 125 | 126 | input { 127 | /* Should be accent-color */ 128 | color: #0000cc; 129 | } 130 | 131 | input[type=radio]:checked { 132 | border-color: #0000cc; 133 | } 134 | 135 | "#; 136 | -------------------------------------------------------------------------------- /packages/dioxus-native/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dioxus-native" 3 | version = "0.7.0" 4 | authors = ["Jonathan Kelley", "Nico Burns"] 5 | edition = "2021" 6 | description = "Native renderer for Dioxus based on blitz" 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/DioxusLabs/dioxus/" 9 | homepage = "https://dioxuslabs.com/learn/0.7/getting_started" 10 | keywords = ["dom", "ui", "gui", "react"] 11 | 12 | [features] 13 | default = ["accessibility", "hot-reload", "net", "html", "svg", "system-fonts", "clipboard", "file_dialog"] 14 | prelude = ["dep:dioxus-core-macro", "dep:dioxus-hooks", "dep:dioxus-signals", "dep:dioxus-stores", "dep:manganis"] 15 | 16 | # DOM features 17 | svg = ["blitz-dom/svg", "blitz-paint/svg"] 18 | floats = ["blitz-dom/floats"] 19 | incremental = ["blitz-dom/incremental"] 20 | autofocus = ["blitz-dom/autofocus"] 21 | system-fonts = ["blitz-dom/system_fonts"] 22 | 23 | # Shell Features 24 | clipboard = ["blitz-shell/clipboard"] 25 | file_dialog = ["file-dialog"] # Backwards compat. Remove in next breaking version 26 | file-dialog = ["blitz-shell/file_dialog"] 27 | accessibility = ["blitz-shell/accessibility", "blitz-dom/accessibility"] 28 | data-uri = ["blitz-shell/data-uri"] 29 | 30 | # Renderer features 31 | vello = ["alt-renderer", "dep:anyrender_vello"] 32 | vello-hybrid = ["alt-renderer", "dep:anyrender_vello_hybrid"] 33 | skia = ["alt-renderer", "dep:anyrender_skia"] 34 | vello-cpu = ["alt-renderer", "vello-cpu-pixels"] 35 | vello-cpu-pixels = ["alt-renderer", "vello-cpu-base", "anyrender_vello_cpu/pixels_window_renderer"] 36 | vello-cpu-softbuffer = ["alt-renderer", "vello-cpu-base", "anyrender_vello_cpu/softbuffer_window_renderer"] 37 | vello-cpu-base = ["alt-renderer", "dep:anyrender_vello_cpu"] 38 | alt-renderer = [] 39 | 40 | # Other features 41 | net = ["dep:tokio", "dep:blitz-net"] 42 | html = ["dep:blitz-html"] 43 | 44 | # Dev 45 | hot-reload = ["dep:dioxus-cli-config", "dep:dioxus-devtools"] 46 | 47 | # Debug/Logging 48 | log-times = ["log-phase-times", "log-frame-times"] 49 | log-phase-times = ["blitz-dom/log_phase_times"] 50 | log-frame-times = [ 51 | "anyrender_vello_cpu?/log_frame_times", 52 | "anyrender_vello?/log_frame_times", 53 | "anyrender_vello_hybrid?/log_frame_times", 54 | "anyrender_skia?/log_frame_times", 55 | ] 56 | tracing = ["dep:tracing", "dioxus-native-dom/tracing", "blitz-shell/tracing", "blitz-dom/tracing"] 57 | 58 | [dependencies] 59 | # Blitz dependencies 60 | blitz-dom = { workspace = true } 61 | blitz-html = { workspace = true, optional = true } 62 | blitz-net = { workspace = true, optional = true } 63 | blitz-paint = { workspace = true, optional = true } 64 | blitz-traits = { workspace = true } 65 | blitz-shell = { workspace = true } 66 | 67 | # AnyRender dependencies 68 | anyrender = { workspace = true } 69 | anyrender_vello = { workspace = true, optional = true} 70 | anyrender_vello_hybrid = { workspace = true, optional = true} 71 | anyrender_vello_cpu = { workspace = true, optional = true} 72 | anyrender_skia = { workspace = true, optional = true} 73 | 74 | # DioxusLabs dependencies 75 | dioxus-core = { workspace = true } 76 | dioxus-html = { workspace = true } 77 | dioxus-native-dom = { workspace = true } 78 | dioxus-asset-resolver = { workspace = true, features = ["native"] } 79 | dioxus-history = { workspace = true } 80 | dioxus-document = { workspace = true } 81 | 82 | # Dioxus hot reload 83 | dioxus-cli-config = { workspace = true, optional = true } 84 | dioxus-devtools = { workspace = true, optional = true } 85 | 86 | # Dioxus prelude 87 | dioxus-hooks = { workspace = true, optional = true } 88 | dioxus-signals = { workspace = true, optional = true } 89 | dioxus-stores = { workspace = true, optional = true } 90 | dioxus-core-macro = { workspace = true, optional = true } 91 | manganis = { workspace = true, features = ["dioxus"], optional = true } 92 | 93 | # Windowing & Input 94 | winit = { workspace = true } 95 | keyboard-types = { workspace = true } 96 | 97 | # IO & Networking 98 | tokio = { workspace = true, features = ["rt-multi-thread"], optional = true } 99 | webbrowser = { workspace = true } 100 | 101 | # Other dependencies 102 | tracing = { workspace = true, optional = true } 103 | rustc-hash = { workspace = true } 104 | futures-util = { workspace = true } 105 | 106 | [target.'cfg(all(target_os = "ios", target_abi = "sim"))'.dependencies] 107 | anyrender_vello_cpu = { workspace = true, features = ["pixels_window_renderer"]} 108 | 109 | [package.metadata.docs.rs] 110 | all-features = true 111 | -------------------------------------------------------------------------------- /packages/blitz-dom/src/util.rs: -------------------------------------------------------------------------------- 1 | use crate::node::{Node, NodeData}; 2 | use color::{AlphaColor, Srgb}; 3 | use style::color::AbsoluteColor; 4 | 5 | pub type Color = AlphaColor; 6 | 7 | #[cfg(feature = "svg")] 8 | use std::sync::{Arc, LazyLock}; 9 | #[cfg(feature = "svg")] 10 | use usvg::fontdb; 11 | #[cfg(feature = "svg")] 12 | pub(crate) static FONT_DB: LazyLock> = LazyLock::new(|| { 13 | let mut db = fontdb::Database::new(); 14 | db.load_system_fonts(); 15 | Arc::new(db) 16 | }); 17 | 18 | #[derive(Clone, Copy, Debug)] 19 | pub enum ImageType { 20 | Image, 21 | Background(usize), 22 | } 23 | 24 | /// A point 25 | #[derive(Clone, Debug, Copy, Eq, PartialEq)] 26 | pub struct Point { 27 | /// The x coordinate 28 | pub x: T, 29 | /// The y coordinate 30 | pub y: T, 31 | } 32 | 33 | impl Point { 34 | pub const ZERO: Self = Point { x: 0.0, y: 0.0 }; 35 | } 36 | 37 | // Debug print an RcDom 38 | pub fn walk_tree(indent: usize, node: &Node) { 39 | // Skip all-whitespace text nodes entirely 40 | if let NodeData::Text(data) = &node.data { 41 | if data.content.chars().all(|c| c.is_ascii_whitespace()) { 42 | return; 43 | } 44 | } 45 | 46 | print!("{}", " ".repeat(indent)); 47 | let id = node.id; 48 | match &node.data { 49 | NodeData::Document => println!("#Document {id}"), 50 | 51 | NodeData::Text(data) => { 52 | if data.content.chars().all(|c| c.is_ascii_whitespace()) { 53 | println!("{id} #text: "); 54 | } else { 55 | let content = data.content.trim(); 56 | if content.len() > 10 { 57 | println!( 58 | "#text {id}: {}...", 59 | content 60 | .split_at(content.char_indices().take(10).last().unwrap().0) 61 | .0 62 | .escape_default() 63 | ) 64 | } else { 65 | println!("#text {id}: {}", data.content.trim().escape_default()) 66 | } 67 | } 68 | } 69 | 70 | NodeData::Comment => println!(""), 71 | 72 | NodeData::AnonymousBlock(_) => println!("{id} AnonymousBlock"), 73 | 74 | NodeData::Element(data) => { 75 | print!("<{} {id}", data.name.local); 76 | for attr in data.attrs.iter() { 77 | print!(" {}=\"{}\"", attr.name.local, attr.value); 78 | } 79 | if !node.children.is_empty() { 80 | println!(">"); 81 | } else { 82 | println!("/>"); 83 | } 84 | } // NodeData::Doctype { 85 | // ref name, 86 | // ref public_id, 87 | // ref system_id, 88 | // } => println!("", name, public_id, system_id), 89 | // NodeData::ProcessingInstruction { .. } => unreachable!(), 90 | } 91 | 92 | if !node.children.is_empty() { 93 | for child_id in node.children.iter() { 94 | walk_tree(indent + 2, node.with(*child_id)); 95 | } 96 | 97 | if let NodeData::Element(data) = &node.data { 98 | println!("{}", " ".repeat(indent), data.name.local); 99 | } 100 | } 101 | } 102 | 103 | #[cfg(feature = "svg")] 104 | pub(crate) fn parse_svg(source: &[u8]) -> Result { 105 | let options = usvg::Options { 106 | fontdb: Arc::clone(&*FONT_DB), 107 | ..Default::default() 108 | }; 109 | 110 | let tree = usvg::Tree::from_data(source, &options)?; 111 | Ok(tree) 112 | } 113 | 114 | pub trait ToColorColor { 115 | /// Converts a color into the `AlphaColor` type from the `color` crate 116 | fn as_color_color(&self) -> Color; 117 | } 118 | impl ToColorColor for AbsoluteColor { 119 | fn as_color_color(&self) -> Color { 120 | Color::new( 121 | *self 122 | .to_color_space(style::color::ColorSpace::Srgb) 123 | .raw_components(), 124 | ) 125 | } 126 | } 127 | 128 | /// Creates an markup5ever::QualName. 129 | /// Given a local name and an optional namespace 130 | #[macro_export] 131 | macro_rules! qual_name { 132 | ($local:tt $(, $ns:ident)?) => { 133 | $crate::QualName { 134 | prefix: None, 135 | ns: $crate::ns!($($ns)?), 136 | local: $crate::local_name!($local), 137 | } 138 | }; 139 | } 140 | -------------------------------------------------------------------------------- /packages/blitz-traits/src/net.rs: -------------------------------------------------------------------------------- 1 | //! Abstractions of networking so that custom networking implementations can be provided 2 | 3 | pub use bytes::Bytes; 4 | pub use http::{self, HeaderMap, Method}; 5 | use serde::{ 6 | Serialize, 7 | ser::{SerializeSeq, SerializeTuple}, 8 | }; 9 | use std::{ops::Deref, path::PathBuf}; 10 | pub use url::Url; 11 | 12 | /// A type that fetches resources for a Document. 13 | /// 14 | /// This may be over the network via http(s), via the filesystem, or some other method. 15 | pub trait NetProvider: Send + Sync + 'static { 16 | fn fetch(&self, doc_id: usize, request: Request, handler: Box); 17 | } 18 | 19 | /// A type that parses raw bytes from a network request into a Data and then calls 20 | /// the NetCallack with the result. 21 | pub trait NetHandler: Send + Sync + 'static { 22 | fn bytes(self: Box, resolved_url: String, bytes: Bytes); 23 | } 24 | 25 | /// A callback which gets called every time a network request completes 26 | // Q: Should we use std::task::Waker for this? 27 | pub trait NetWaker: Send + Sync + 'static { 28 | fn wake(&self, client_id: usize); 29 | } 30 | 31 | impl NetWaker for F { 32 | fn wake(&self, doc_id: usize) { 33 | self(doc_id) 34 | } 35 | } 36 | 37 | #[non_exhaustive] 38 | #[derive(Debug, Clone)] 39 | /// A request type loosely representing 40 | pub struct Request { 41 | pub url: Url, 42 | pub method: Method, 43 | pub content_type: String, 44 | pub headers: HeaderMap, 45 | pub body: Body, 46 | } 47 | impl Request { 48 | /// A get request to the specified Url and an empty body 49 | pub fn get(url: Url) -> Self { 50 | Self { 51 | url, 52 | method: Method::GET, 53 | content_type: String::new(), 54 | headers: HeaderMap::new(), 55 | body: Body::Empty, 56 | } 57 | } 58 | } 59 | 60 | #[derive(Debug, Clone)] 61 | pub enum Body { 62 | Bytes(Bytes), 63 | Form(FormData), 64 | Empty, 65 | } 66 | 67 | /// A list of form entries used for form submission 68 | #[derive(Debug, Clone, PartialEq, Default)] 69 | pub struct FormData(pub Vec); 70 | impl FormData { 71 | /// Creates a new empty FormData 72 | pub fn new() -> Self { 73 | FormData(Vec::new()) 74 | } 75 | } 76 | impl Serialize for FormData { 77 | fn serialize(&self, serializer: S) -> Result 78 | where 79 | S: serde::Serializer, 80 | { 81 | let mut seq_serializer = serializer.serialize_seq(Some(self.len()))?; 82 | for entry in &self.0 { 83 | seq_serializer.serialize_element(entry)?; 84 | } 85 | seq_serializer.end() 86 | } 87 | } 88 | impl Deref for FormData { 89 | type Target = Vec; 90 | 91 | fn deref(&self) -> &Self::Target { 92 | &self.0 93 | } 94 | } 95 | 96 | /// A single form entry consisting of a name and value 97 | #[derive(Debug, Clone, PartialEq)] 98 | pub struct Entry { 99 | pub name: String, 100 | pub value: EntryValue, 101 | } 102 | impl Serialize for Entry { 103 | fn serialize(&self, serializer: S) -> Result 104 | where 105 | S: serde::Serializer, 106 | { 107 | let mut serializer = serializer.serialize_tuple(2)?; 108 | serializer.serialize_element(&self.name)?; 109 | match &self.value { 110 | EntryValue::String(s) => serializer.serialize_element(s)?, 111 | EntryValue::File(p) => serializer.serialize_element(p.to_str().unwrap_or_default())?, 112 | EntryValue::EmptyFile => serializer.serialize_element("")?, 113 | } 114 | serializer.end() 115 | } 116 | } 117 | 118 | #[derive(Debug, Clone, PartialEq)] 119 | pub enum EntryValue { 120 | String(String), 121 | File(PathBuf), 122 | EmptyFile, 123 | } 124 | impl AsRef for EntryValue { 125 | fn as_ref(&self) -> &str { 126 | match self { 127 | EntryValue::String(s) => s, 128 | EntryValue::File(p) => p.to_str().unwrap_or_default(), 129 | EntryValue::EmptyFile => "", 130 | } 131 | } 132 | } 133 | 134 | impl From<&str> for EntryValue { 135 | fn from(value: &str) -> Self { 136 | EntryValue::String(value.to_string()) 137 | } 138 | } 139 | impl From for EntryValue { 140 | fn from(value: PathBuf) -> Self { 141 | EntryValue::File(value) 142 | } 143 | } 144 | 145 | /// A default noop NetProvider 146 | #[derive(Default)] 147 | pub struct DummyNetProvider; 148 | impl NetProvider for DummyNetProvider { 149 | fn fetch(&self, _doc_id: usize, _request: Request, _handler: Box) {} 150 | } 151 | -------------------------------------------------------------------------------- /wpt/runner/src/report.rs: -------------------------------------------------------------------------------- 1 | //! Code related to writing a report in "WPT Report" format 2 | 3 | use std::{path::Path, process::Command}; 4 | use wptreport::{ 5 | reports::wpt_report::{self, WptRunInfo}, 6 | wpt_report::WptReport, 7 | }; 8 | 9 | use crate::{TestResult, TestStatus}; 10 | 11 | fn get_git_hash(path: &Path) -> String { 12 | let output = Command::new("git") 13 | .current_dir(path) 14 | .args(["rev-parse", "HEAD"]) 15 | .output() 16 | .expect("Failed to run git rev-parse HEAD"); 17 | if !output.status.success() { 18 | panic!("Failed to run git rev-parse HEAD (command failed)") 19 | } 20 | let hash = String::from_utf8(output.stdout) 21 | .expect("Failed to run git rev-parse HEAD (non-utf8 output)"); 22 | // Remove trailing newline 23 | hash.trim().to_string() 24 | } 25 | 26 | pub fn generate_run_info(wpt_dir: &Path) -> WptRunInfo { 27 | let os_info = os_info::get(); 28 | 29 | WptRunInfo { 30 | product: String::from("blitz"), 31 | revision: get_git_hash(wpt_dir), 32 | browser_version: Some(get_git_hash(&std::env::current_dir().unwrap())), 33 | automation: true, 34 | debug: cfg!(debug_assertions), 35 | display: None, 36 | has_sandbox: false, 37 | headless: true, 38 | verify: false, 39 | wasm: false, 40 | os: String::new(), 41 | os_version: String::new(), 42 | version: String::new(), 43 | processor: String::new(), 44 | bits: match os_info.bitness() { 45 | os_info::Bitness::X32 => 32, 46 | os_info::Bitness::X64 => 64, 47 | os_info::Bitness::Unknown | _ => 0, 48 | }, 49 | python_version: 0, 50 | apple_catalina: false, 51 | apple_silicon: false, 52 | win10_2004: false, 53 | win10_2009: false, 54 | win11_2009: false, 55 | } 56 | } 57 | 58 | fn convert_status(status: TestStatus) -> wpt_report::TestStatus { 59 | match status { 60 | TestStatus::Pass => wpt_report::TestStatus::Pass, 61 | TestStatus::Fail => wpt_report::TestStatus::Fail, 62 | TestStatus::Skip => wpt_report::TestStatus::Skip, 63 | TestStatus::Crash => wpt_report::TestStatus::Crash, 64 | } 65 | } 66 | 67 | fn convert_subtest_status(status: TestStatus) -> wpt_report::SubtestStatus { 68 | match status { 69 | TestStatus::Pass => wpt_report::SubtestStatus::Pass, 70 | TestStatus::Fail => wpt_report::SubtestStatus::Fail, 71 | TestStatus::Skip => wpt_report::SubtestStatus::Skip, 72 | TestStatus::Crash => unreachable!(), 73 | } 74 | } 75 | 76 | pub fn generate_report( 77 | wpt_dir: &Path, 78 | results: Vec, 79 | time_start: u64, 80 | time_end: u64, 81 | ) -> WptReport { 82 | let results: Vec<_> = results 83 | .into_iter() 84 | .map(|test| wpt_report::TestResult { 85 | test: test.name, 86 | status: convert_status(test.status), 87 | duration: test.duration.as_millis() as i64, 88 | message: test.panic_info.and_then(|info| info.message), 89 | known_intermittent: Vec::new(), 90 | subsuite: String::new(), 91 | subtests: test 92 | .subtest_results 93 | .into_iter() 94 | .map(|subtest| wpt_report::SubtestResult { 95 | name: subtest.name, 96 | status: convert_subtest_status(subtest.status), 97 | message: if subtest.errors.is_empty() { 98 | None 99 | } else { 100 | Some(subtest.errors.join("\n")) 101 | }, 102 | known_intermittent: Vec::new(), 103 | }) 104 | .collect(), 105 | }) 106 | .collect(); 107 | 108 | WptReport { 109 | time_start, 110 | time_end, 111 | run_info: generate_run_info(wpt_dir), 112 | results, 113 | } 114 | } 115 | 116 | pub fn generate_expectations(results: &[TestResult]) -> String { 117 | let mut out = String::with_capacity(10 * 1024 * 1024); // 10MB 118 | 119 | for test in results { 120 | out.push_str(&test.name); 121 | out.push(' '); 122 | out.push_str(test.status.as_str()); 123 | out.push(' '); 124 | 125 | for subtest in &test.subtest_results { 126 | let c = match subtest.status { 127 | TestStatus::Pass => 'Y', 128 | TestStatus::Fail => 'N', 129 | TestStatus::Skip => '.', 130 | TestStatus::Crash => unreachable!(), 131 | }; 132 | out.push(c); 133 | } 134 | 135 | out.push('\n'); 136 | } 137 | 138 | out 139 | } 140 | -------------------------------------------------------------------------------- /packages/blitz-paint/src/render/box_shadow.rs: -------------------------------------------------------------------------------- 1 | use super::ElementCx; 2 | use crate::{ 3 | color::{Color, ToColorColor as _}, 4 | layers::maybe_with_layer, 5 | }; 6 | use anyrender::PaintScene; 7 | use kurbo::{Rect, Vec2}; 8 | 9 | impl ElementCx<'_> { 10 | pub(super) fn draw_outset_box_shadow(&self, scene: &mut impl PaintScene) { 11 | let box_shadow = &self.style.get_effects().box_shadow.0; 12 | 13 | // TODO: Only apply clip if element has transparency 14 | let has_outset_shadow = box_shadow.iter().any(|s| !s.inset); 15 | if !has_outset_shadow { 16 | return; 17 | } 18 | 19 | let current_color = self.style.clone_color(); 20 | let max_shadow_rect = box_shadow.iter().fold(Rect::ZERO, |prev, shadow| { 21 | let x = shadow.base.horizontal.px() as f64 * self.scale; 22 | let y = shadow.base.vertical.px() as f64 * self.scale; 23 | let blur = shadow.base.blur.px() as f64 * self.scale; 24 | let spread = shadow.spread.px() as f64 * self.scale; 25 | let offset = spread + blur * 2.5; 26 | 27 | let rect = self.frame.border_box.inflate(offset, offset) + Vec2::new(x, y); 28 | 29 | prev.union(rect) 30 | }); 31 | 32 | maybe_with_layer( 33 | scene, 34 | has_outset_shadow, 35 | 1.0, 36 | self.transform, 37 | &self.frame.shadow_clip(max_shadow_rect), 38 | |scene| { 39 | for shadow in box_shadow.iter().filter(|s| !s.inset).rev() { 40 | let shadow_color = shadow 41 | .base 42 | .color 43 | .resolve_to_absolute(¤t_color) 44 | .as_srgb_color(); 45 | 46 | let alpha = shadow_color.components[3]; 47 | if alpha != 0.0 { 48 | let transform = self.transform.then_translate(Vec2 { 49 | x: shadow.base.horizontal.px() as f64 * self.scale, 50 | y: shadow.base.vertical.px() as f64 * self.scale, 51 | }); 52 | 53 | // TODO draw shadows with matching individual radii instead of averaging 54 | let radius = self.frame.border_radii.average(); 55 | 56 | let spread = shadow.spread.px() as f64 * self.scale; 57 | let rect = self.frame.border_box.inflate(spread, spread); 58 | 59 | // Fill the color 60 | scene.draw_box_shadow( 61 | transform, 62 | rect, 63 | shadow_color, 64 | radius, 65 | shadow.base.blur.px() as f64, 66 | ); 67 | } 68 | } 69 | }, 70 | ) 71 | } 72 | 73 | pub(super) fn draw_inset_box_shadow(&self, scene: &mut impl PaintScene) { 74 | let current_color = self.style.clone_color(); 75 | let box_shadow = &self.style.get_effects().box_shadow.0; 76 | let has_inset_shadow = box_shadow.iter().any(|s| s.inset); 77 | if !has_inset_shadow { 78 | return; 79 | } 80 | 81 | maybe_with_layer( 82 | scene, 83 | has_inset_shadow, 84 | 1.0, 85 | self.transform, 86 | &self.frame.padding_box_path(), 87 | |scene| { 88 | for shadow in box_shadow.iter().filter(|s| s.inset) { 89 | let shadow_color = shadow 90 | .base 91 | .color 92 | .resolve_to_absolute(¤t_color) 93 | .as_srgb_color(); 94 | if shadow_color != Color::TRANSPARENT { 95 | let transform = self.transform.then_translate(Vec2 { 96 | x: shadow.base.horizontal.px() as f64, 97 | y: shadow.base.vertical.px() as f64, 98 | }); 99 | 100 | //TODO draw shadows with matching individual radii instead of averaging 101 | let radius = self.frame.border_radii.average(); 102 | 103 | // Fill the color 104 | scene.draw_box_shadow( 105 | transform, 106 | self.frame.border_box, 107 | shadow_color, 108 | radius, 109 | shadow.base.blur.px() as f64 * self.scale, 110 | ); 111 | } 112 | } 113 | }, 114 | ); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /wpt/runner/src/test_runners/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, sync::Arc, time::Instant}; 2 | 3 | use blitz_dom::{BaseDocument, DocumentConfig}; 4 | use blitz_html::HtmlDocument; 5 | use log::info; 6 | 7 | use crate::{SubtestCounts, TestFlags, TestKind, TestStatus, ThreadCtx}; 8 | 9 | mod attr_test; 10 | mod ref_test; 11 | 12 | pub use attr_test::process_attr_test; 13 | pub use ref_test::process_ref_test; 14 | 15 | pub struct SubtestResult { 16 | pub name: String, 17 | pub status: TestStatus, 18 | pub errors: Vec, 19 | } 20 | 21 | pub fn process_test_file( 22 | ctx: &mut ThreadCtx, 23 | relative_path: &str, 24 | ) -> ( 25 | TestKind, 26 | TestFlags, 27 | TestStatus, 28 | SubtestCounts, 29 | Vec, 30 | ) { 31 | info!("Processing test file: {relative_path}"); 32 | 33 | let file_contents = fs::read_to_string(ctx.wpt_dir.join(relative_path)).unwrap(); 34 | 35 | // Compute flags 36 | let mut flags = TestFlags::empty(); 37 | if ctx.float_re.is_match(&file_contents) { 38 | flags |= TestFlags::USES_FLOAT; 39 | } 40 | if ctx.intrinsic_re.is_match(&file_contents) { 41 | flags |= TestFlags::USES_INTRINSIC_SIZE; 42 | } 43 | if ctx.calc_re.is_match(&file_contents) { 44 | flags |= TestFlags::USES_CALC; 45 | } 46 | if ctx.direction_re.is_match(&file_contents) { 47 | flags |= TestFlags::USES_DIRECTION; 48 | } 49 | if ctx.writing_mode_re.is_match(&file_contents) { 50 | flags |= TestFlags::USES_WRITING_MODE; 51 | } 52 | if ctx.subgrid_re.is_match(&file_contents) { 53 | flags |= TestFlags::USES_SUBGRID; 54 | } 55 | if ctx.masonry_re.is_match(&file_contents) { 56 | flags |= TestFlags::USES_MASONRY; 57 | } 58 | if ctx.script_re.is_match(&file_contents) { 59 | flags |= TestFlags::USES_SCRIPT; 60 | } 61 | 62 | // Ref Test 63 | let reference = ctx 64 | .reftest_re 65 | .captures(&file_contents) 66 | .and_then(|captures| captures.get(1).map(|href| href.as_str().to_string())); 67 | if let Some(reference) = reference { 68 | let counts = process_ref_test( 69 | ctx, 70 | relative_path, 71 | file_contents.as_str(), 72 | reference.as_str(), 73 | &mut flags, 74 | ); 75 | 76 | let status = counts.as_status(); 77 | return (TestKind::Ref, flags, status, counts, Vec::new()); 78 | } 79 | 80 | // Attr Test 81 | let mut matches = ctx.attrtest_re.captures_iter(&file_contents); 82 | let first = matches.next(); 83 | let second = matches.next(); 84 | if first.is_some() && second.is_none() { 85 | // TODO: handle tests with multiple calls to checkLayout. 86 | #[allow(clippy::unnecessary_unwrap)] 87 | let captures = first.unwrap(); 88 | let selector = captures.get(1).unwrap().as_str().to_string(); 89 | drop(matches); 90 | 91 | println!("{selector}"); 92 | 93 | let (status, counts, results) = 94 | process_attr_test(ctx, &selector, &file_contents, relative_path); 95 | 96 | return (TestKind::Attr, flags, status, counts, results); 97 | } 98 | 99 | // TODO: Handle other test formats. 100 | ( 101 | TestKind::Unknown, 102 | flags, 103 | TestStatus::Skip, 104 | SubtestCounts::ZERO_OF_ZERO, 105 | Vec::new(), 106 | ) 107 | } 108 | 109 | fn parse_and_resolve_document( 110 | ctx: &mut ThreadCtx, 111 | html: &str, 112 | relative_path: &str, 113 | ) -> BaseDocument { 114 | ctx.net_provider.reset(); 115 | let mut document = HtmlDocument::from_html( 116 | html, 117 | DocumentConfig { 118 | base_url: Some(ctx.dummy_base_url.join(relative_path).unwrap().to_string()), 119 | font_ctx: Some(ctx.font_ctx.clone()), 120 | net_provider: Some(Arc::clone(&ctx.net_provider) as _), 121 | navigation_provider: Some(Arc::clone(&ctx.navigation_provider)), 122 | ..Default::default() 123 | }, 124 | ); 125 | 126 | document.as_mut().set_viewport(ctx.viewport.clone()); 127 | document.as_mut().resolve(0.0); 128 | 129 | // Load resources. 130 | // Loop because loading a resource may result in further resources being requested 131 | let start = Instant::now(); 132 | while ctx.net_provider.pending_item_count() > 0 { 133 | ctx.net_provider.for_each(|_| {}); 134 | document.as_mut().resolve(0.0); 135 | if Instant::now().duration_since(start).as_millis() > 500 { 136 | ctx.net_provider.log_pending_items(); 137 | panic!( 138 | "Timeout. {} pending items.", 139 | ctx.net_provider.pending_item_count() 140 | ); 141 | } 142 | } 143 | 144 | document.as_mut().resolve(0.0); 145 | 146 | document.into() 147 | } 148 | -------------------------------------------------------------------------------- /packages/blitz-shell/src/application.rs: -------------------------------------------------------------------------------- 1 | use crate::event::BlitzShellEvent; 2 | 3 | use anyrender::WindowRenderer; 4 | use std::collections::HashMap; 5 | use winit::application::ApplicationHandler; 6 | use winit::event::WindowEvent; 7 | use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; 8 | use winit::window::WindowId; 9 | 10 | use crate::{View, WindowConfig}; 11 | 12 | pub struct BlitzApplication { 13 | pub windows: HashMap>, 14 | pub pending_windows: Vec>, 15 | pub proxy: EventLoopProxy, 16 | } 17 | 18 | impl BlitzApplication { 19 | pub fn new(proxy: EventLoopProxy) -> Self { 20 | BlitzApplication { 21 | windows: HashMap::new(), 22 | pending_windows: Vec::new(), 23 | proxy, 24 | } 25 | } 26 | 27 | pub fn add_window(&mut self, window_config: WindowConfig) { 28 | self.pending_windows.push(window_config); 29 | } 30 | 31 | fn window_mut_by_doc_id(&mut self, doc_id: usize) -> Option<&mut View> { 32 | self.windows.values_mut().find(|w| w.doc.id() == doc_id) 33 | } 34 | } 35 | 36 | impl ApplicationHandler for BlitzApplication { 37 | fn resumed(&mut self, event_loop: &ActiveEventLoop) { 38 | // Resume existing windows 39 | for (_, view) in self.windows.iter_mut() { 40 | view.resume(); 41 | } 42 | 43 | // Initialise pending windows 44 | for window_config in self.pending_windows.drain(..) { 45 | let mut view = View::init(window_config, event_loop, &self.proxy); 46 | view.resume(); 47 | if !view.renderer.is_active() { 48 | continue; 49 | } 50 | self.windows.insert(view.window_id(), view); 51 | } 52 | } 53 | 54 | fn suspended(&mut self, _event_loop: &ActiveEventLoop) { 55 | for (_, view) in self.windows.iter_mut() { 56 | view.suspend(); 57 | } 58 | } 59 | 60 | fn window_event( 61 | &mut self, 62 | event_loop: &ActiveEventLoop, 63 | window_id: WindowId, 64 | event: WindowEvent, 65 | ) { 66 | // Exit the app when window close is requested. 67 | if matches!(event, WindowEvent::CloseRequested) { 68 | // Drop window before exiting event loop 69 | // See https://github.com/rust-windowing/winit/issues/4135 70 | let window = self.windows.remove(&window_id); 71 | drop(window); 72 | if self.windows.is_empty() { 73 | event_loop.exit(); 74 | } 75 | return; 76 | } 77 | 78 | if let Some(window) = self.windows.get_mut(&window_id) { 79 | window.handle_winit_event(event); 80 | } 81 | 82 | let _ = self.proxy.send_event(BlitzShellEvent::Poll { window_id }); 83 | } 84 | 85 | fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: BlitzShellEvent) { 86 | match event { 87 | BlitzShellEvent::Poll { window_id } => { 88 | if let Some(window) = self.windows.get_mut(&window_id) { 89 | window.poll(); 90 | }; 91 | } 92 | BlitzShellEvent::RequestRedraw { doc_id } => { 93 | // TODO: Handle multiple documents per window 94 | if let Some(window) = self.window_mut_by_doc_id(doc_id) { 95 | window.request_redraw(); 96 | } 97 | } 98 | 99 | #[cfg(feature = "accessibility")] 100 | BlitzShellEvent::Accessibility { window_id, data } => { 101 | if let Some(window) = self.windows.get_mut(&window_id) { 102 | match &*data { 103 | accesskit_winit::WindowEvent::InitialTreeRequested => { 104 | window.build_accessibility_tree(); 105 | } 106 | accesskit_winit::WindowEvent::AccessibilityDeactivated => { 107 | // TODO 108 | } 109 | accesskit_winit::WindowEvent::ActionRequested(_req) => { 110 | // TODO 111 | } 112 | } 113 | } 114 | } 115 | 116 | BlitzShellEvent::Embedder(_) => { 117 | // Do nothing. Should be handled by embedders (if required). 118 | } 119 | BlitzShellEvent::Navigate(_opts) => { 120 | // Do nothing. Should be handled by embedders (if required). 121 | } 122 | BlitzShellEvent::NavigationLoad { .. } => { 123 | // Do nothing. Should be handled by embedders (if required). 124 | } 125 | } 126 | } 127 | } 128 | --------------------------------------------------------------------------------